Ruby Hooks: Extending Class Functionality with the included Hook
Included Hook

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:

# Define a module named LearnIncluded module LearnIncluded   # Define a class method called echo_class_name which outputs the class name of the calling object   def self.echo_class_name     puts "I am called from #{self.class}"   end end  # Define a class called User that includes the LearnIncluded module class User   include LearnIncluded end  # Call the class method echo_class_name on the User class User.echo_class_name  # Output: included_hook.rb:11:in `<main>': undefined method `echo_class_name' for User:Class (NoMethodError)
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:

# LearnIncluded module adds functionality to classes that include it module LearnIncluded   # This hook is triggered when the module is included in a class   def self.included(klass)     klass.extend MyClassMethods   end    # Instance method added by LearnIncluded module   def instance_method     puts 'I am an instance method'   end    # Submodule containing class-level methods   module MyClassMethods     # Class-level method to echo the class name     def echo_class_name       puts "I am called from #{self}"     end   end end  # Example class that includes the LearnIncluded module class User   include LearnIncluded end  # Create an instance of the User class and call the instance method user = User.new user.instance_method  # Call the class-level method User.echo_class_name

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.

# Module that tracks the number of instances created for each class that includes it module InstanceCounter   def self.included(klass)     klass.extend ClassMethods     klass.include InstanceMethods     klass.initialize_counter   end    module ClassMethods     attr_accessor :instance_count      def initialize_counter       @instance_count = 0     end   end    module InstanceMethods     def initialize(*)       super       self.class.instance_count += 1     end   end end  # Example classes that include the InstanceCounter module class Person   include InstanceCounter end  class Animal   include InstanceCounter end  # Creating instances of the Person class person1 = Person.new person2 = Person.new  # Creating instances of the Animal class animal1 = Animal.new  # Output the instance count for each class puts "Person instances: #{Person.instance_count}" puts "Animal instances: #{Animal.instance_count}"

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:

  • https://www.geeksforgeeks.org/ruby-hook-methods/
  • https://www.codingninjas.com/codestudio/library/hooks-in-ruby
  • Programming Ruby 3.2 Book

To view or add a comment, sign in

More articles by Atish Maske

  • How Apache Lucene Makes Searching Super Fast

    Today, I want to talk about something really cool: how Apache Lucene stores and retrieves data so efficiently. We’re…

    1 Comment
  • How to Use Multiple GitHub Accounts

    If you have multiple GitHub accounts, it can be a challenge to manage them on the same computer. Fortunately, with a…

    10 Comments
  • Design Patterns - Strategy Pattern

    Introduction to Design Patterns In software engineering, a design pattern is a reusable solution to a common problem in…

  • Introduction to Pattern Matching in Ruby 3.0

    Ruby 3.0 introduces a new feature called pattern matching, which allows you to match values against patterns and…

    5 Comments

Others also viewed

Explore content categories