Object-oriented approach to ActiveRecord

One of my colleges (the main programmer for the company I work for) has created a nice post on creating dynamic search criteria in an object-oriented way using Rails ActiveRecord. You can find his post here.

At first, I was a little out of balance, becouse I couldn’t realy see the advantage of it. But now after working on a few big rails projects, I realized quickly that this was a real neat way of generating a query in ActiveRecord.  Yet, I had to make a few changes.

First, create a class called record_finder.rb and add the following code:

class RecordFinder
  attr_reader :parameters
  attr_accessor :order_by

  def initialize(bool_mode = 'AND') 
    @bool_mode = bool_mode 
    @sqls = [] 
    @parameters = [] 
    @includes = [] 
    @order_by = ''
  end

  def add(sql, *params) 
    @sqls << sql 
    @parameters += params 
  end

  def >add_ref(field, int) 
    add "#{field.to_s} = ?, int 
  end

  def add_wildcard(field, value) 
    add "#{field.to_s} LIKE ?", "%#{value}%"
  end 
 
  def add_range(field, range) 
    if field.instance_of?(Hash) 
      add "#{field['from']} >= ?", range['from'] 
      add "#{field['until']} = ?", range['from'] 
      add "#{field['sql_string']}", @parameters
    else
      nil
    end
  end

  def get_all
    options = { :include => @includes, :conditions => get, } 
    if @order_by.filled? 
      options[:order>] = @order_by
    end

    options 
  end

  def include>(path) 
    unless @includes.include? path 
      @includes << path 
    end
  end 

  def is_empty(var) 
    return var == nil || var == ""
  end
end

The only special difference is the add_range action. With the add_range, you can search for a field in a certain range. If the parameter is a Hash, you can use it to filter a record that has a start and end date. Otherwise, you just filter a range on one field.

The next step is to create a finder class that inherits from the RecordFinder class for all the resources you wish to search through. It might look like something as this:

class ResourceFinder < RecordFinder
  def by_user(user_id) 
    add("user_id = ?", user_id) 
  end 

  def by_name(name) 
    add_wildcard("name", name) 
  end

  def read_search(search) 
    if search 
      self.by_name unless is_empty(search[:name]) 
    end 
  end
end 

Now, you have 2 options on how to build your query. The first one is to call for all the actions needed in your controller like this:

finder = ResourceFinder.new 
finder.by_user(session[:user].id) 
finder.by_name(params[:search][:name]) 
@resource = Resource.find(:all, finder.get) 

Now you build your search in your controller. But if you have a lot of search parameters, your index action could get ugly after a while. That’s why I have created the read_search action in my ResourceFinder.rb class. Just pass you search hash to it, and build your query in there. This way, you build-up is centered in the finder class and your controller stays neat and clean 🙂