It is known that in Ruby, class methods get inherited:
class P def self.mm; puts 'abc' end end class Q < P; end Q.mm # works
However, it comes as a surprise to me that it does not work with mixins:
module M def self.mm; puts 'mixin' end end class N; include M end M.mm # works N.mm # does not work!
I know that #extend method can do this:
module X; def mm; puts 'extender' end end Y = Class.new.extend X X.mm # works
But I am writing a mixin (or, rather, would like to write) containing both instance methods and class methods:
module Common def self.class_method; puts "class method here" end def instance_method; puts "instance method here" end end
Now what I would like to do is this:
class A; include Common # custom part for A end class B; include Common # custom part for B end
I want A, B inherit both instance and class methods from Common
module. But, of course, that does not work. So, isn't there a secret way of making this inheritance work from a single module?
It seems inelegant to me to split this into two different modules, one to include, the other to extend. Another possible solution would be to use a class Common
instead of a module. But this is just a workaround. (What if there are two sets of common functionalities Common1
and Common2
and we really need to have mixins?) Is there any deep reason why class method inheritance does not work from mixins?
The Ruby class Class inherits from Module and adds things like instantiation, properties, etc – all things you would normally think a class would have. Because Module is literally an ancestor of Class , this means Modules can be treated like classes in some ways. As mentioned, you can find Module in the array Class.
Mixins are sometimes described as being "included" rather than "inherited". In short, the key difference from an inheritance is that mix-ins does NOT need to have a "is-a" relationship like in inheritance. From the implementation point of view, you can think it as an interface with implementations.
Mixins in Ruby allows modules to access instance methods of another one using include method. Mixins provides a controlled way of adding functionality to classes. The code in the mixin starts to interact with code in the class. In Ruby, a code wrapped up in a module is called mixins that a class can include or extend.
As with class methods, you call a module method by preceding its name with the module's name and a period, and you reference a constant using the module name and two colons.
A common idiom is to use included
hook and inject class methods from there.
module Foo def self.included base base.send :include, InstanceMethods base.extend ClassMethods end module InstanceMethods def bar1 'bar1' end end module ClassMethods def bar2 'bar2' end end end class Test include Foo end Test.new.bar1 # => "bar1" Test.bar2 # => "bar2"
Here is the full story, explaining the necessary metaprogramming concepts needed to understand why module inclusion works the way it does in Ruby.
Including a module into a class adds the module to the ancestors of the class. You can look at the ancestors of any class or module by calling its ancestors
method:
module M def foo; "foo"; end end class C include M def bar; "bar"; end end C.ancestors #=> [C, M, Object, Kernel, BasicObject] # ^ look, it's right here!
When you call a method on an instance of C
, Ruby will look at every item of this ancestor list in order to find an instance method with the provided name. Since we included M
into C
, M
is now an ancestor of C
, so when we call foo
on an instance of C
, Ruby will find that method in M
:
C.new.foo #=> "foo"
Note that the inclusion does not copy any instance or class methods to the class – it merely adds a "note" to the class that it should also look for instance methods in the included module.
Because inclusion only changes the way instance methods are dispatched, including a module into a class only makes its instance methods available on that class. The "class" methods and other declarations in the module are not automatically copied to the class:
module M def instance_method "foo" end def self.class_method "bar" end end class C include M end M.class_method #=> "bar" C.new.instance_method #=> "foo" C.class_method #=> NoMethodError: undefined method `class_method' for C:Class
In Ruby, classes and modules are plain objects – they are instances of the class Class
and Module
. This means that you can dynamically create new classes, assign them to variables, etc.:
klass = Class.new do def foo "foo" end end #=> #<Class:0x2b613d0> klass.new.foo #=> "foo"
Also in Ruby, you have the possibility of defining so-called singleton methods on objects. These methods get added as new instance methods to the special, hidden singleton class of the object:
obj = Object.new # define singleton method def obj.foo "foo" end # here is our singleton method, on the singleton class of `obj`: obj.singleton_class.instance_methods(false) #=> [:foo]
But aren't classes and modules just plain objects as well? In fact they are! Does that mean that they can have singleton methods too? Yes, it does! And this is how class methods are born:
class Abc end # define singleton method def Abc.foo "foo" end Abc.singleton_class.instance_methods(false) #=> [:foo]
Or, the more common way of defining a class method is to use self
within the class definition block, which refers to the class object being created:
class Abc def self.foo "foo" end end Abc.singleton_class.instance_methods(false) #=> [:foo]
As we just established, class methods are really just instance methods on the singleton class of the class object. Does this mean that we can just include a module into the singleton class to add a bunch of class methods? Yes, it does!
module M def new_instance_method; "hi"; end module ClassMethods def new_class_method; "hello"; end end end class HostKlass include M self.singleton_class.include M::ClassMethods end HostKlass.new_class_method #=> "hello"
This self.singleton_class.include M::ClassMethods
line does not look very nice, so Ruby added Object#extend
, which does the same – i.e. includes a module into the singleton class of the object:
class HostKlass include M extend M::ClassMethods end HostKlass.singleton_class.included_modules #=> [M::ClassMethods, Kernel] # ^ there it is!
extend
call into the moduleThis previous example is not well-structured code, for two reasons:
include
and extend
in the HostClass
definition to get our module included properly. This can get very cumbersome if you have to include lots of similar modules.HostClass
directly references M::ClassMethods
, which is an implementation detail of the module M
that HostClass
should not need to know or care about.So how about this: when we call include
on the first line, we somehow notify the module that it has been included, and also give it our class object, so that it can call extend
itself. This way, it's the module's job to add the class methods if it wants to.
This is exactly what the special self.included
method is for. Ruby automatically calls this method whenever the module is included into another class (or module), and passes in the host class object as the first argument:
module M def new_instance_method; "hi"; end def self.included(base) # `base` is `HostClass` in our case base.extend ClassMethods end module ClassMethods def new_class_method; "hello"; end end end class HostKlass include M def self.existing_class_method; "cool"; end end HostKlass.singleton_class.included_modules #=> [M::ClassMethods, Kernel] # ^ still there!
Of course, adding class methods is not the only thing we can do in self.included
. We have the class object, so we can call any other (class) method on it:
def self.included(base) # `base` is `HostClass` in our case base.existing_class_method #=> "cool" end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With