This blog has been archived. Our writing has moved to makandra cards.
The blog of , a Ruby on Rails development team

How to split a Ruby class into multiple source files

Many Rails projects have one or two models that act as a hub for the rest of the application. One is usually the User model, the others depend on your problem domain. If your application is about managing projects, chances are the Project class does a lot of things.

Such larger models are likely concerned with multiple themes like "permission checks" or "budget calculations". Each of these concerns requires a couple of validations and callbacks here, and a method definition there.

Lumping all these concerns into one large file blurs the lines between them. Someone opening the Project class for the first time will have a hard time seeing its individual aspects. In addition, a developer will usually be interested in a single concern in order to fix a bug or implement a new feature.

A way to better organize a large class is to split it into multiple source files. Ruby comes with a built-in tool for that: modules. Unfortunately vanilla Ruby modules lack support for many idioms popular in modern Ruby. Most importantly, we have become accustomed to composing our classes with meta-programming macros such as has_many, validates_presence_of or after_save. And modules weren't built with macros in mind.

Enter Modularity. Modularity is to your models what partials are for your views. It lets you write classes like this:

class Project
  does 'project/billing'
  does 'project/budgets'
  does 'project/permissions'
end

This Project class is now organized in three additional sources files called traits:

app/models/project.rb
app/models/project/billing_trait.rb
app/models/project/budgets_trait.rb
app/models/project/permissions_trait.rb

Each trait is packaging a single concern, including its method definitions and macro calls. Let's take a look at the trait that deals with project billing:

module Project::BillingTrait
  as_trait do

    has_defaults :billed => false
    belongs_to :billing_address_id
    validates_presence_of :bill_date, :if => :billed?

    def billed?
      billing_address_id.present?
    end

    def payment_overdue?
      billed? && bill_date + 21.days < Time.now
    end

  end
end

Organizing a model into partial classes is only one of Modularity's many uses. Most importantly you can parametrize your traits. Just like has_many :cats and has_many :dogs will add different methods to your class, a parametrized trait can provide different behavior depending on which argument they are included with.

Find out more about rolling your own meta-programming macros with Modularity over at Agile Web Operations.

Growing Rails Applications in Practice
Check out our e-book:
Learn to structure large Ruby on Rails codebases with the tools you already know and love.

Recent posts

Our address:
makandra GmbH
Werner-von-Siemens-Str. 6
86159 Augsburg
Germany
Contact us:
+49 821 58866 180
info@makandra.de
Commercial register court:
Augsburg Municipal Court
Register number:
HRB 24202
Sales tax identification number:
DE243555898
Chief executive officers:
Henning Koch
Thomas Eisenbarth