Ruby’s Important Hook Methods

Share this article

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.

Key Takeaways

  • Hook methods in Ruby provide dynamic ways to extend and modify the behavior of programs at runtime, enhancing flexibility in code management.
  • The `included` and `extended` hook methods allow for modular code enhancements in Ruby, with `included` triggering when a module is included in another module or class, and `extended` when a module extends another.
  • The `prepended` hook method offers a unique approach by allowing methods in a prepending module to take precedence over those in the target class or module, which is useful for overriding existing methods without altering the original module.
  • The `inherited` hook method is beneficial for actions that need to be triggered when a class inherits from another, such as logging or setting up configurations automatically.
  • The `method_missing` hook method can be used to elegantly handle calls to undefined methods, providing a way to capture and manage potential errors in a controlled manner.

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.

Frequently Asked Questions (FAQs) about Ruby’s Important Hook Methods

What are Ruby’s Hook Methods?

Ruby’s Hook Methods are special methods that get triggered when certain events occur in the object lifecycle. They are a part of Ruby’s metaprogramming toolkit and are used to add or alter the behavior of an object or class. Some of the important hook methods in Ruby include included, extended, prepended, method_added, method_removed, and method_undefined.

How do Ruby’s Hook Methods work?

Ruby’s Hook Methods work by getting triggered automatically when certain events occur. For instance, the included hook method gets triggered when a module is included in a class or another module. Similarly, the method_added hook method gets triggered when a new method is defined in a class or module. These hook methods can be used to perform specific actions whenever these events occur.

What is the purpose of Ruby’s Hook Methods?

The main purpose of Ruby’s Hook Methods is to provide a way to add or alter the behavior of an object or class during its lifecycle. They allow developers to write code that responds to certain events, such as the inclusion of a module or the addition of a new method. This can be useful for a variety of purposes, such as logging, debugging, or modifying the behavior of a class or object.

How can I use Ruby’s Hook Methods in my code?

To use Ruby’s Hook Methods in your code, you need to define these methods in your class or module. For instance, if you want to perform a specific action whenever a new method is added, you can define the method_added hook method in your class or module. This method will then be automatically called whenever a new method is defined.

Can I override Ruby’s Hook Methods?

Yes, you can override Ruby’s Hook Methods in your classes or modules. However, you should be careful when doing so, as overriding these methods can change the behavior of your objects or classes in unexpected ways. It’s generally recommended to call super within your overridden method to ensure that the original behavior is preserved.

What is the difference between included and extended Hook Methods in Ruby?

The included and extended hook methods in Ruby are triggered by different events. The included method is called when a module is included in a class or another module, while the extended method is called when a module is extended by an object or class. Both of these methods can be used to perform specific actions when these events occur.

How can I debug using Ruby’s Hook Methods?

Ruby’s Hook Methods can be used for debugging by providing a way to track and log events that occur during the lifecycle of an object or class. For instance, you can use the method_added hook method to log a message every time a new method is defined. This can help you understand the flow of your code and identify potential issues.

Can I use Ruby’s Hook Methods with inheritance?

Yes, Ruby’s Hook Methods can be used with inheritance. When a class inherits from another class, the hook methods defined in the parent class will also be triggered in the child class. This can be useful for propagating behavior from a parent class to its subclasses.

Are there any potential pitfalls when using Ruby’s Hook Methods?

While Ruby’s Hook Methods are powerful tools, they should be used with caution. Overusing or misusing these methods can lead to code that is difficult to understand and maintain. It’s also important to remember that hook methods can change the behavior of your objects or classes in unexpected ways, so they should be used judiciously.

Can I use Ruby’s Hook Methods to modify the behavior of built-in classes?

Yes, you can use Ruby’s Hook Methods to modify the behavior of built-in classes. However, this should be done with caution, as it can lead to unexpected behavior and potential conflicts with other parts of your code. It’s generally recommended to avoid modifying built-in classes unless absolutely necessary.

Imran LatifImran Latif
View Author

Imran Latif is a Web Developer and Ruby on Rails and JavaScript enthusiast from Pakistan. He is a passionate programmer and always keeps on learning new tools and technologies. During his free time he watches tech videos and reads tech articles to increase his knowledge.

GlennG
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week