I came across some posts which postulate that it's undesirable to
share helper methods across controller and views because UI code
(designed to render HTML) should be separate from controller code
(designed for handling requests). That makes sense but there are
times, a good example is filtering, when it only makes sense to create
a reusable method to query against the params hash from both
controller and view. For example, I created some custom filtering and
sorting. When a http request is initially made, my controller must be
able to query the database for results without user input.


# controller layer (query a default without user input)
helper_method :sort_column, :sort_direction, :for_selected_month, :for_selected_year
def driver_reports_table
@drivers = Driver.select("drivers.*,
#{sort_column}").joins([:reports, :driving_habits]).by_month(for_selected_month.to_i,
for_selected_year.to_i).order(sort_column + " " +
sort_direction).page(params[:page]).per(10)
@unit = current_unit
respond_to do |format|
format.html { render :partial => '/home/reports/
driver_reports_table', :layout => false, :locals => { :drivers =>
@drivers, :unit => @unit } }
format.json { render :json => @drivers}
end
end


private

def sort_column
if legal_attributes.include? params[:order]
params[:order]
else
"drivers.id"
end
end

def sort_direction
%w[asc desc].include?(params[:direction]) ? params[:direction] :
"asc"
end

def for_selected_month
(params[:date] && params[:date][:month]) || Time.now.month
end

def for_selected_year
(params[:date] && params[:date][:year]) || Time.now.year
end

def legal_attributes
@columns ||= Driver.column_names + DrivingHabit.column_names
end

In my view layer, the user will interact with form elements and links
to modify the values of the params hash
In one situation, I have a form tag where the user will set the date
and date and month/year attributes of the params hash:
#view
= form_tag driver_reports_path, :method => 'get', :id =>
'drivers_filter' do
%fieldset.filter_tab
= select_month(Date.today)
= select_year(Date.today, :start_year => 2012, :end_year =>
Time.now.year)
= submit_tag "Filter Date"
= render '/home/reports/driver_reports_table'

In another situation, I have links where the user will set the sort
and direction attributes of the params hash, depending on which link
they click:
#partial
= hidden_field_tag :sort, params[:sort]
= hidden_field_tag :direction, params[:direction]
...
%table.sortable
%tr
= sortable "id", :order => "drivers.id"

#helper
def sortable(column, query_string={})
title ||= column.titleize
query_string[:order] = query_string[:order] || column
css_class = 'driver_refresh'
css_class << (column ==
sort_column.gsub("driving_habits.","").gsub("drivers.","") ? " current
#{sort_direction}" : "")
query_string[:direction] = column ==
sort_column.gsub("driving_habits.","").gsub("drivers.","") &&
sort_direction == "asc" ? "desc" : "asc"
query_string[:page] = nil

content_tag :th, link_to(title,
driver_reports_path(params.merge(query_string)), {:class =>
css_class })
end

Each of the situations compensate for the other. If the user selects
an option from the select tag and thus populates the date attributes,
when they click a link, it will merge the date attributes from the
form with the link attributes that were selected and thus send them
together to the server. Conversely, when the form is submitted,
because of the hidden field tags with the current value of the sort
and direction attributes stored in them, those attributes will be send
with the form attributes.

As you can see, my view helper makes use of the sort_column and
sort_direction methods to check the current values in the params hash
to determine how to render the links. So those methods have importance
both in the controller and view layer. So it would make sense to
define the methods once and reuse them in both layers. So what is
wrong with this technique and how else could it be done so as not to
violate the MVC structure and separation of concerns?

--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-talk+unsubscribe@googlegroups.com.
To post to this group, send email to rubyonrails-talk@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Search Discussions

  • Tamouse at Apr 3, 2013 at 11:38 pm
    I am unfamiliar with this concept of separating helper functions. In
    the interest of DRY code, what you're doing makes complete sense to
    me.
    On Tue, Apr 2, 2013 at 4:54 PM, John Merlino wrote:
    I came across some posts which postulate that it's undesirable to
    share helper methods across controller and views because UI code
    (designed to render HTML) should be separate from controller code
    (designed for handling requests). That makes sense but there are
    times, a good example is filtering, when it only makes sense to create
    a reusable method to query against the params hash from both
    controller and view. For example, I created some custom filtering and
    sorting. When a http request is initially made, my controller must be
    able to query the database for results without user input.


    # controller layer (query a default without user input)
    helper_method :sort_column, :sort_direction, :for_selected_month, :for_selected_year
    def driver_reports_table
    @drivers = Driver.select("drivers.*,
    #{sort_column}").joins([:reports, :driving_habits]).by_month(for_selected_month.to_i,
    for_selected_year.to_i).order(sort_column + " " +
    sort_direction).page(params[:page]).per(10)
    @unit = current_unit
    respond_to do |format|
    format.html { render :partial => '/home/reports/
    driver_reports_table', :layout => false, :locals => { :drivers =>
    @drivers, :unit => @unit } }
    format.json { render :json => @drivers}
    end
    end


    private

    def sort_column
    if legal_attributes.include? params[:order]
    params[:order]
    else
    "drivers.id"
    end
    end

    def sort_direction
    %w[asc desc].include?(params[:direction]) ? params[:direction] :
    "asc"
    end

    def for_selected_month
    (params[:date] && params[:date][:month]) || Time.now.month
    end

    def for_selected_year
    (params[:date] && params[:date][:year]) || Time.now.year
    end

    def legal_attributes
    @columns ||= Driver.column_names + DrivingHabit.column_names
    end

    In my view layer, the user will interact with form elements and links
    to modify the values of the params hash
    In one situation, I have a form tag where the user will set the date
    and date and month/year attributes of the params hash:
    #view
    = form_tag driver_reports_path, :method => 'get', :id =>
    'drivers_filter' do
    %fieldset.filter_tab
    = select_month(Date.today)
    = select_year(Date.today, :start_year => 2012, :end_year =>
    Time.now.year)
    = submit_tag "Filter Date"
    = render '/home/reports/driver_reports_table'

    In another situation, I have links where the user will set the sort
    and direction attributes of the params hash, depending on which link
    they click:
    #partial
    = hidden_field_tag :sort, params[:sort]
    = hidden_field_tag :direction, params[:direction]
    ...
    %table.sortable
    %tr
    = sortable "id", :order => "drivers.id"

    #helper
    def sortable(column, query_string={})
    title ||= column.titleize
    query_string[:order] = query_string[:order] || column
    css_class = 'driver_refresh'
    css_class << (column ==
    sort_column.gsub("driving_habits.","").gsub("drivers.","") ? " current
    #{sort_direction}" : "")
    query_string[:direction] = column ==
    sort_column.gsub("driving_habits.","").gsub("drivers.","") &&
    sort_direction == "asc" ? "desc" : "asc"
    query_string[:page] = nil

    content_tag :th, link_to(title,
    driver_reports_path(params.merge(query_string)), {:class =>
    css_class })
    end

    Each of the situations compensate for the other. If the user selects
    an option from the select tag and thus populates the date attributes,
    when they click a link, it will merge the date attributes from the
    form with the link attributes that were selected and thus send them
    together to the server. Conversely, when the form is submitted,
    because of the hidden field tags with the current value of the sort
    and direction attributes stored in them, those attributes will be send
    with the form attributes.

    As you can see, my view helper makes use of the sort_column and
    sort_direction methods to check the current values in the params hash
    to determine how to render the links. So those methods have importance
    both in the controller and view layer. So it would make sense to
    define the methods once and reuse them in both layers. So what is
    wrong with this technique and how else could it be done so as not to
    violate the MVC structure and separation of concerns?

    --
    You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-talk+unsubscribe@googlegroups.com.
    To post to this group, send email to rubyonrails-talk@googlegroups.com.
    For more options, visit https://groups.google.com/groups/opt_out.
    --
    You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-talk+unsubscribe@googlegroups.com.
    To post to this group, send email to rubyonrails-talk@googlegroups.com.
    For more options, visit https://groups.google.com/groups/opt_out.
  • Robert Walker at Apr 3, 2013 at 11:46 pm

    John Merlino wrote in post #1104251:
    As you can see, my view helper makes use of the sort_column and
    sort_direction methods to check the current values in the params hash
    to determine how to render the links. So those methods have importance
    both in the controller and view layer. So it would make sense to
    define the methods once and reuse them in both layers. So what is
    wrong with this technique and how else could it be done so as not to
    violate the MVC structure and separation of concerns?
    First a disclaimer... Everything I'm about to say is an opinion, nothing
    more, nothing less.

    As a developer who has worked with a number of MVC frameworks I really
    don't consider Rails to be a very "pure" MVC. Views and controllers are
    more tightly coupled to each other than in other MVC systems I've worked
    with.

    Take the simple example that controller instance variable are "made
    available" in views. That's a lot of knowledge that the view has about
    the controller, where in a "pure" MVC environment a view would know
    nothing about the internal implementation of a controller.

    For example, a text field in a desktop style application is a "pure"
    view object. It has zero knowledge about the controller that interact
    with it nor the model where the controller gets the data. That text
    field can be used in any context, anywhere, in any application without
    modification.

    Compare that to something like:

    form_for @product

    This is obviously a view component (a form) yet is has direct knowledge
    of what type of object it gets it's data from (an instance of a
    Product). The product is provided to the view template by the controller
    through a form of "injection," but having this direct dependency ties
    the view to working with a specific type of model object that is
    provided by a specific controller instance.

    My point is not to start a war around the definition of MVC. I'm just
    saying that MVC is a guideline pattern. If you have a solution that
    works well for your application then feel free to use it.

    --
    Posted via http://www.ruby-forum.com/.

    --
    You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-talk+unsubscribe@googlegroups.com.
    To post to this group, send email to rubyonrails-talk@googlegroups.com.
    For more options, visit https://groups.google.com/groups/opt_out.
  • Julian Leviston at Apr 3, 2013 at 11:56 pm

    On 04/04/2013, at 10:46 AM, Robert Walker wrote:

    As a developer who has worked with a number of MVC frameworks I really
    don't consider Rails to be a very "pure" MVC. Views and controllers are
    more tightly coupled to each other than in other MVC systems I've worked
    with.

    I'm interested in which ones you do think adhere to pure MVC? What's the golden example?

    WebObjects? I'm just interested. :)

    Julian

    --
    You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-talk+unsubscribe@googlegroups.com.
    To post to this group, send email to rubyonrails-talk@googlegroups.com.
    For more options, visit https://groups.google.com/groups/opt_out.

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
grouprubyonrails-talk @
categoriesrubyonrails
postedApr 2, '13 at 9:54p
activeApr 3, '13 at 11:56p
posts4
users4
websiterubyonrails.org
irc#RubyOnRails

People

Translate

site design / logo © 2022 Grokbase