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.)

Whats new in Rails 5 ?

I watched the live stream of DHH’s Railsconf 2015 keynote yesterday and he was talking about Rails 5. Here is a quick look at the new things I found out thats coming in Rails 5.

1. Turbolinks 3

Consider you have a simple blogging application where you can add posts and comments. So there are basically 2 important sections of a blog page. One the blog post self and second are the comments. Section one is not going to change frequently. But section two which is comments will keep changing. So in turbolinks 3, you can tell the server to just update the comment section and keep the rest of the page as it is, as there is no need to refresh the whole page. This is an attempt to reduce the page load time even further and improve the experience for the user.

You can specify that this part of the page will not change by mentioning something like this “turbolink-permanent” in HTML.

2. ActionCable

This sounds interesting. This is an attempt to implement WebSockets the Rails way. Making it super easy to subscribe to a channel, sending and receiving messages over the channel.

Screen Shot 2015-04-21 at 8.06.09 pm

Screen Shot 2015-04-21 at 8.06.58 pm

The code looks like a controller code, so I think before_filters should also work with classes inheriting from ActionCable::Channel::Base.

P.S.
1. Code snippets are screenshots from DHH’s keynote live streaming.
2. This is just glimpse of what I took away from his keynote. I did not see the whole thing from the start, so I am not sure what else he talked about.