The Inherited Hook Method in Ruby - and More Parenting Lessons

farsi_mehdi

Mehdi FARSI

Posted on September 3, 2019

The Inherited Hook Method in Ruby - and More Parenting Lessons

Hello children and parents, ahem Rubyists. In an earlier article, we dove into the ancestry chain. In today's post, we'll dive deeper into parenting and inheritance. We will explore the inherited hook method and look into preventing inheritance.

When Kids Inherit 101: The Inherited Hook Method

Let's start with a look at how parenthood is declared. Ruby provides a neat way of interacting with a class when it is declared as the parent of another class.

class Parent
  def self.inherited(subclass)
    puts "#{subclass} inherits from Parent"
  end
end

class Child < Parent
end

Running this code prints out "Child inherits from Parent", as the Parent.inherited method is called when the Child class inherits from Parent. Note that this method takes the subclass as parameter—Child, in our case. This mechanism allows you to interact with the Parent class to define a set of behaviors only if it is inherited. By behavior, in this context, we mean modifying, defining or deleting variables, methods and constants on the inheriting or the inherited class.

Now, let's define the parent_name method on-the-fly:

class Parent
  def self.inherited(subclass)
    subclass.define_method :parent_name do
      "Daddy"
    end
  end
end

class Child < Parent
end

Child.new.parent_name # => "Daddy"

Here, we define a method on the Child class when it inherits from the Parent class, but without adding that method directly to the Parent class. Instead, it's only defined when another class inherits from Parent.

Alright, we've covered the theory, now let’s take a look at a more realistic example in the life of a Rubyist.

Prevent Class Inheritance

In Ruby on Rails, database migrations are handled by the ActiveRecord::Migration class. Let’s try to directly inherit from this class.

class AddFirstnameToUsers < ActiveRecord::Migration
end

# => StandardError (Directly inheriting from ActiveRecord::Migration is not supported..)

This error is raised because Ruby on Rails provides a mechanism that prevents us from inheriting from this class. So why did Ruby on Rails implement this mechanism?

A migration is strongly coupled to a specific version of Ruby on Rails. Indeed, the API provided by this class can slightly change between 2 versions. So, to avoid breaking migrations when you upgrade Ruby on Rails, the framework forces you to choose a specific version of the ActiveRecord::Migration class. This ensures the smooth running of your migrations.

class AddFirstnameToUsers < ActiveRecord::Migration[4.2]
end

In the example above, our migration is coupled to the ActiveRecord::Migration API provided with version 4.2 of Ruby on Rails. So, even if we upgrade our application to version 5.0, our migrations will still run smoothly because they'll still run with version 4.2 of the ActiveRecord::Migration API.

How Preventing Inheritance Works

Now that we understand why inheritance is prevented here, let’s see how this ActiveRecord::Migration prevents inheritance. All the logic is defined in the ActiveRecord::Migration.inherited method.

class AddFirstnameToUsers < ActiveRecord::Migration[4.2]
end

When our AddFirstnameToUsers class inherits from ActiveRecord::Migration, the ActiveRecord::Migration.inherited hook method is called.

module ActiveRecord
  class Migration
    def self.inherited(subclass) #:nodoc:
      super
      if subclass.superclass == Migration
        raise StandardError, "Directly inheriting from ActiveRecord::Migration is not supported. " \
          "Please specify the Rails release the migration was written for:\n" \
          "\n" \
          "  class #{subclass} < ActiveRecord::Migration[4.2]"
      end
    end
  end
end

As we can see, this hook method checks if the subclass (AddFirstnameToUsers) directly inherits from ActiveRecord::Migration. If it does, an error is raised. This is the perfect entry point for controlling inheritance.

Conclusion

Today, we covered the basics of inheritance and preventing inheritance. We covered the inherited hook method and saw how it can be very handy when interacting with the inheriting/inherited class on-the-fly.

In a real-world setting, watch out when playing with inheritance. Be very cautious when you remove or override an existing method or class. This could result in some unwanted side effects. Children may stop calling you daddy.

Et Voilà, this concludes our post for today!

💖 💪 🙅 🚩
farsi_mehdi
Mehdi FARSI

Posted on September 3, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related