Simple search in Rails applications

Almost every application does need search feature. Even if not direct feature You need some efficient way to narrow data selection. And I do like to have clean and compact code :) so I have spent some time to create my own search schema which I’d like to share with You.

First – what I’m talking about? Let’s assume You need to create some kind of report and has to provide several options to narrow down scope of entries. Since Active Record requires You to create part of SQL query in :conditions it is easy to create some not nice looking code with string joining, wondering if You should add AND operator and so on.

In most cases You don’t need full blown search capabilities (like with Ferret) and You want to keep Your dependencies list as short as possible.

How to search?

Right tools when You need to see some details
Right tools when You need to see some details (c) Wrote

I came up with following code:

conditions = ["1=1"]
cond_data = []
includes = [:association_used_for_display]


unless params[:condition_1].blank?
  conditions << "field_1 = ?"
  cond_data << params[:condition_1]
end

unless params[:condition_2].blank?
  conditions << "other_table.field_2 = ?"
  cond_data << params[:condition_2]
  includes << :some_association #for other_table
end

unless params[:condition_3].blank?
  conditions << "one_more_table.field_3 like ?"
  cond_data << "#{params[:field_3]}%"
  includes << :one_more_association #to get one_more_table
end

  
@results = Model.find(
  :all,
  :conditions => [ conditions.join(" and "), *cond_data ],
  :include => includes)

The idea is to keep array conditions with entries to create :conditions option and cond_data array to keep arguments to substitute all question marks.

With includes we can add associations we need to load when criteria need it. We can preset some associations to force load some data always needed for view creation (association_used_for_display in this example).

Using this schema allows easilly add new criteria – it just another unless statement.

So what do You think about it?

I’m curious what do You think about this approach – or You do have some own solutions? I’m waiting for Your comments!

Join the Conversation

10 Comments

  1. I used this solution for RoR 1.2.x. In solution you present I have problems with dates ex: transactions between two dates. For Ror 2.x I prefer to use special model for search eg. Search and named_scopes. I plan to describe it in my blog soon.

  2. first of all, you are opening up yourself to sql injection attacks as you are not sanitizing the params before placing them in the sql.

    Secondly, as already mentioned, named scopes are the way to go here as they can be concactenated.

    named_scope(:older_than, do |age|
    {
    :conditions => [“users.age > :age”, {:age => age}]
    }
    end)

    named_scope(:taller_than, do |height|
    {
    :conditions => [“users.height > :height”, {:height => height}]
    }
    end)

    User.older_than(35).taller_than(4)

    ilan

  3. @ilan
    First of all – where do You see vulnerability to SQL injection? All parameters to query go through Rails sanitizing with ? placeholder

    Second – named_scope is available in Rails 2.1. For older versions You need to deal with search on Your own. And I have to deal on daily basis with versions ‘before’ 2.1.

  4. @Witold,
    I didn’t see any place in your original text where you stipulated the constraint that you were working with rails versions prior to 2.1. If this is the case, then I would just yank out the 2.1 stuff in named scopes and stick it in your 1.2/2.0 rails libs. It would be a far better approach than reinventing the wheel..

    As for injecting raw values into strings, yes you are correct (I see now that you are passing it in as a conditions) but it is still recommended to use the substitution method as it is far more maintainable since rails will do the type conversion for you.. and they had this way before 1.2..

    ilan

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.