Hook methods is actually the convenient way to help us extend the behaviors of existing class at runtime. In rails there are many popular hook methods we often use such as “included, extended, inherited”. We build many modules (mixins in vue.js for example) and allow to include them to classes.
Hook methods implemented by meta-programming
As we might know about meta-programming in ruby. We usually use class_eval or instance_eval to extend behaviors for classes. But we don’t want to use them directly. We develop many methods to do that instead. We make many rooms for developer to put code later. They are the specific method names that we call hook methods. Because it’s like a hook, we just need to put code in there without using class_eval or instance_eval.
But don’t get stuck in those methods. We can also create our own hook methods. This is an example code of cancancan gem and lazy load hooks in rails (ActiveSupport). on_load is a hook method. We use authorize! methods of cancancan every time need to verify the permissions in rails controller. Have you ever wondered why rails controllers have authorize! method which belongs to cancancan?
# cancancan gem: controller_additions.rb
ActiveSupport.on_load(:action_controller) do
include CanCan::ControllerAdditions
end
The gem use the on_load hook method to include CanCan::ControllerAdditions at runtime. The miracle happens! We extended the behaviors of rails controllers.
Below is the implementation of on_load hook method.
# ruby ActiveSupport: lazy_load_hooks.rb
def on_load(name, options = {}, &block)
@loaded[name].each do |base|
execute_hook(name, base, options, block)
end
@load_hooks[name] << [block, options]
end
def execute_hook(name, base, options, block)
with_execution_control(name, block, options[:run_once]) do
if options[:yield]
block.call(base)
else
base.instance_eval(&block)
end
end
end
Above code we can see that there are two options:
if options[:yield]
block.call(base)
else
base.instance_eval(&block)
end
That is about switching the position of the context.
- option yield: we put the base object to block to use.
- otherwise, we use meta-programming, put the block to the context of base instance.
Hook methods implemented by override
There is another way to implement hook methods. That is override. We can see that through template method pattern.
class Engine
# This is template method
def prepare
supply_fuel
start
control_speed
end
private
# hook methods, this is optional implementation, not like abstract concept.
def supply_fuel; end
def start; end
def control_speed; end
end
We have prepare method is the template method and supply_fuel is Non-abstract method which can be re-implemented in concrete classes or not. We call it hook method. Anyway, hook method is like a hook which allow us to inject new code to existing method. We have two ways to use it:
- Using override: loading code based on the required order.
- Using meta-programming: loading code at runtime, in order of processes.
Now we know more one way to apply Open/Closed principle as well. However, there are a bit different with Inheritance. With inheritance we expand the behaviors of superclass by using subclass. But with hook methods, we target using superclass.