December 06, 2005

list_for class method

In a Rails application, I often need to display the child objects belonging to a parent object. For instance, a Community may have a number of Locations within it. If my communities_controller produces a list of Communities, I'd like to have a Locations link for each entry which will ask the locations_controller to list only the Locations for that Community. Thus, I want the LocationsController to have a list_for_community method. Coding this for each level of a structure grows wearisome.

Therefore, I took advantage of the flexibility of Ruby to create a list_for class method, below:

class ApplicationController < ActionController::Base
  # Generate a controller method to list _children_ (of this class) for
  # a given _parent_ of some other class.  
  # @parent@::       name of parent entity, as string
  #                  or symbol: @:community@
  # @order_clause@:: order for listed children: defaults to 'id ASC'
  # @children@::     plural name for collection passed to 'list' view.
  #                  Will default to the prefix of the controller name,
  #                  thus, foo_bars_controller will
  #                  yield a collection named @foo_bars.  For
  #                  that matter, product_controller will yield
  #                  a collection named @products, because
  #                  we pluralize the name anyway.
  #
  def self.list_for(parent,order_clause='id ASC',children=nil)
    unless children
      children = self.name.underscore.gsub(/_controller$/,'').pluralize
    end
    parvar = parent.to_s.underscore
    parcls = parent.to_s.camelize.constantize
    child_qual = children.to_s.singularize.underscore
    child_var = children.to_s
    pages = "@#{child_qual}_pages"
    code = %Q{
    def list_for_#{parvar}
      #{parvar} = #{parcls}.find(params[:id])
      #{pages} = Paginator.new self,
        #{parvar}.#{children}.count, 10, @params['page']
      @#{children} = #{parvar}.#{children}.find(:all,
      :order => '#{order_clause}',
      :limit => #{pages}.items_per_page,
        :offset => #{pages}.current.offset)
        @title = "#{children.humanize} for #{parvar.humanize} '\#{#{parvar}}'"
      render :action => :list
    end
  }
    module_eval code
  end
end

So, if in my locations_controller.rb file I have the line

  ...
  list_for :community, 'Service_Location ASC'
  ...
the class method will generate a list_for_community method that expects the id parameter to identify a community. It will then pass to the default 'list' view an instance variable @locations (it takes the name from the controller class) which contains only the locations for the selected community. Posted by ronlusk at December 6, 2005 12:32 PM