Filtration and Searching in Rails
We are here to discuss how to implement filtration or searching workflows step by step in Rails by keeping our Rails app clean and dry.
Before directly jumping on workflow, we need to know few concepts:
scope
Scope basically adds a class method for retrieving and querying objects.
The method is intended to return an ActiveRecord::Relation object, which is composable with other scopes as well.
class Article < ActiveRecord::Base
scope :published, -> { where(published: true) }
scope :featured, -> { where(featured: true) }
end
We are able to call the methods like this:
Article.published
Article.published.featured
ActiveSupport::Concern
The Concern is a tool provided by the ActiveSupport lib for including modules in classes, creating mixins.
require 'active_support/concern'module M
extend ActiveSupport::Concern included do
scope :disabled, -> { where(disabled: true) }
end class_methods do
...
endend
included: The code within the included block will be executed wherever the module is included.
class_methods: Define class methods from given block.
Now consider the implementation of our main agenda in our User service using these concepts.
In our user.rb file we mentioned scopes like:
class User < ActiveRecord::Base scope :first_name, -> (fname) { where first_name: fname }
scope :status, -> (status) { where status: status }
scope :address, -> (addrs) { where("address like ?", "{addrs}%")}end
The first approach that strikes in our mind is to continue like this.
User.status(“active”).address(“India”)
But we can optimize it in more better way using concerns.
# app/models/concerns/filterable.rbmodule Filterable
extend ActiveSupport::Concern module ClassMethods def filter(filtering_params) results = self.where(nil) # Return all the records of a model. filtering_params.each do |key, value|
results = results.public_send(key, value) if value.present?
end results end end
end
public_send:
It calls public methods only defined in a model. We can pass the method name as an argument to public_send and the method name can be a symbol or a string.(Internally the string is converted to a symbol and then passed for further processing)
Then we can add it into application_record.rb
class ApplicationRecord < ActiveRecord::Base …
include Filterable
…end
The reason to add it into application_record file is that, we can access the filtration functionality in each and every model.
Now in our controller, we can easily implement the searching workflow.
# user_controller.rbdef index
@users = User.filter(filtering_params)
endprivatedef filtering_params(params)
params.slice(:status, :first_name, :address)
end
Now its become easy for you to define filtration to any model by simply stating a single line and passing filtering parameters.
Thanks for reading. :)