Rethinking code reuse with Modularity for Ruby

by on March 26, 2010 · 2 comments

This is a guest post by our friends over at makandra, a cool Ruby on Rails development shop. Today they announce a great new Ruby gem for dealing with separating concerns in your ActiveRecord models.

Reusing code is hard. But although we knew that high-level components don’t work, we found ourselves rewriting similar code again and again for different projects. Was there maybe another angle from which to slice our code into reusable pieces?

We sat down and looked at what was truly worth sharing. Here are some of the many things we found:

  • Flag: A boolean attribute must be either true or false (not nil). The attribute has a default value. Also there is a named scope Model.flag which finds records for which the flag is true.

  • Searchable model: The records of a model can be searched, matching the query against the concatenation of some text attributes.

  • Booking sum: A numeric field is the sum of many bookings. A booking adds or substracts an amount from the sum. There are also absolute correction bookings, which overshadow all previous bookings.

These are all behaviors of individual classes, or as we call them, traits. They are different from components in that they do not provide the glue between classes or application layers. Traits are isolated building blocks. You bring the glue.

We field tested the trait pattern in some of our projects and it deeply transformed the way we work and reuse code. We extracted it all into a gem, Modularity,

Using and defining traits

Let’s look at the Flag trait in the list above. We would like to use it like we would use has_many or validates_presence_of, as a macro. For this Modularity defines a new keyword does, which includes a trait in a class:

class User < ActiveRecord::Base
  does 'flag', :admin, :default => false
end

Implementing the flag trait with vanilla Ruby would be awkward. Modules are a bad fit here because we want to define stuff dependent on the flag’s name (include takes no arguments). Also our trait will have to call some ActiveRecord macros for the including class in order to define the validation and the scope. This is not how modules work in Ruby.

This is how traits are defined in Modularity:

module FlagTrait
  as_trait do |name, options|
    validates_inclusion_of name, :in => [true, false]
    has_defaults name => options[:default]
    named_scope name, :conditions => { name => true }        
  end
end

Think of the as_trait block as a partial that renders itself into whatever class that does 'flag'. It defines the validation, sets the defaults (using the delicious has_defaults plugin and then defines a named scope, all based on the flag’s name.

Modularity has changed the way we do Ruby at makandra. No longer are macros something that only your framework can provide for your classes. Defined as traits, macros become a basic building block of your own application.

You can find more examples and use cases for traits on Modularity’s github page.

Did you enjoy this article? Get new articles for free by email:

Comments

Trackbacks