views:

731

answers:

2

I've been refactoring my models and controllers in an effort to remove code duplication, and so far it seems to be all peachy creamy. Currently I've got a bit of code that is common to two of my controllers, like so:

def process_filters
# Filter hash we're going to pass to the model
  filter_to_use = {}

  # To process filters, we first query the model to find out what filters
  # we should be looking for, as the model knows what we can filter.
  Iso.available_filters.each do |filter|
    # We should have our array with our filter listing.
    # Check the purchase order model for a description
    filter_name = filter[0][:filter_name]

    # Filters are stored in a session variable, this way filters survive
    # page reloads, etc.  First thing we do, is set the session if new filters
    # have been set for the filter.
    session_name = session_filter_name( filter_name )
    if params[session_name]
      if params[session_name] == 'All'
        session[session_name] = nil
      else
        session[session_name] = params[session_name]
        filter_to_use[filter_name] = params[session_name]
      end
    elsif session[session_name]
      # If params aren't read, we still need to filter based off the users
      # session
      filter_to_use[filter_name] = session[session_name]
    end
  end

  # Just using this variable for now until I can refactor the helper code
  # so that this is passed in.
  @current_filter_values = filter_to_use

  filter_to_use[:page] = @current_page

  @isos = Iso.find_filtered( filter_to_use )

  if @isos.out_of_bounds?
    filter_to_use[:page] = session[:previous_page] = @current_page = 1
    @isos = Iso.find_filtered( filter_to_use )
  end
end

Now this code is exactly the same as code in another controller, except for the model reference (in this case Iso). Is there someway I can make that model reference dynamic?

Basically I'ld like to replace the Iso references (including the @iso variable) to something based off controller.controller_name or similar.

+1  A: 

You can move this code into module, mixin this module in all controllers you need and use self.class variable inside module to figure out concrete controller name. With this name you can use standard string functions (e.g. capitalize) and Kernel.const_get function to get classes by their names.

IDBD
Ahhh, I think Kernel.const_get is what I needed to know about. Thanks!
Mike
+1  A: 

Ok, here's what I've ended up with. By no means am I a Ruby/Rails guru, but it works for me, so hopefully it might point someone in the right direction if they're trying something similar:

module Filtered
  module Controller
    module InstanceMethods
      def process_filters
        # Filter hash we're going to pass to the model
        filter_to_use = {}

        # To process filters, we first query the model to find out what filters
        # we should be looking for, as the model knows what we can filter.
        Kernel.const_get( 
          self.controller_name.singularize.camelize ).available_filters.each do |filter|
          # We should have our array with our filter listing.
          # Check the purchase order model for a description
          filter_name = filter[0][:filter_name]

          # Filters are stored in a session variable, this way filters survive
          # page reloads, etc.  First thing we do, is set the session if new filters
          # have been set for the filter.
          session_name = session_filter_name( filter_name )
          if params[session_name]
            if params[session_name] == 'All'
              session[session_name] = nil
            else
              session[session_name] = params[session_name]
              filter_to_use[filter_name] = params[session_name]
            end
          elsif session[session_name]
            # If params aren't read, we still need to filter based off the users
            # session
            filter_to_use[filter_name] = session[session_name]
          end
        end

        # Just using this variable for now until I can refactor the helper code
        # so that this is passed in.
        self.instance_variable_set( '@current_filter_values', filter_to_use )

        filter_to_use[:page] = self.instance_variable_get( '@current_page' )

        self.instance_variable_set( "@#{self.controller_name}",
          Kernel.const_get( self.controller_name.singularize.camelize ).find_filtered(
            filter_to_use ) )

        if self.instance_variable_get( "@#{self.controller_name}" ).out_of_bounds?
          filter_to_use[:page] = session[:previous_page] = 1
          self.instance_variable_set( "@#{self.controller_name}",
            Kernel.const_get( self.controller_name.singularize.camelize ).find_filtered(
              filter_to_use ) )
        end
      end

      private

      # Quick helper function for generating session variable names
      def session_filter_name( name )
        "#{self.controller_name}_#{name}_filter".to_sym
      end

    end
  end
end

I'm including it in all my controllers via a plugin, init.rb file like so:

# Include hook for controllers
ActionController::Base.class_eval do
  include Filtered::Controller::InstanceMethods
end
Mike