written by PrimeHammer

Single Responsibility Principle

In computer programming, SOLID (Single responsibility, Open-closed, Liskov substitution, Interface segregation and Dependency inversion) is a mnemonic acronym introduced by Michael Feathers for the “first five principles” named by Robert C. Martin in the early 2000s that stands for five basic principles of object-oriented programming and design. The principles, when applied together, intend to make it more likely that a programmer will create a system that  is easy to maintain and extend over time.

The principles of SOLID are guidelines that can be applied while working on software to remove code smells by causing the programmer to refactor the software’s source code until it is both legible and extensible. It is part of an overall strategy of agile and adaptive programming.

Single Responsibility Principle – an object should have only one (single) responsibility

Open Closed Principle – an object should be open for extension, but closed for modification

Liskov Substitution Principle – objects in a program should be replaceable with instances of their subtypes, without altering the correctness of that program

Interface segregation principle – many client specific interfaces are better than one general purpose interface

Dependency inversion principle – depend upon abstractions, do not depend upon concretions

Why is Single Responsibility Principle important?

Single Responsibility Principle Let’s start with a simple example. We were given the following task: download a CSV file and store it in a database. We write a test first.


require 'minitest/autorun'
require 'webmock/minitest'
require 'pry'
require_relative './download_and_save_csv'

class TestDownloadAndSaveCSV < Minitest::Test

  def setup
    Item.delete_all
    @download_and_save_csv = DownloadAndSaveCSV.new('http://example.com/list_of_items.csv')
  end

  def test_that_it_can_download_and_save_csv
    csv_response = CSV.generate do |csv|
      csv << ['John Lennon']
      csv << ['Ring Starr']
    end
    stub_request(:get, 'http://example.com/list_of_items.csv').
      with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Host'=>'example.com', 'User-Agent'=>'Ruby'}).
      to_return(:status => 200, :body => csv_response, :headers => {})
    @download_and_save_csv.call
    assert_equal 'John Lennon', Item.first.name
  end
end

In the test we stub the http request so that it returns CSV data we can parse.

The implementation is pretty simple.


require 'net/http'
require 'active_record'
require 'csv'
require 'pry'

class DownloadAndSaveCSV
  attr_reader :url

  def initialize(url)
    @url = URI(url)
  end

  def call
    csv_data = Net::HTTP.get(url)
    begin
      options = { col_sep: ",", quote_char:'"' }
      CSV.parse(csv_data, options) do |row|
        Item.create(name: row.first)
      end
      rescue NoMethodError => e
        # notify airbrake
    end
  end
end

ActiveRecord::Base.establish_connection(
  :adapter => 'sqlite3',
  :database  => 'database.sqlite'
)

class Item < ActiveRecord::Base
end

We are able to satisfy the test with just a few lines of code. So why should anyone bother with Single Responsibility Principle? Let’s say we have an http request that doesn’t return valid CSV and we would like to test that case. We want to test just the invalid CSV, but even if we are certain that the code for getting response from the URL works, we still have to setup another stub request to the endpoint.


  def test_that_it_handles_response_from_server_that_is_not_ok
    malformatted_response = "Mar 1, 2013 12:03:54 AM PST","5481545091"
    stub_request(:get, 'http://example.com/list_of_items.csv').
      with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Host'=>'example.com', 'User-Agent'=>'Ruby'}).
      to_return(:status => 200, :body => malformatted_response, :headers => {})
    @download_and_save_csv.call
    assert_equal 0, Item.count
  end

So wouldn’t it be better just to pass the invalid CSV to the part of the code that is responsible for parsing it? Usually this is the point where I realise that my object is doing too much (you may read more about TDD in David’s post). If we create a new object that handles only the CSV parsing, each object’s behaviour can be tested separately. So let’s move with parsing and storing to individual objects.


require 'net/http'
require 'active_record'
require 'csv'
require 'pry'

class DownloadAndSaveCSV
  attr_reader :url

  def initialize(url)
    @url = URI(url)
  end

  def call
    csv_data = Net::HTTP.get(url)
    ParseAndStoreCSV.new(csv_data).call
  end
end
ActiveRecord::Base.establish_connection(
  :adapter => 'sqlite3',
  :database  => 'database.sqlite'
)

class Item < ActiveRecord::Base
end

class ParseAndStoreCSV
  attr_reader :csv

  def initialize(csv)
    @csv = csv
  end

  def call
    begin
      options = { col_sep: ",", quote_char:'"' }
      CSV.parse(csv, options) do |row|
        Item.create(name: row.first)
      end
    rescue NoMethodError => e
      # notify airbrake
    end
  end

end

Wrap up

Another benefit, apart from the understandable code, is that we can reuse ParseAndStoreCSV for different purposes such as parsing a file uploaded by the user. But that’s a topic we’ll discuss at a different time.

References: SOLID Design Principles SOLID Ruby: Single Responsibility Principle Back to Basics: SOLID

  • I’m curious to find out what blog platform you happen to be using? I’m experiencing some small security problems with my latest blog and I’d like to find something more safeguarded. Do you have any solutions?

    • admin

      Hi, we use WordPress.