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?
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With