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 🙂