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!