A Simple Case For Skinny Models

When beginning with Ruby on Rails, one may have learned to place logic associated with a model directly in the model class. In this post let's explore other options for code organization while keeping models as small as possible. As a starting place we will use this simple example.

You can find the code for this blog post on Github.

Logic in our model

The following is a Company model with a method that determines if a company is considered a small business.


class Company < ActiveRecord::Base
  has_many :employees
  
  def small_business?
    employees.count <= 500
  end
end

Looks simple enough, now lets add a model spec.


RSpec.describe Company do
  context '#small_business?' do 
    before { subject.save! }
    
    context 'less than 500 employees' do
      before do
        10.times { subject.employees.create! }
      end
      it 'is a small business' do
        expect(subject).to be_small_business
      end
    end

    context 'more than 500 employees' do
      before do
        501.times { subject.employees.create! }
      end

      it 'is not a small business' do
        expect(subject).to_not be_small_business
      end
    end
  end
end

Let's run it and see what we get.

..

Finished in 0.53566 seconds (files took 1.37 seconds to load)

Two passing specs completed in half a second. We should be good to go, so what's so wrong with this approach?

1. Violates Single Responsibility Principal

With our current implementation, Company is responsible for how to access data from the database as well as determining company size. We only added one method but if we continue to add responsibilities to Company, it will become harder to understand and test.

2. Instantiating actual database objects

Multiple ActiveRecord creates are slow. The full test run took a half second, which doesn't seem like a lot but we only wrote 2 tests. If we had 2000 tests our runtime would be over 15 minutes. Let's see if we can clean up our code and make our tests faster as a result.

Logic out of our model

First, we will extract the code for determining a company's size into a simple class:


class CompanySizeStandard
  def initialize(company)
    @company = company
  end
  def small?
    company.employees.count <= 500
  end
  private
  attr_reader :company
end

This new class is responsible for determining a company's size standard (Government jargon). CompanySizeStandard takes company object which is used to figure out its size. Lets writes some tests to go with our new class:


RSpec.describe CompanySizeStandard do
  subject { CompanySizeStandard.new(company) }
  context '#small?' do
    let(:company) { double(:company, employees: employees) }
    context 'less than 500 employees' do
      let(:employees) { double(:employees, count: 10) }

      it 'is a small business' do
        expect(subject).to be_small
      end
    end

    context 'more than 500 employees' do
      let(:employees) { double(:employees, count: 501) }

      it 'is not a small business' do
        expect(subject).to_not be_small
      end
    end
  end
end



Now let's re-run our tests.

Finished in 0.00234 seconds (files took 1.36 seconds to load)

Our new tests run without a single database call. This greatly improves the speed of our test run and can scale as the application grows. Additionally, we were able to separate our logic into a class that describes exactly what it does. If we need to change how we determine what a small business is, we can modify CompanySizeStandard without having to touch the Company model.

Conclusion

Hopefully this post has sparked your interest on the possibilities for structuring your code. You can find the full source code from this blog post on GitHub. For some further reading, take a look at the fantastic blog post, 7 Patterns to Refactor Fat ActiveRecord Models, which covers many techniques on how to extract logic from your model classes.