Sam Saffron
Sam Saffron

Reputation: 131112

Dynamic find conditions in active record

I have an index action in rails that can handle quite a few params eg:

params[:first_name] # can be nil or first_name
params[:age]        # can be nil or age 
params[:country]    # can be nil or country

When finding users I would like to AND all the conditions that are not nil. This gives me 8 permutations of the find conditions.

How can I can I keep my code DRY and flexible and not end up with a bunch of if statements just to build the conditions for the find. Keep in mind that if no conditions are specified I just want to return User.all

Upvotes: 5

Views: 3296

Answers (7)

sequielo
sequielo

Reputation: 1631

If you happen to be on an ancient project (Rails 2.x) and very messy, you could do something like the following for adding new fields to the original query.

Original code:

User.find(:all,
   :conditions => ['first_name LIKE ? AND age=? AND country=?',
        "#{name}%", age, country]

Adding a new dynamic condition on zip_code field:

zip_code  = params[:zip_code]   # Can be blank
zip_query = "AND zip_code = ?" unless zip_code.blank?

User.find(:all,
   :conditions => ['first_name LIKE ? AND age=? AND country=? #{zip_query}',
        "#{name}%", age, country, zip_code].reject(&:blank?)

Adding a reject(&:blank?) to the conditions arrays will filter the nil value.

Note: The other answers are much better if you are coding from zero, or refactoring.

Upvotes: 0

William
William

Reputation: 611

Using James Healy answer, I modify the code to be used in Rails 3.2 (in case anyone out there need this).

conditions = params.slice(:first_name, :age, :country)
conditions = conditions.delete_if {|key, value| value.blank?}

@users = User.where(conditions)

Upvotes: 1

Eugene
Eugene

Reputation: 1013

This works for me too

conditions = params[:search] ? params[:search].keep_if{|key, value| !value.blank?} : {}
User.all(:conditions => conditions)

Upvotes: 0

François Beausoleil
François Beausoleil

Reputation: 16515

I would normally use named scopes for something like this:

class User < ActiveRecord::Base
  named_scope :name_like, lambda {|name| {:conditions => ["first_name LIKE ?", "#{name}%"]}}
  named_scope :age, lambda {|age| {:conditions => {:age => age}}}
  named_scope :in_country, lambda {|country| {:conditions => {:country => country}}}
end

class UsersController < ActionController
  def index
    root = User
    root = root.name_like(params[:first_name]) unless params[:first_name].blank?
    root = root.age(params[:age]) unless params[:age].blank?
    root = root.country(params[:country]) unless params[:age].blank?

    @users = root.paginate(params[:page], :order => "first_name")
  end
end

That's what I normally do.

Upvotes: 3

Fran&#231;ois Beausoleil
Fran&#231;ois Beausoleil

Reputation: 16515

You could try Ambition, or a number of other ActiveRecord extensions.

Upvotes: 0

Sam Saffron
Sam Saffron

Reputation: 131112

This seems to work quite nicely:

conditions = params.slice(:first_name, :age, :country)
hash = conditions.empty? ? {} : {:conditions => conditions}
@users = User.all hash 

Upvotes: 1

James Healy
James Healy

Reputation: 15168

How about something like:

conditions = params.only(:first_name, :age, :country)
conditions = conditions.delete_if {|key, value| value.blank?}

if conditions.empty?
  User.all
else
  User.all(:conditions => conditions)
end

Upvotes: 3

Related Questions