Space Vatican

Ramblings of a curious coder

Modules Aren't Just for Instance Methods

Modules are great for sharing bits of code across models, controllers etc.. The very basic case, where you just want to share some instance methods is dead easy: just write the methods and include the module. The module gets inserted in your class’ list of ancestors and so ruby will look inside it when it needs to find a method. If you’re using rails you might also want to share validations on models, filters on controllers and so on. But first something a little simpler.

Using a module to add class methods is a bit more fiddly than sharing instance methods. Of course you can just include the module inside class << self, but often we want to add both class and instance methods. Requires two modules and two includes when one cannot exist without the other is a bit messy and repetitive(not to mention error-prone). Luckily, whenever a module is included, a callback is called passing what did the including as an argument. Instead on relying on the programmer to do that second include we can do it ourselves from this callback. This is a fairly standard pattern:

1
2
3
4
5
6
7
8
9
10
11
12
13
module MyModule
  module ClassMethods
    def a_class_method
    end
  end

  def an_instance_method
  end

  def self.included(base)
    base.extend ClassMethods
  end
end

Our included function is dead simple, we just want to add our module of class methods. We need to use extend here because include would add them as instance methods, we want to add them as singleton methods on the class, i.e. class methods. If you include this module then you’ll gain the methods in MyModule::ClassMethods as class methods. Naming the module ClassMethods is just for ease of reading, you could call it anything you want.

We could be doing a lot more though. It’s important to understand that things like has_many, validates_format_of and so on are just methods. They don’t always look like it (which is of course one of the things that makes ruby great for building DSLs), but it’s definitely there. Laying to one side issues of protected or private methods,

1
2
3
class Person < ActiveRecord::Base
  validates_presence_of :email
end

is the same as

1
2
3
class Person < ActiveRecord::Base
  self.validates_presence_of :email
end

or

1
2
class Person < ActiveRecord::Base; end
Person.validates_presence_of :email

This is of course true of all those other things (named_scope, before_filter, verify etc…) that you frequently see in models or controllers: just methods you can call on classes. What a happy coincidence that the included callback gives you a class! This means that we can package up sets of validtions or associations or anything like that into a module. For example, we could have an Auditable module. Things that are auditable have a polymorphic audit_trails association that tracks whenever the object was modified.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module Auditable
  def update_with_audit(*args)
    returning update_without_audit(*args) do |rows_changed|
      if rows_changed > 0
        audit_trails.create(...)
      end
    end
  end

  def self.included(base)
     base.has_many :audit_trails, :as => :auditable
     base.alias_method_chain :update, :audit
   end
end

In our included method we use has_many to create the association and we use alias_method_chain to hook the update method (which is used whenever you update a row). Sometimes it’s nice to use instance_eval on base to make things look a little neater, for example

1
2
3
4
5
6
7
8
module FooBarish
  def self.included(base)
    base.instance_eval do
      validates_presence_of :foo
      validates_presence_of :bar
    end
  end
end

Whenever you include this module the 2 validations will be defined.

You could play similar tricks with sets of filters that you want certain controllers to have and all sorts of things. That said, don’t go overboard, sometimes regular old subclassing is exactly the right thing, but happily modules will keep you going even when it’s not.