One or more params in model find conditions with Ruby on Rails
Say I have model 'Car' and controller 'cars', and a method 'display'.
I have multiple attributes like:
in_production
,year
,make
I can easily do something开发者_StackOverflow中文版 like this to find cars that match all the parameters passed:
def display
@cars = Car.find(:all, :conditions => { :in_production => #{params[:in_production]}, :year => #{params[:year]}, :make => #{params[:make]} })`
end
So what I'm doing is coding hard links in the menu, so if I wanted to find all Nissan cars from 2009 that were in production, I would pass those values as parameters in my link.
On another page I want to show every car from 2009 that is in_production, only two params instead of three. What's the best way to dynamically alter the conditions so it will work with one, two, or three params, whilst using the same action?
Any ideas?
First of all, using
:conditions => "in_production = '#{params[:in_production]}' AND year = '#{params[:year]}' AND make = '#{params[:make]}'"
is vulnerable to SQL injection. You need to escape the user provided parameters before using them in database conditions.
Something like this should let you add conditions more dynamically depending on whether or not the parameters exist. I did not test it, so I may edit it shortly...
def display
conditions = []
conditions << [ "in_production = ?", params[:in_production] ] if params[:in_production].present?
conditions << [ "year = ?", params[:year] ] if params[:year].present?
conditions << [ "make = ?", params[:make] ] if params[:make].present?
@cars = Car.all(:conditions => conditions )
end
Certainly escape the params and ensure that you only query against fields you want to be exposed. Beyond that, you could use what is built into Rails:
Car.find_all_by_in_production_and_year_and_make(in_production, year, make)
Hand-rolling the conditions may allow for additional logic to be applied (search by year only if the year is between x and y, etc). Using the rails finders (which in turn use method_missing) keeps the API clean and flexible without having to stare at direct SQL conditions.
You could construct a Car#search method that takes the entire params hash as input, where the params are sanitized and stripped of non-exposed fields, and construct the Car#find_all_by* method call using the param names themselves. Adding new conditions to search by is then as simple as passing them in the params.
You might check out searchlogic. It uses some method missing magic to construct named_scopes that would do what you want.
http://github.com/binarylogic/searchlogic
I use SmartTuple for stuff like this. Simple, powerful, designed specifically for the task.
@cars = Car.all(:conditions => (SmartTuple.new(" AND ") +
({:in_production => params[:in_production]} if params[:in_production].present?) +
({:year => params[:year]} if params[:year].present?) +
({:make => params[:make]} if params[:make].present?)
).compile)
or
@cars = Car.all(:conditions => [SmartTuple.new(" AND "),
({:in_production => params[:in_production]} if params[:in_production].present?),
({:year => params[:year]} if params[:year].present?),
({:make => params[:make]} if params[:make].present?),
].sum.compile)
or
keys = [:in_production, :year, :make]
@cars = Car.all(:conditions => (SmartTuple.new(" AND ").add_each(keys) do |k|
{k => params[k]} if params[k].present?
end).compile)
Pick the one you like the most. :)
精彩评论