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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | 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['until'] else add "#{field} >= ?", range['from'] add "#{field} <= ?", range['until'] end end def has_conditions? @sqls.filled? end def add_finder(finder) if finder.has_conditions? @sqls << finder.sql_string @parameters += finder.parameters end end def sql_string @sqls.collect{|sql| "(#{sql})"}.join(" #{@bool_mode} ") end def get if @sqls.length > 0 [ sql_string ] + @parameters else nil end end def get_all options = { :include => @includes, :conditions => get, } if @order_by.filled? options[:order] = @order_by end return 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 diffrence is the add_range action. With the add_range, you can search for a field in a sertain 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 inherets from the RecordFinder class for all the resources you wish to search through. It might look like something as this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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:
1 2 3 4 5 | 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