Single Responsibility Principle(SRP) in a Rails Project

August 10, 2017
admin

Is your project getting harder to maintain even after following all the rails convention? As your project gets bigger, is it becoming more difficult to change your code?

If you follow some SOLID principles in your rails app, you will not face such problems.

In this blog we will discuss about one of the SOLID principles, named ‘Single Responsibility Principle’. We will also try to understand when and how to use it in our rails project.

Single Responsibility Principle

Single Responsibility Principle (SRP) is the “S” of well-known SOLID principle. It sounds simple, but it is very powerful. According to this principle –
 “There should not be more than one reason for a class to change.”

It means that each class/module should have only one responsibility. If it seems like a single class is performing multiple types of task then we should try to separate those functionalities and implement them in other classes or modules.

Classes which have single responsibility are highly cohesive which means that elements of the class are highly interconnected with each other. When you have highly cohesive classes, it is more likely that you are following SRP principle. On the other hand if the elements of the class are not interconnected with each other then it is more likely that you are not following SRP properly. One example of low cohesive may be ‘ApplicationHelper’ where different methods do different kinds of tasks.

Why should we implement SRP in our project?

  • Cleaner Code
  • Easier to understand
  • Easier to change code
  • Code becomes reusable
  • Easier to write test codes

According to SRP, each and every class should have only one responsibility that will make the code adjustable and understandable.

It is very important to write code in a manner that can be adjusted when necessary. Though small projects are easy to change, however, it’s hard to fix once it becomes substantial.

When we implement SRP in our project, the class/module becomes highly cohesive and less coupled with each other. As a result the classes/modules do not depend too much on each other. This has two benefits:

a) they become reusable, and
b) it becomes super easy to write test code for them.

Example 1

Suppose we have a primary Event model which keeps information about various events like party, charity, concert etc.

class Event < ActiveRecord::Base
  def to_s
    name
  end
  def pretty_optional_receipt_title
    optional_receipt_title || 'No Title'
  end
  def to_params
    token
  end

  private

  def set_token
    self.token = SecureRandom.urlsafe_base64 
  end
end

This model has an additional responsibility of generating token. We may separate this responsibility from Event model in the following way –

class Event < ActiveRecord::Base
  def to_s
    name
  end

  def pretty_optional_receipt_title
      optional_receipt_title || "No Title"
  end
end
 
class Tokenized < SimpleDelegator
  def set_token
    __getobj__.token = SecureRandom.urlsafe_base64
  end

  def to_param 
    __getobj__.token
  end
end

Now the code is cleaner. Not only that, now we will be able to use ‘Tokenized’ to add token to other models if we want to.

Example 2

Suppose we have a ‘Movie’ model.

class Movie < ActiveRecord::Base
 belongs_to :genre
 validates_presence_of :title, :genre_id, :release_date


  def get_gross_profit
    open("http://imdb.com/api/movies/#{title}/gross_profit?api_key=xyz")
  end

  def print
    <<-EOF
      #{title}, #{genre}, #{release_date}

      Gross profit: #{get_gross_profit}
    EOF
  end
end

It uses external service to get ‘gross profit’ of the movie. It also presents the movie related data. The ‘Movie’ model is performing two different types of tasks:

a) it is consuming external service, and
b) it is presenting the data together.

To implement SRP we can write additional 2 classes for these two different responsibilities.

We can write a class ‘MovieService’ for consuming external service, and we can separate the third party data to a config file.

File: .env.development

BASE_URL = "http://imdb.com/api/movies/"
API_KEY = "xyz"

File: movie_service.rb

class MovieService
 attr_accessor :movie

 def initialize(movie)
  self.movie = movie
 end

 def get_gross_profit
  price_info = open("#{ENV['BASE_URL']}/#{game.name}/gross_profit?api_key=#{ENV['API_KEY']}")
  JsonParserLib.parse(price_info)
 end
end

Let’s write another class MoviePresenter for presenting the movie data.

File: movie_presenter.rb

class MoviePresenter
 attr_accessor :movie

 def initialize(movie)
  self.movie = movie
 end

 def print
  profit_data = MovieService.new(movie)
  <<-EOF
    #{movie.title}, #{movie.genre}, #{movie.release_date}

    Gross profit: #{profit_data.get_gross_profit}
  EOF
 end
end

Now the movie class will be like this –

File: movie.rb

class Movie < ActiveRecord::Base
 belongs_to :genre
 validates_presence_of :title, :genre_id, :release_date
end
When to implement SRP in rails model

For a well maintained project, it is very important to implement SRP. However, it is also not a good idea to overdo this principle. It depends on project structure and other factors. Here are some checkpoints for implementing SRP to a project. There may be others. But I follow these 3 checkpoints –

  1. Does this model have more than 500 lines?

If the file has more than 500 lines of code, then it seems like the model has become less maintainable. In that case it is a good idea to separate the responsibilities and extract them in separate classes.

2. Do I change this model frequently?

The more frequently a file is changed, the more risky it is. If you find that you change this file a lot then it is a good idea to implement SRP. There is gem named ‘Churn’ which can create report on how many times each file has been changed over a specific time period.

3. Is this a responsibility magnet?

There are some models in each project that act like responsibility magnet. For example, for most projects, ‘User’ model is a responsibility magnet. These models have a tendency to have a lot of diverse responsibilities. Another example is, if you have a website about movies then your ‘Movie’ model will probably be responsibility magnet. Before adding any method to a model like that, we should ask ourselves whether we can extract some common functionalities and separate them into a different place.

At first, it may seem like implementing single responsibility principle is slowing down your project, but you will be pleased when the project will run smoothly. It will reduce the complexity of your project and save time for the development in the long run.

Contributor: Khaled ImranNascenia

1 Comment. Leave new

Mehreen Mansur
August 15, 2017 11:30 am

insightful

Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.