Ruby Hooks: Extending Class Functionality with the included Hook
Introduction
In the world of Ruby programming, we often rely heavily on Rails due to its abundant support and convenient features. Rails concerns are commonly used in models or controllers to abstract and provide the necessary functionality. However, when working with pure Ruby code, we may find ourselves in situations where we need to extend class functionality without the aid of Rails. This is where Ruby hooks, particularly the included hook, come into play.
The Problem: Adding Class Methods in a Module
Recently, while working on a Ruby project, I encountered a scenario where I needed to write class methods within a module. Initially, I attempted to accomplish this using the self keyword 🤦🏻, as shown in the following code snippet:
Unfortunately, this approach did not work as expected. It resulted in an error stating that the echo_class_name method was undefined for the User class.
The Solution: Leveraging the included Hook
Fortunately, I soon discovered the power of Ruby hooks, specifically the included hook. Ruby hooks allow us to execute code when specific events occur. In our case, the included hook enables us to extend class functionality when a module is included in a class.
To utilize the included hook, we define a method with the same name within the module that we want to include. This method will be called automatically when the module is included in a class, giving us the opportunity to modify the class's behaviour in any desired way.
Example Implementation
Let's see an example that demonstrates the usage of the included hook:
In this example, we define the LearnIncluded module, which adds functionality to classes that include it. The included hook within the module is triggered when the module is included in a class. In our case, it extends the class with a submodule called MyClassMethods. This submodule contains a class-level method called echo_class_name, which echoes the class name.
We then create an example class called User that includes the LearnIncluded module. By doing so, the class inherits both the instance method instance_method and the class-level method echo_class_name.
Finally, we demonstrate the usage of both the instance method and the class-level method. We create an instance of the User class, invoke the instance_method, and observe the output "I am an instance method." Subsequently, we call the class-level method echo_class_name on the User class, which produces the output "I am called from User."
Additional Example: Utilizing the included Hook
To further illustrate the usage of the included hook, let's consider a scenario where we want to track the number of instances created for each class that includes a specific module.
Recommended by LinkedIn
In this example, we have defined a module called InstanceCounter that tracks the number of instances created for each class that includes it.
In this method, we extend the class with the ClassMethods submodule and include the InstanceMethods submodule. Additionally, we initialize the instance counter for the class.
The ClassMethods submodule defines an instance_count attribute accessor and a method called initialize_counter that sets the instance_count to 0.
The InstanceMethods submodule overrides the initialize method to increment the instance_count every time a new instance is created.
We then create two example classes, Person and Animal, that include the InstanceCounter module. This automatically triggers the included hook, extending the classes with the required functionality and initializing their instance counters.
Finally, we create instances of the Person and Animal classes and output the instance count for each class. The output will display the number of instances created for each class.
All available Ruby Hooks:
Method-related hooks
method_added, method_missing, method_removed, method_undefined, singleton_method_added, singleton_method_removed, singleton_method_undefined
Class and module-related hooks
append_features, const_missing, extend_object, extended, included, inherited, initialize_clone, initial- ize_copy, initialize_dup
Object marshaling hooks
marshal_dump, marshal_load
Coercion hooks
coerce, induced_from, to_xxx
References: