Filtration and Searching in Rails

Gursimranjit Singh
2 min readOct 28, 2019

--

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
...
end
end

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)
end
privatedef 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. :)

--

--