Keeping the harmony in your Rails apps

Sometimes your controllers and models start growing exponentially as your application starts getting bigger and bigger. After a while, your application will be so complex that to implement a feature or maintain existent ones will take so much time and effort that you will lose the love about the project or even your work. So, for your sake and your project’s, you actually don’t want that to happen. What could you do when you start feeling that’s going to be the case? Or what could you do to prevent that?

With the following strategics you will be able to refactor and/or start your application with a really good set of patterns and a great way to develop Ruby on Rails applications.

Controllers

Are your controllers too complex? Are they too big with too much business logic? If so, you should extract some of the logic to some patterns that I will present next.

Service Objects

Let’s say you have a controller that does a user registration, sends a welcome email and give credits to the user. That’s too much business logic to be in a controller. Models are not the place as well, because you want to go far away as possible from fat models. So what can we do?

Service Objects are the way to go. They are a simple Ruby Object that implements the user’s interactions logic.

In Rails they usually live in app/services folder and you don’t need to install anything else in your application, it’s just Plain Old Ruby Objects (PORO).

To learn a detailed way to implement Services Objects in your Rails application, take a read here.

Presenters

Does your controllers instantiates too many global variables with different models to just render one view? Presenters are for you then.

Let’s say you have the following action and you will have some other pages that use the exactly same variables, you would probably copy and paste it.

def show  
  raw_project = Project.find(params[:id])
  @project = raw_project.decorate
  @contributions = raw_project.contributions.random.decorate
  @rewards = raw_project.rewards.decorate
end  

As you can see, it would be kind of hard to test it and make sure all the places you use them will be maintained equal. So, with Presenters you would be able to do something like this:

class ProjectPresenter  
  def initialize(project_id)
    @project = Project.find(project_id)
  end

  def project
    @decorated_project ||= @project.decorate
  end

  def contributions
    @contributions ||= @project.contributions.random.decorate
  end

  def rewards
    @rewards ||= @project.rewards.decorate
  end
end  

Presenters are just Plain Old Ruby Objects, so to test it is really easy.

With presenter, your controller would look like this:

def show  
  @presenter = ProjectPresenter.new(params[:id])
end  

And your tests should make sure to just instantiate the presenter.

SQL queries

Somethings you find your self writing SQL queries to filter some data from your model in your controllers. In time to time, it will repeat and you will end up with too many different ways to do the same thing, with the long run, it will probably break the consistence of your filters. So, how do you solve it? Extract it to scopes on models.

Let’s use the following controller as example.

class CustomersController < ApplicationController  
  # …
  def appointments
    @appointments = current_customer.appointments.joins(:user_payment)
    .where("user_payments.status = 'COMPLETED' AND starts_at >= ? AND ends_at <= ?",
    Time.at(params[:start].to_i).to_datetime, Time.at(params[:end].to_i).to_datetime)

    respond_with @appointments
  end
  # …
end  

You can see in the code above that we have two filter logic in the appointments model. First is filtering by completed appointments and other is filtering between starts and ends at.

We can extract these filters to two scopes to the appointment’s model.

class Appointment < ActiveRecord::Base  
 # …

  scope :completed, -> do
    joins(:user_payment).where("user_payments.status = 'COMPLETED'")
  end

  scope :between_starts_and_ends_at, ->(starts_at, ends_at) do
    where("starts_at >= ? AND ends_at <= ?",
      Time.at(starts_at.to_i).to_datetime,
      Time.at(ends_at.to_i).to_datetime)
  end

 # …
end  

With this change, you will be able to test more easily and maintain more consistency across your application.

Your controller would look like this after this refactoring:

class CustomersController < ApplicationController  
  # …
  def appointments
    @appointments = current_customer.
                      appointments.
                      completed.
                      between_starts_and_ends_at(params[:start], params[:end])
    respond_with @appointments
  end
  # …
end  

Models

There are several strategics to avoid fat models, but I will not talk about that, I will talk about some small changes that you can do and get a great return from it.

Use scope instead of self.method_name

Scopes exists for one reason, to specify a filter method for your data. Why do you use self.method_name if we have scope :scope_name?

I know that in the end, scope does the same thing that self.method_name does, but it makes clearly to look in your
model and see what are the methods to filter data.

Here an example of refactoring you could do.

class Hint < ActiveRecord::Base  
  # …

  def self.approved
    where(approved: true)
  end

  # …
end of  

Using scopes would be like this:

class Hint < ActiveRecord::Base  
  # …

  scope :approved, -> { where(approved: true) }

  # …
end  

Also you should note that scopes are always chainable, different from class methods, that depending of how you create your methods, they might not be. To read more about it, check this awesome article.

Why not delegate?

Why do you don’t use delegate? Sometimes you will write something like the following code and will not realize that you could use Ruby’s delegate to obtain the same thing.

class Hint < ActiveRecord::Base  
  # …

  def user_name
    user.name if user.present?
  end 

  # …
end  

With delegate you would have this:

class Hint < ActiveRecord::Base  
  # …

  delegate :name, to: :user, prefix: true, allow_nil: true

  # …
end  

Use Observers, not callbacks.

You should avoid using callbacks, they usually go back to hunt you after a while. Instead you can use Observers, that provide you a good API and easy testing. To read more about why callbacks are bad, go over here.

Check the rails-observers gem out: rails/rails-observers.

Decorators

Decorators are a way to extract record related logic from views or even models to an object.

Let’s assume that you have the following view:

<div class="publication-status">  
  <% if article.published? %>
    Published at <%= article.published_at.strftime('%A, %B %e') %>
  <% else %>
    Unpublished
  <% end %>
</div>  

You can find this logic in several places and if you need to change something, you will need to do it over and over again. You may end up with inconsistence. Also there is the testing factor, using decorators, will help you test this kind of things.

Here how the decorator would look like:

class ArticleDecorator < Draper::Decorator  
  delegate_all

  def publication_status
    if published?
      "Published at #{published_at}"
    else
      "Unpublished"
    end
  end

  def published_at
    object.published_at.strftime("%A, %B %e")
  end
end  

In this example I used the draper gem.

Wrapping up

There are several patterns that you can use to improve the code base of your application and keep the harmony. I just presented some with just a little bit of information, each one of these patterns could be a different article with a lot more details. But instead, I chose to just present a summary of them so you can go and learn more on each of them.

Take some time off and research for more posts/articles about these patterns and what else could you use to help your application. Enjoy your reading time.