Join our team!

We're looking for mid-level and senior software developers who love working throughout the stack, and have a track record for designing, building, shipping, and supporting web services and applications. Ideal candidates love Ruby, Rails and Ember, and using BDD and agile processes to ship working software quickly! Experience with Redis, Postgres, RabbitMQ, and ElasticSearch are a huge plus!

Learn more about our mission by visiting IoraHealth.com and by following @IoraHealth.

TDD'ing a Ruby refactor: "Module Extraction"

At Iora we are always discussing the best ways to architect our apps. We tend to do a number of things to isolate concerns and reduce complexity. Sometimes we start out creating a gem we will need, other times we extract code into libraries as complexity grows. When we do this we often have an eye on what parts of our app can one day be stand alone services. This allows us to keep our ActiveRecord Models light on domain logic.

We plan on writing a few posts about isolation of concerns and reducing complexity, and I wanted to start small. This first post is a simple example of how you can extract some logic from a model into a module that is reusable.

User Model

In our User model we use a unique identifier (GUID) for each third party service we communicate with. When we create a user we want to generate their GUIDs for each service. Each guid is stored on the user table in a separate column. For this example we have three GUIDs guid_a, guid_b, and guid_c.

require 'spec_helper'

describe User do
  let(:known_guid_keys) { %w[ guid_a guid_b guid_c ] }
  describe User, "before create" do
    before do
      subject.stubs(generate_unique_id: "xxx-yyy")
      subject.save
    end

    it "sets all known GUIDs" do
      known_guid_keys.map { |k| subject.send(k) }.should == [ "xxx-yyy" ] * known_guid_keys.size
    end
  end
end

Now to get this to pass we can have our User model create the GUIDs in a before_create callback

class User < ActiveRecord::Base
  before_create :generate_guids

  def generate_guids
    User.guid_keys.each do |key|
      guid = generate_unique_id
      self.send(key+"=", guid)
    end
  end

  def self.guid_keys
    %w{guid_a guid_b guid_c}
  end

  def generate_unique_id
    Digest::MD5.hexdigest(Time.now.to_f.to_s + rand.to_s)
  end
end

So this is a pretty straight forward example. And it is not a lot of logic on the model. That said it does not mean that it should not be extracted. I had two motivations for extracting this into a module

  1. The User model was getting large with a lot of different concerns.
  2. There was also a Patient model that would need GUIDs in the future.

Refactoring to a module

Before you begin a refactor you need to be confident in you tests. So that once you move some code your test should still pass.

Extracting the GUID logic out into a module I get:

module Iora
  module GUID
    extend ActiveSupport::Concern

    included do
      before_create :generate_guids
    end

    def generate_guids
      self.class.guid_keys.each do |key|
        guid = generate_unique_id
        self.send(key+"=", guid)
      end
    end

    def generate_unique_id
      Digest::MD5.hexdigest(Time.now.to_f.to_s + rand.to_s)
    end
  end
end

In order for the module to be reusable we cannot reference User.guid_keys directly (line 11). So I make use of the model’s class method.

class User < ActiveRecord::Base
  include Iora::GUID

  def self.guid_keys
    %w{guid_a guid_b guid_c}
  end
end

# == Schema Information
#
# Table name: users
#
#  id     :integer     not null, primary key
#  guid_a :string(255)
#  guid_b :string(255)
#  guid_c :string(255)

At this point we have refactored the logic. Our specs should still pass because we have not changed any behavior. Once we confirm this we should then refactor our specs.

Refactoring the specs

When we reuse the module in other models we do not want to rewrite the spec each time. So let’s move the existing spec to a shared example and call it from the user spec.

require 'spec_helper'

shared_examples Iora::GUID do
  describe Iora::GUID, "before create" do
    before do
      subject.stubs(generate_unique_id: "xxx-yyy")
      subject.save
    end

    it "sets all known GUIDs" do
      known_guid_keys.map { |k| subject.send(k) }.should == [ "xxx-yyy" ] * known_guid_keys.size
    end
  end
end

Now to use that in the models spec we do the following

require 'spec_helper'

describe User do
  it_behaves_like Iora::GUID do
    let(:known_guid_keys) { %w[ guid_a guid_b guid_c ] }
  end
end

it_behaves_like is how you call RSpec’s shared examples. You can learn more about shared examples here.

Now that we have nicely extracted this we can very easily apply it to other models. Our Patient model needs to interact with a number of third party services also. And again we want to use a unique id for each service.

Let’s start with the spec

require 'spec_helper'

describe Patient do
  it_behaves_like Iora::GUID do
    let(:known_guid_keys) { %w[ guid_d guid_e ] }
  end
end

That was easy. The only difference being the guid keys. Now let’s get it to pass.

require 'iora/guid'

class Patient < ActiveRecord::Base
  include Iora::GUID

  def self.guid_keys
    %w{guid_d guid_e}
  end
end

# == Schema Information
#
# Table name: users
#
#  id     :integer     not null, primary key
#  guid_d :string(255)
#  guid_e :string(255)

This was a simple example of how you can extract a concern into a module and make it reusable. Patrick will soon be posting about how he extracted more complex code using DCI type roles and contexts.

Code from this example can be viewed here. You can follow the step by step process of how I extracted this logic to a module by looking at each commit.