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 extend
ed 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 extend
ing 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 prepend
ing 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 Person
module.
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 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.