Ruby
Article

Ruby’s Important Hook Methods

By Imran Latif

Screenshot 2015-04-26 10.59.14

Ruby’s philosophy is based on a strong primitive which is programmer happiness. Ruby strongly believes in programmer happiness and it has provided many different ways to make it so. Its metaprogramming capabilities allow programmers to write dynamic code at runtime. It’s threading abilities give programmers an elegant way to write multithreaded code. It’s hook methods help programmers extend behavior of programs at runtime.

The aforementioned features, along with some other cool language aspects, make Ruby one of the preferred choices for writing code. This post will explore some important hook methods in Ruby. We will discuss different aspects about hook methods, such as what they are, what are they used for, and how we can use them to solve different problems. We will also look at how popular Ruby frameworks/gems/libraries use them to provide pretty cool features.

Let’s get started.

What is a Hook Method?

Hook methods provide a way to extend behavior of programs at runtime. Imagine having the ability to get notified whenever a child class inherits from some particular parent class or handling non-callable methods on objects elegantly without allowing the compiler to raise exceptions. These are some of the use cases for hook methods, but their usage is not limited to this. Different frameworks/libraries have used different hook methods to achieve their desired functionality.

We will be discussing following hook methods in this post:

  • included
  • extended
  • prepended
  • inherited
  • method_missing

included

Ruby provides us a way to write modular code using modules (called mixins in other languages) which can be later used in other modules/classes. The idea behind module is quite simple; it’s a standalone piece of code that can be used in other places.

For example, if we want to write some code which returns a static string whenever a particular method is called. Let’s call that method name. You might want to use this same piece of code in other spots, as well. Creating a module makes perfect sense here. Let’s create one:

module Person
  def name
    puts "My name is Person"
  end
end

This is quite a simple module with only one method name returning a static string. Let’s use this in our program:

class User
  include Person
end

Ruby provides some different ways to use modules. One of them is include. What include does is it makes methods defined in the underlying module available on instances of a class. In our case, methods defined in the Person module become available as instance methods on objects of the User class. It is as if we have written the name method in the User class itself, but the advantage of defining it in a module is reusability. To call name we need to create an instance of User and then call name on newly created object. For example:

User.new.name 
=> My name is Person

Let’s look at the hook method based on include. included is a hook method provided by Ruby which gets called whenever you include a module in some module or class. Update the Person module with following code:

module Person
  def self.included(base)
    puts "#{base} included #{self}"
  end

  def name
    "My name is Person"
  end
end

You can see a new method included defined as a class method on the Person module. This included method will be called whenever you includePerson in other modules or classes. This methods receives an argument which is a reference to the class including the module. Try running User.new.name and you will see the following output:

User included Person
My name is Person

As you can see, base is returning the including class name. Now that we have a reference to the class including Person, we can do some metaprogramming to achieve our desired functionality. Let’s see how Devise uses the included hook.

included in Devise

Devise is one of the most widely used authentication gems in Ruby. It is primarily written by my favorite programmer, José Valim, and now maintained by some awesome contributors. Devise gives us complete functionality from registration to login, from forgot password to recover password, etc. It lets us configure various modules using a simple syntax in a user model:

devise :database_authenticatable, :registerable, :validatable

The devise method that we use in our models is defined here. I have pasted the code below for your convenience:

def devise(*modules)
  options = modules.extract_options!.dup

  selected_modules = modules.map(&:to_sym).uniq.sort_by do |s|
    Devise::ALL.index(s) || -1  # follow Devise::ALL order
  end

  devise_modules_hook! do
    include Devise::Models::Authenticatable

    selected_modules.each do |m|
      mod = Devise::Models.const_get(m.to_s.classify)

      if mod.const_defined?("ClassMethods")
        class_mod = mod.const_get("ClassMethods")
        extend class_mod

        if class_mod.respond_to?(:available_configs)
          available_configs = class_mod.available_configs
          available_configs.each do |config|
            next unless options.key?(config)
            send(:"#{config}=", options.delete(config))
          end
        end
      end

      include mod
    end

    self.devise_modules |= selected_modules
    options.each { |key, value| send(:"#{key}=", value) }
  end
end

The named modules passed to the devise method in our model will be passed to *modules as an array. extract_options! is called on passed modules to extract any options has that user might have passed. On line 11 there is an each and every module is represented as m in code block. At line 12 m is converted to a constant (class name), so a symbol like :validatable becomes Validatable using m.to.classify. classify is an ActiveSupport method, by the way. Devise::Models.const_get(m.to_classify) gets a reference to the module and assigns it to mod. On line 27, the module reference included using include mod. In the Validatable example, the module which is defined here is included. Validatable has the following included hook method at:

def self.included(base)
  base.extend ClassMethods
  assert_validations_api!(base)

  base.class_eval do
    validates_presence_of   :email, if: :email_required?
    validates_uniqueness_of :email, allow_blank: true, if: :email_changed?
    validates_format_of     :email, with: email_regexp, allow_blank: true, if: :email_changed?

    validates_presence_of     :password, if: :password_required?
    validates_confirmation_of :password, if: :password_required?
    validates_length_of       :password, within: password_length, allow_blank: true
  end
end

The model, in this case, is base. At line 5, there is a class_eval block which evaluates in the context of calling class. Writing code through class_eval is the just like writing the same code by opening the class file and pasting in the code. Devise is using class_eval to include validations on our model.

We see validations when we try to register or login using Devise, but we didn’t write those validations. Devise provides them by taking advantage of the included hook. Pretty neat.

extended

Ruby also allows developers to extend a module, which is a bit different then include. Instead of applying methods defined in module to instances of a class, extend applies methods to the class itself. Let’s see a quick example:

module Person
  def name
    "My name is Person"
  end
end

class User
  extend Person
end

puts User.name # => My name is Person

As you can see, we have called the name method defined in the Person module as a class method on User. extend added the methods of the Person module to the User class. extend can also be used to apply methods defined in a module as singleton methods on objects. Let’s see another quick example:

# We are using same Person module and User class from previous example.     

u1 = User.new
u2 = User.new

u1.extend Person

puts u1.name # => My name is Person
puts u2.name # => undefined method `name' for #<User:0x007fb8aaa2ab38> (NoMethodError)

We created two instances of User and then called extend on u1, passing Person as an argument. Because of this invocation, the name method defined in Person module is only available on u1 and is not available on other instances.

Just like included, there is an associated extended hook for extend. It will be called when a module is extended by another module or class. Let’s see an example:

# Modified version of Person module

module Person
  def self.extended(base)
    puts "#{base} extended #{self}"
  end

  def name
    "My name is Person"
  end
end

class User
  extend Person
end

Running this code results in User extended Person as output.

With the introduction to extended complete, let’s see how ActiveRecord is using it.

extended in ActiveRecord

ActiveRecord is the a widely used ORM for Ruby and Rails. It has many cool features, which makes it the preferred ORM in most cases. Let’s dive into ActiveRecord internals to see how ActiveRecord gets callbacks (we are using Rails v3.2.21).

ActiveRecord is extending ActiveRecord::Models here.

extend ActiveModel::Callbacks

ActiveModel provides a known set of interfaces for usage in model classes. They allow for ActionPack helpers to interact with non-ActiveRecord models. In ActiveModel::Callbacks, defined here, you will see the following code:

def self.extended(base)
  base.class_eval do
    include ActiveSupport::Callbacks
  end
end

ActiveModel::Callbacks is calling class_eval on base, which is ActiveRecord::Callbacks, and including a module ActiveSupport::Callbacks. As we discussed previously, calling class_eval on a class reference is the same as writing that code manually in that particular class. It is ActiveSupport::Callbacks which is providing ActiveRecord::Callbacks with Rails’ (in)famous callbacks.

We have discussed extend, it’s associated hook extended and also have seen how ActiveRecord/ActiveModel is using aforementioned method/hook to provide us with some ready-to-use functionality.

prepended

There is another way to use methods defined in modules called prepend. prepend was introduced in Ruby 2.0 and it is quite different from include and extend. Methods used by include and extend can be overridden by methods defined in the target module/class. For example, if we have defined a method name in some module and the same method defined in the target module/class, then the method defined in our class will override name from the module. prepend is quite different as it overrides methods defined in our module/class with methods defined in the prepending module. Let’s see a quick example:

module Person
  def name
    "My name belongs to Person"
  end
end

class User
  def name
    "My name belongs to User"
  end
end

puts User.new.name 
=> My name belongs to User

Now let’s see prepend in action:

module Person
  def name
    "My name belongs to Person"
  end
end

class User
  prepend Person
  def name
    "My name belongs to User"
  end
end

puts User.new.name 
=> My name belongs to Person

Adding prepend Person overrides methods with same names defined in User, which results in My name belongs to Person in the console. prepend actually prepends methods to the method chain. To call the name method defined in User class call super from within name in the Personmodule.

prepend has an associated callback named (you guessed it) prepended, which is called when a module is prepended to another module/class. Let’s see that in action. Update the definition of Person module with following code:

module Person
  def self.prepended(base)
    puts "#{self} prepended to #{base}"
  end

  def name
    "My name belongs to Person"
  end
end

As soon as you run this code, you’ll see the following:

Person prepended to User
My name belongs to Person

prepend was introduced to get rid of the ugly alias_method_chain hack which has been used by Rails (and others) quite extensively to achieve the exact same functionality as prepend. Since prepend is available in Ruby >= 2.0, you should update your Ruby version if you are planning to use prepend.

inherited

Inheritance is one the most important concepts in object-oriented programming. Ruby is an object-oriented language and provides the ability to inherit a child class from some base/parent class. Let’s see a quick example:

class Person
  def name
     "My name is Person"
  end
end

class User < Person
end

puts User.new.name # => My name is Person

We created a Person class and a child User class. The methods defined in Person become part of User . That is pretty straightforward inheritance. You might be wondering, is there some way to get notified when a class inherits from another class? Yup, there is a Ruby hook called inherited available for this exact thing. Let’s see that in action:

class Person
  def self.inherited(child_class)
    puts "#{child_class} inherits #{self}"
  end

  def name
    "My name is Person"
  end
end

class User < Person
end

puts User.new.name

As you can see, the inherited class method, be called whenever Person class is inherited by some child class. Running the above code snippet shows:

User inherits Person
My name is Person

Let’s see how Rails is using inherited in its codebase.

inherited in Rails

There is an important class in every Rails app called Application defined in the config/application.rb file. That class performs many different tasks, such as executing all Railties, engines, and plugin initializers. An interesting thing about the Application class is that there cannot be two instances of it running in the same process. If we try to override this behavior, Rails will throw an exception. Let’s see how Rails has implemented this feature.

The Application class inherits from Rails::Application, which is defined here. On line 62, there is an inherited hook defined which will be called when our Rails app Application class gets inherited from Rails::Application. The code of inherited hook is:

class << self
  def inherited(base)
    raise "You cannot have more than one Rails::Application" if Rails.application
    super
    Rails.application = base.instance
    Rails.application.add_lib_to_load_path!
    ActiveSupport.run_load_hooks(:before_configuration, base.instance)
  end
end

class < < self is another way of defining class methods in Ruby. In inherited, the first line checks if Rails.application exists, raising an exception if it does. The first time this code runs, Rails.application will return false and super is called. super in this case is the inherited hook from Rails::Engine, because Rails::Application itself inherits from Rails::Engine.

On next line, you can see Rails.application is assigned base.instance. The remainder of the method setups the Rails app.

This is how Rails is intelligently using the inherited hook to make sure that only one instance of our Rails Application class is running per process.

method_missing

method_missing is probably the most widely used Ruby hook. It can be found in many popular Ruby frameworks/gems/libraries. It is called when our code tries to call a non-existent method on an object. Let’s see a quick example:

class Person
  def name
    "My name is Person"
  end
end

p = Person.new

puts p.name     # => My name is Person  
puts p.address  # => undefined method `address' for #<Person:0x007fb730a2b450> (NoMethodError)

We have declared a simple Person class with only one method, name. Then, create an instance of Person and call two methods, name and address, respectively. Since name is defined on Person, it’ll run smoothly. However address is not defined on Person and will raise an exception as. The method_missing hook can avoid these kinds of exceptions, capturing those undefined methods gracefully. Let’s write a new version of Person class:

class Person
  def method_missing(sym, *args)
     "#{sym} not defined on #{self}"
  end

  def name
    "My name is Person"
  end
end

p = Person.new

puts p.name     # => My name is Person
puts p.address  # => address not defined on #<Person:0x007fb2bb022fe0>

method_missing receives two arguments: the name of the method called and the arguments passed to that method. First, Ruby will look for the method that we are trying to call, if the method is not found it will look for method_missing. Now we have overridden method_missing on Person, so Ruby will call that and will not raise any exception.

Let’s see how the Rake gem is using method_missing in the real world.

method_missing in Rake

Rake is one of the most widely used Ruby gems. Rake uses method_missing to provide access to the arguments passed to a Rake task. Create a simple rake task first:

task :hello do
  puts "Hello"
end

If you run this rake task by calling rake hello, you’ll see Hello. Let’s extend this rake task so that it accepts an argument (the name of a person) and greet that person:

task :hello, :name do |t, args|
  puts "Hello #{args.name}"
end

t is name of the of task and args holds the arguments passed to that task. As you can see, we have called args.name to get the name argument passed to the hello task. Run the rake task, passing it a name argument like so:

rake hello["Imran Latif"] 
=> Hello Imran Latif

Let’s see how Rake is using method_missing to provide us with arguments passed to the task.

The args object in the above task is an instance of Rake::TaskArguments which is defined here. This class is responsible for managing the arguments passed to a Rake task. Viewing the code of Rake::TaskArguments, you’ll see there are no methods defined which correspond to arguments passed to task. So, how does Rake provide the arguments passed to the task? The answer to this question is Rake is using method_missing intelligently to achieve desired functionality. If you look at line 64, method_missing is defined:

def method_missing(sym, *args)
  lookup(sym.to_sym)
end

Having method_missing in the class definition ensures undefined methods will get routed to it and no exception will be raised by Ruby. In this method_missing, there is a call to a lookup method:

def lookup(name)
  if @hash.has_key?(name)
   @hash[name]
  elsif @parent
    @parent.lookup(name)
  end
end

method_missing is calling lookup, passing it the name of the method in the form of a Symbol. The lookup method will look into @hash which is created in the constructor of Rake::TaskArguments. If @hash contains the argument it will be returned, if it is not present in @hash then Rake will call lookup on the @parent if it exists. If the argument is not found, then nothing is returned.

This is how Rake intelligently provides access to arguments passed to a Rake task using method_missing. Thanks Jim Weirich for writing Rake.

Closing Remarks

We discussed five important Ruby hook methods, explored how they work, and how popular frameworks/gems are using those to provide some neat functionality. I hope you enjoyed this article. Please let us know in comments about your favorite Ruby hook and what you solved using that Ruby hook.

Comments
pmatsinopoulos

Nice Article. Good Work!

ilatif

Thanks for liking thumbsup .

Muaaz_Rafi

Indeed a very well versed article, on the mysteries of hook methods in ruby. Learned allot from the article and will be looking to next article by author.

ilatif

Thanks for liking my article. Stay tuned for upcoming articles.

rizalmuthi

Good job.. really well explained.

ilatif

Thanks for liking smile. Stay tuned for next articles.

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in Ruby, once a week, for free.