Intend to extend (metaprogramming in Ruby)

In this post I want to illustrate the different ways of how to extend Ruby classes and instances. As being a former Java developer it is always impressive, but also sometimes a bit confusing how many ways are available in Ruby to achieve that. There is already a lot written about this topic (see resources below), but as it is such an essential Ruby topic it is always nice to see things from different views.

Inheritance

One of the most common ways to add further functionality to an already existing class is to inherit from it and add the new stuff to the inherited class. Ruby isn’t here much different than any other object oriented language.

class Lifeform
  def eat
    puts "Yam"
  end
end
 
class Dog < Lifeform
  def eat
    puts "Yummi " + super
  end
 
  def bark
    puts "Wuff"
  end
end
 
Lifeform.new.eat # Yam
Lifeform.new.bark # NoMethodError
Dog.new.eat # Yummi Yam
Dog.new.bark # Wuff

Merge methods in existing classes

But in Ruby you can also add new methods to a existing classes by just “reopening” the class and adding the definitions. In the below case Time is already defined in the Ruby standard library.

class Time
  def jump
    "Two thousand years later!"
  end
end
 
Time.now.jump # Two thousand years later!

Redefine methods of existing class

This approach also allows to redefine already existing methods. The hour method of the Time class is already defined in the Ruby standard library.

class Time
  def hour
    puts "Too late my friend."
  end
end
 
Time.now.hour # Too late my friend.

The downside by doing it this way is to loose the possibility to access the former functionality (no, the super method does not help here). If you still need to access the former defined method then you can use alias_method.

class Time
  alias_method :old_hour, :hour
 
  def self.now
    "Your hour, my lord: #{old_hour.to_s}.
  end
end
 
Time.now.hour # Your hour, my lord: 9.

For class methods you have to use alias_methos in the following way.

class Time
  class << self
    alias_method :old_now, :now
  end
 
  def self.now
    "The time my lord: #{old_now.to_s}.
  end
end
 
Time.now # The time my lord: Fri Mar 11 01:15:23 +0100 2011.

For a nice explanation of what class << self is all about just read this nice answer at Stackoverflow.

Define a method just for one instance

You can also add a method to just a single instance. All other instances of the class won’t have the method then.

class Lifeform
end
 
dog = Lifeform.new
 
def dog.bite
  "grrr"
end
 
cat = Lifeform.new
 
dog.bite # grrr
cat.bite # NoMethodError

An alternative way to define the same method is by using the above mentioned class << X syntax.

class << dog
  def bite
    "grrr"
  end
end
 
dog.bite # grrr

Include a module

In Ruby you can mix modules into classes (in contrast to inheritance as many as you like). You can inherit and add modules at the same time (I guess that why it is also called mixins). When you include a module you make the module methods available to the instances of a class.

module Lifeform
  def eat
    puts "Yam"
  end
 
  def self.sleep
    puts "Zzz"
  end
end
 
class Worm
  include Lifeform
end
 
Worm.new.eat # Yam
Lifeform.sleep # Zzz
Worm.sleep # NoMethodError
Lifeform.eat # NoMethodError
Lifeform.new # NoMethodError

Note that in the above example sleep is defined as a module function and so could be used directly (without mixing the module into a class or instance).

Extend a class with a module

When you extend a class by using a module you make the methods of the module available to the class itself.

module Lifeform
  def eat
    puts "Yam"
  end
end
 
class Worm
  extend Lifeform
end
 
Worm.eat # Yam
Worm.new.eat # NoMethodError

You can also extend the class outside of the class definition:

Worm.extend(Lifeform)

Extend an instance with a module

You can also extend an instance with a module. The module methods are then only available to this single instance.

module Lifeform
  def eat
    puts "Yam"
  end
end
 
class Worm
end
 
worm1 = Worm.new
worm1.extend(Lifeform)
worm1.eat # Yam
 
worm2 = Worm.new
worm2.eat # NoMethodError

Note that including a module into an instance is as far as I know not possible. It would not make sense as an instance can’t be instantiated again (only classes can be instantiated), and so include would not have any effect.

Include and extend the Rails plugin way

In Ruby On Rails there is a paradigm that is used by a lot of plugins (especially those acts as something ones). It is rather complicated done in the Rails Guide. Yehuda mentions better solutions on his blog. Here is a way I personally prefer.

module ActsAsCounter
  # class method
  def acts_as_lifeform
    include InstanceMethods
  end
 
  module InstanceMethods
    def count
      counter++
      save
    end
  end
end
 
ActiveRecord::Base.extend ActsAsCounter

The above code adds the acts_as_lifeform method to Active::Record::Base as a class method. When this method now is called in one of the Rails model classes (that inherit from ActiveRecord::Base) then the count method is added to all instances of the model class. If you want to do something like that then I recommend to take also a closer look at ActiveSupport::Concern.

Extend at runtime with class_eval on class

In Ruby you can also dynamically extend classes and instances at runtime. class_eval evaluates the provided code block in the context of a class and thereby allows to attach methods to the class and all instances.

class Worm
end
 
Worm.class_eval do
  def eat
    puts "Yam"
  end
 
  def self.sleep
    puts "Zzz"
  end
end
 
Worm.new.eat # Yam
Worm.sleep # Zzz
 
Worm.eat # NoMethodError
Worm.new.sleep # NoMethodError

Extend at runtime with instance_eval

In contrast instance_eval evaluates the provided code block in the context of an instance.

class Worm
end
 
charlie = Worm.new
charlie.instance_eval do
  def eat
    puts "Yam"
  end
end
 
charlie.eat # Yam
Worm.eat # NoMethodError

But you can also use it on a class. In that case the class is treated as an instance and the methods are directly attached (as class methods).

class Worm
end
 
Worm.instance_eval do
  def eat
    puts "Yam"
  end
end
 
Worm.eat # Yam
Worm.new.eat # NoMethodError

A short memory crutch for class_eval and instance_eval could be (from Brian Morearty’s blog): * Use ClassName.instance_eval to define class methods. * Use ClassName.class_eval to define instance methods.

Benefit of extending at runtime

If you still wonder what the benefit of this is, then look at this code snippet from the ActsAsTaggable.

class_eval %(
  def #{tag_type}_list
    tag_list_on('#{tags_type}')
  end
 
  def #{tag_type}_list=(new_tags)
    set_tag_list_on('#{tags_type}', new_tags)
  end
 
  def all_#{tags_type}_list
    all_tags_list_on('#{tags_type}')
  end
)

I don’t want to to go into details what exactly happens here. But as you see class_eval can also get a simple string. It is fully evaluated as Ruby code and can extend the class runtime (the string could be generated at runtime). That way there are nearly no limits at all.

The method_missing method

There is another way to define new methods without defining them … nope, I didn’t drink too much yet … just a bit red wine :-) When you call a non existent method it normally raises a NoMethodError. You can change that behavior in the predefined method_missing method and even put some logic or call other methods from there. I normally try to avoid this as it makes the code harder to read, but it can give some really convenient accessibilities (something like the find_by_something methods in Rails).

class Worm
  def method_missing(m, *args, &block)
    if (m.to_s == "eat")
      puts "Yam"
    else
      super
    end
  end
 
  def self.method_missing(m, *args, &block)
    if (m.to_s == "sleep")
      puts "Zzz"
    else
      super
    end
  end
end
 
Worm.new.eat # Yam
Worm.slepp # Zzz
 
Worm.new.sleep # NoMethodError
Worm.eat # NoMethodError

Resources