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'
endThis 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.rbEach 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
endOrganizing 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.
You can follow any response to this post through the Atom feed.



This seems pretty similar to dm is remixable? Does this work outside the scope of rails?
dm-is-remixable could be expressed in a Modularity trait.
You can use Modularity in any Ruby application, Rails is not required.
What is the difference between doing this and using the standard “include” method Ruby provides? Is it just syntatic sugar for the sake of it or do I get a free rainbow with it?
@Ryan: Your free rainbow is that modularity traits can be parametrized, providing different behavior depending on which arguments they are included with. This is not possible with Ruby’s include.
Check out the link at the end of the article for an example.