Using Service Classes to improve code-quality

ServiceClasses are nothing but plain old ruby classes. But they go a long way to keep your controllers thin and models thin. To show what I mean by this, lets look at some code. This is the code in controller to download data following some set of rules.

class ImportExportsController < ApplicationController

  def multi_column_download_template
    rows = []
    header = ['Spec Number', 'place', 'measurement',
              'measurement number']

    file_name = "multi-column-template" + "_#{Time.now.to_i}.xlsx"
    file_path = "#{Rails.root}/tmp/#{file_name}"

    specs = @inspection_master.row_no_wise_specs
    specs.each do |spec|
      header << spec.id.to_s + "#" + spec.characteristic
    end
    rows << header

    feature_repeat = specs.collect(&:feature_repeats).max
    measurement    = specs.collect(&:minimum_readings_required).max

    inspection = Inspections::Inspection.new(
                   :feature_repeats => feature_repeat, 
                   :minimum_readings_required => measurement)
    place_cols = inspection.calculate_records

    @inspection_master.inspection_serial_numbers.each_with_index do |d, index|
      rows << [index+1, d.place, d.measurement, d.serial_number]      
    end
  
    generate_and_download_file(rows, file_path, file_name)
  end

  def generate_and_download_file(rows, file_path, file_name)
    #code which generate a xls file and send it as response
  end

end

This is bad. The controller should not look like this. It is not thin. It is difficult to understand. And the bigger problem which is difficult to see the moment we write code is that things will change. Believe me change is the only constant. And the cost of those changes will be much more higher. So comes in Service classes.

The ideology behind service classes is that it should have only one purpose to serve and nothing else.

And I can clearly see a purpose which our new service class should serve and that is to provide the data for our template download and nothing else. I keep the service classes under app/services folder. Also I do not think I need to iterate much on the importance of the name of the service class you choose. It goes without saying that the name should tell the purpose it is going to serve. I will name my service class MultiColumnDownload and I put it under app/services folder. This is how the service class looks like:


class MultiColumnDownload

  def initialize(inspection_master_id)
    @inspection_master = InspectionMaster.find inspection_master_id
    @file_name = "Multi-column-template-" + "_#{Time.now.to_i}.xlsx"
  end

  def file_name
    @file_name
  end

  def file_path
    "#{Rails.root}/tmp/#{@file_name}"
  end

  def run!
    initialize_header
    find_specs
    insert_data
    @rows
  end

  private

  def initialize_header
    header = ['Spec Number', 'place', 'measurement', 'measurement number']
    specs = @inspection_master.row_no_wise_specs
    specs.each do |spec|
      header << spec.id.to_s + "#" + spec.characteristic
    end
    @rows = [header]
  end

  def find_specs
    @specs = @inspection_master.specifications
  end

  def find_feature_repeats
    @specs.collect(&:feature_repeats).max
  end

  def find_minimum_readings_required
    @specs.collect(&:minimum_readings_required).max
  end

  def insert_data
    @inspection_master.inspection_serial_numbers.each_with_index do |d, index|
      @rows << [index+1, d.place, 
                d.measurement, d.serial_number]
    end
  end

end

And now our controller looks like this:


class ImportExportsController < ApplicationController

  def multi_column_download_template
    download_object = MultiColumnDownload.new(@inspection_master.id)
    rows = download_object.run!
    generate_and_download_file(rows, download_object.file_path, download_object.file_name)
  end

  def generate_and_download_file(rows, file_path, file_name)
    #code which generate a xls file and send it as response
  end
end

In the controller, I just have to instantiate an object for our newly created service class and we call this object service object. And call the run! method. Keeping our controller code very thin just the way it should be.

Now this is the code I like and proud of. Not the earlier one. Now in future if I want to change the formatting of this template or add or remove some data, I know where to look for the code and where to make changes. Also I do not have to change anything in my controller code as long as the service class returns data.

Have we reduced the number of lines of code? NO. In fact we have increased the lines of code by at least one and half times.

Have we reduced the complexity of code? YES. Now the code is easier to understand and maintain over long term.

I can only hope that you find this useful and make service objects without any fear of increasing code complexity or number of lines of code. I certainly do..!!!

(p.s. I have reduced the complexity of the controller to not deviate away from what we are trying to understand here.)

Advertisements

5 thoughts on “Using Service Classes to improve code-quality

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s