In the past month or so I’ve been learning Ruby, this is my first blog post about the language and my experiences learning it. I’d love to hear your feedback.
Ruby allows you to work with modules and mixins, giving characteristics to classes and instances that otherwise could only happen through inheritance. One can argue that there are many similarities between Ruby’s mixins and PHP’s traits.
But first things first: what is a mixin? A mixin is a module that you include in a class, because you are “mixing in” the module methods with the instance methods of a class.
When learning about Ruby’s modules behavior, specifically the include
functionality, I couldn’t help but think how similar it was to PHP traits. It reminded me how a class can override an inherited method from a trait, or just plain inheritance of new features that class didn’t have before.
What I learned was the following:
- classes that
extend
a module have methods from it, however they are only accessible through a class callextend
doesn’t change the inheritance chain
- classes that
include
a module have methods from it, however they are only accessible through an instance callinclude
does change the inheritance chain
- and, finally;
prepend
. Which is a funny case in my opinion, a bit trickier to grasp the concept at first sight. But I got a bit more understanding after playing with the following. I think…
Prepend
I learned about the existence of prepend
after doing this small exercise. Before this I thought you either extend
a module or include
it.
Try out the following code:
What should we get when calling #color
?
car = Car.new car.color
The naive me, thought in the beginning the result would be:
# Red # => nil
And my reasoning was, except for monkey patching, isn’t Car
overriding everything because when instantiated we overload the method #color
? To my surprise, it printed (come on, go check it!):
# Blue # => nil
Apparently I was wrong. Playing with pry
I added super
(LOC4) to Vehicle#color
, convinced that the return would be Green
, since Car
inherits Vehicle
, right? Wrong again!
module Paintable | |
def color | |
puts "Blue" | |
super | |
end | |
end | |
class Vehicle | |
def color | |
puts "Green" | |
end | |
end | |
class Car < Vehicle | |
prepend Paintable | |
def color | |
puts "Red" | |
end | |
end | |
car = Car.new | |
car.color | |
# Blue | |
# Red | |
# => nil |
It looks like, Paintable
is overriding Car
, which is a bit confusing because the keyword is prepend
, for all I know (and the dictionary too, trust me, I checked) it means to attach something in the beginning of something else.
With that in mind, shouldn’t Paintable
come before Car
, and because I have overridden #color
in Car
(LOC17), shouldn’t Car
have the last say in what color should be?
Apparently, no. The correct way to read it is:
Paintable
is prepending the inheritance chain ofCar
And this is why:
# without the prepend, extend or include | |
Car.ancestors | |
# => [Car, Vehicle, Object, PP::ObjectMixin, Kernel, BasicObject] | |
# with the extend | |
Car.ancestors | |
# => [Car, Vehicle, Object, PP::ObjectMixin, Kernel, BasicObject] | |
# with the include | |
Car.ancestors | |
# => [Car, Paintable, Vehicle, Object, PP::ObjectMixin, Kernel, BasicObject] | |
# with the prepend | |
Car.ancestors | |
# => [Paintable, Car, Vehicle, Object, PP::ObjectMixin, Kernel, BasicObject] |
Which means that when calling #color
in the instance we get Blue
and why super
prints Red
, since Printable
now has Car
as a superclass.
Conclusion and Use Cases
Modules add functionality to your class, either by being a mixin
or through class methods when you extend
it.
I found this blog post with a good example of inheritance vs mixins:
Inheritance means that a class is a “type of something” and suggests specialisation. For example, a
Pikachu
object is a type ofPokemon
and so it makes sense to inherit from thePokemon
class.When a class should be capable of something, you should use a Mixin. For example,
DVD
,MP3
, andBluray
classes all have the#play
method, but just because they are all capable of the same action, does not mean they all should inherit from the same parent
Keeping the Car
example, the Paintable
module makes more sense to be used with an extend
, since you can change colors not only of cars, vehicles, multiple types of objects that won’t necessarily inherit Vehicle
.
Also, beyond including, and extending a module you can prepend it. prepend
changes the inheritance chain as does include
, but with inverse order. extend
does not change the inheritance chain.
You will be want to use prepend
whenever you want your code to be executed before the class. After working with so many legacy applications, I can see this being really useful when you want to extend the functionality of some class, let’s say Login
, you want to run before Login
a check to see if the requester address is accessing only through your closed network, prepend
would allow you to do that. And a good side effect of using prepend
is that you remove the necessity of directly monkey patch a method.
I think only experience will make clear the best way to implement a certain module in an application, but understanding how each of these tools behave is important before you can make that decision.