Using extend in Ruby

In Ruby, it is possible to enhance the functionality of an existing object by including a module into a class using a mixin. It is commonplace to see code like this:

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

  def calculate_interval
    (Date.today - @elapsed_created).to_i
  end

  module ClassMethods
    def interval; 3; end
  end
end

class MyClass
  include DateMethods

  attr_accessor :started
  def initialize(date:)
    @started = Date.parse(date)
  end
end

The example above is trivialized but hopefully helps to illustrate my observations. We have a module called ‘DateMethods’ which we are including into ‘MyClass’.

Within the ‘DateMethods’ module, the ‘self.included’ callback allows us to call ‘extend’ on the singleton object of ‘MyClass’ in order to extend the ‘ClassMethods’ module to create class methods.

While this approach works and it is something I have been doing myself, I can’t help but feel that this is not as syntactically pleasing as calling ‘MyClass.extend(DateMethods)’ which reads better.

We are also overriding ‘include’ to create an ‘extend’ when Ruby allows us to extend a module directly.

Rewriting the example above but using ‘extend’ instead:

module DateMethods
  def self.extended(base)
    base.include InstanceMethods
  end

  # class methods stay in the module body
  def interval; 3; end

  module InstanceMethods
    def calculate_interval
      (Date.today - @elapsed_created).to_i
    end
  end
end

class MyClass
  attr_accessor :started
  def initialize(date:)
    @started = Date.parse(date)
  end
end

MyClass.extend(DateMethods)
myclass = MyClass.new(date: "2015-05-28")
puts myclass.calculate_interval # => 1
puts MyClass.interval # => 3

Ruby provides a callback ‘extended’ which gets invoked whenever the receiver is used to extend an object. Within the ‘extended’ callback, we then ‘include’ the instance methods.

This is more flexible than ‘include’ itself as it can’t be called on instances directly. In addition, ‘include’ affects the entire class itself which may not be the intention if all you need is to have certain behavior at certain times. By using ‘extend’ on instances, we can achieve a kind of dynamic code loading:

module Pagination
  def paginate(page = 1, items_per_page = size, total_items = size)
    @page = 1
    @items_per_page = items_per_page
    @total_items = total_items
  end

  attr_reader :page, :items_per_page, :total_items

  def total_pages
    (total_items / items_per_page).ceil
  end

end

collection = %w[first second third]

collection.extend(Pagination)
collection.paginate(1, 3, 10)

In the above example adapted from a James Earl Gray article on mixins, only the ‘collection’ object has the ‘Pagination’ module methods since the ‘extend’ is on the singleton/metaclass of ‘collection’ object itself, which restricts the scope of the ‘Pagination’ behavior to be contained and not affecting the entire class. We could just have easily swapped out ‘Pagination’ with another module at runtime.

In summary, I feel this is a more flexible approach to writing more maintable, dynamic code.

Happy hacking!