Reputation: 231
How would I do a search on a database when the search can provide many optional parameters such as ID, Zip, City, and State? These can either have values or be blank entirely. How would I make a rails query like that?
Upvotes: 23
Views: 10139
Reputation: 67850
The usual advice is to move logic to the model and keep the controller as lean as possible. There are different approaches for the filter method, the first one:
class Record < ActiveRecord::Base
def self.filter(attributes)
attributes.select { |k, v| v.present? }.reduce(all) do |scope, (key, value)|
case key.to_sym
when :id, :zip # direct search
scope.where(key => value)
when :city, :state # regexp search
scope.where(["#{key} ILIKE ?", "%#{value}%"])
when :order # order=field-(ASC|DESC)
attribute, order = value.split("-")
scope.order("#{self.table_name}.#{attribute} #{order}")
else # unknown key (do nothing or raise error, as you prefer to)
scope
end
end
end
end
A second approach, write a bare filter
that only uses existing scopes:
class Record < ActiveRecord::Base
SUPPORTED_FILTERS = [:id, :city, ...]
scope :id, ->(value) { where(id: value) }
scope :city, ->(value) { where(city: "%#{value}%") }
...
def self.filter(attributes)
attributes.slice(*SUPPORTED_FILTERS).reduce(all) do |scope, (key, value)|
value.present? ? scope.send(key, value) : scope
end
end
end
For Rails 5, which now uses ActionController::Parameters, the syntax for the filter method is:
def self.filter(attributes)
attributes.permit(SUPPORTED_FILTERS).to_hash.reduce(all) do |scope, (key, value)|
value.present? ? scope.send(key, value) : scope
end
end
Models can be called from anywhere in your app, so they are re-usable and easier to test. Now the controller looks as simple as:
class RecordsController < ApplicationController::Base
respond_to :html, :xml
def index
@records = Record.filter(params)
end
end
Upvotes: 32
Reputation: 9
http://metautonomo.us/projects/metasearch/ is what you need.
= text_field_tag 'search[city_like]', ''
= text_field_tag 'search[zip_equals]', ''
= text_field_tag 'search[state_equals]', ''
And then just
Record.search(params[:search])
Upvotes: 0
Reputation: 34340
You can build up a query:
conditions = {}
conditions[:city] = city unless city.blank?
conditions[:zip] = zip unless zip.blank?
conditions[:state] = state unless state.blank?
Address.find(:all, :conditions => conditions)
Upvotes: 17