Multiple parameters searching with Rails

When you're working with web applications and, in most cases, with the MVC architectural pattern, it's very common to see controllers with more logic that it deserves. This problem, called "fat controllers", can make your code hard to maintain and increase the repetition of it in your application.

In Rails, we have the Active Record scopes to help us to avoid put domain logic in controllers. So, imagine you are developing a search feature for a booking service, you may have a piece of code like this:

class ReservationsController < ApplicationController
  def index
    @reservations = Reservation.all
    @reservations = @reservations.where(date: params[:date]) if params[:date].present?
    @reservations = @reservations.where(user: params[:user]) if params[:user].present?
  end
end

So, what's the problem? You may notice that if you want to search reservations by date, you'll have to repeat this piece of code. With Rails scopes, you can take the advantage of reusing common queries and scope chaining, improving the readability of our code.

Refactoring to scopes

The first change would be move that search logic into Named Scopes in our model, making our controllers more readable and with a less domain concepts. Let's make this happen:

# app/models/reservation.rb

class Reservation < ActiveRecord::Base
  scope :for_date, -> (date) { where date: date unless date.blank? }
  scope :by_user, -> (user) { where user: user unless user.blank? }
end
# app/controllers/reservations_controller.rb

class ReservationsController < ApplicationController
  def index
    @reservations = Reservation.all.for_date(params[:date]).by_user(params[:user])
  end
end

Scopes or class methods?

There are many ways to write a code that resolves this problem, class methods resolve it, too. Internally, there's no difference between them, because Active Record converts a scope into a class method. In this post, written by Plataformatec developers, they explain the difference in the application design.

Conclusion

Scopes can make your code more readable and descriptive. The example above show us a refactoring that make the search code reusable and the controllers are decoupled of the domain concepts.