There are three major means of importing code into a Ruby class or module:
Module#extend. As covered in an earlier post,
#prepend make methods available by tricking out an object’s ancestor chain, giving Ruby new instructions on how, where, and in what order to look for a classes’ methods.
Module#extend does something very different. First, let’s look at what happens when we use
#extend to import a module’s methods. Then we’ll pop open the hood and see if we can figure out just how that’s happening.
What Does Module#extend do?
The simple answer:
Module#extend makes a module’s methods available as class methods:
RobotMethods are included into the
Extender class, that module’s method is made accessible at the class level. Those methods, however, are not made accessible as instance methods, as they would be if we’d used
But how? In order to understand what’s happening here, we’ll have to take a broader look at Ruby method dispatch, classes, and the idea of class methods.
Ruby Method Dispatch
The first question we should ask is “Where do methods live?”
If you say “on the class” you’d only be half right. Ruby classes define instance methods, accessible to instances of that class.
Consider an instance of the class
Here’s how an instance of this object is actually represented in memory.
some_employee doesn’t know that it has a method called work. All it knows is that it is a member of the class
Employee. When you call a method on
some_employee, Ruby follows that *class* reference and then looks for a matching method where they’re defined.
Makes sense, right? That way, instances of each class don’t need to carry copies of every instance method that they answer to. It’s a good way to save memory in the system.
So that’s all well and good for instance methods, but what about class methods?
Class Methods — A Terrible Secret
In your travels, you may have noticed that there’s a
Module#instance_methods but there’s no
Module#class_methods. There’s a good reason for that, but you might not be ready to hear about it.
Take a deep breath. Are you sitting down? Good. There is no such thing as a Ruby class method, only instance methods that look and smell like class methods.
When we call that class method
validate_id!, Ruby will follow the same flow that we did before:
We’ll check the
Employee class for an instance method. When we don’t find it, we’ll follow that pointer reference up to the
Class class and look for it There.
Employee is, after all, an instance of the class
But wait. If we find
validate_id! on the
Class class, then that method should be available on every class, not just
Employee. What in the blazes is going on?
Ruby Singleton Classes
You’ve probably seen a trick like this before:
We call these instance-specific method singleton methods. These methods live on an object called a singleton class, which actually lives invisibly between the object itself and its formal class. Need proof? Check this out:
Makes sense, right? If the method definition was on the
Employee class, then it wouldn’t be instance-specific.
So then what is a Class Method?
A class method is a method that lives on a classes’ singleton class.
That statement deserves some unpacking, no?
When we make a call to
Employee.validate_id!, we’re invoking the
Employee class, which is actually an instance of Ruby’s
Class class. When that
Employee class is created, a singleton class is created on-the-fly that contains the method
Check out this example for proof:
So to summarize: class methods aren’t methods that are define at the class level of an object. They’re methods that are defined on the implicitly-created singleton class that’s built at the moment that the class is created in the system.
Finally: How does Module#extend work?
When you invoke
Module#extend to import methods, you’re not bringing them into the instance. You’re not bringing them into the class. You’re actually bringing them into the singleton class that lives between that object’s class and Ruby’s
Class class, which lives above it.
This isn’t vital need-to-know information for Ruby developers. It’s not even need-to-know for most advanced Rubyists. But it’s really useful to know if you’re using
#extend extensively in your system and you ever need to debug funky method behavior.
And for nerds like me, it’s just cool to see how this stuff works behind the scenes.
My understanding of class method dispatch came largely from this brilliant article by Leo Hetsch. If you want to go deeper, I’d call that required reading.