Reputation: 6574
Foo = Module.new
class MyClass
include Foo
end
When a module is included in a class, an anonymous proxy class is created and set as MyClass's superclass.
MyClass.ancestors => [MyClass, Foo, ...]
But what happens internally when a module is extended? How does Ruby handle this?
Upvotes: 2
Views: 353
Reputation: 3406
I think what you ask is Object#extend
So with extend
, I can include any module's methods into that object.
For example I have a module called HelperModule:
module HelperModule
def foo
puts "from module helper"
end
end
obj = Object.new
obj.extend HelperModule
obj.foo # => "from module helper"
class MyClass
extend HelperModule
end
MyClass.foo # => "from module helper"
Internally, according to Metaprogramming Ruby:
Object#extend() is simply a shortcut that includes a module in the receiver’s eigenclass.
A brief explanation of ruby's methods call :
obj
|
| class
| superclass superclass
---> ObjectClass --------------> SuperClass1 --------------> SuperClass2 ....
The detailed explanation about eigenclass and method call path, please reference this awesome book Metaprogramming Ruby
Thanks
Upvotes: 5
Reputation: 954
In a nutshell, Ruby's include
method will emulate inheritance: it will allow the including class access to the included module's instance methods, variables and constants as if they had been defined in the including class itself. Ruby does this by creating an anonymous proxy class (known as an eigenclass or singleton class), as you mentioned, which references the included module, and inserting it into the ancestors of the including class as the immediate superclass. This is what is known as a mixin in Ruby.
Using extend
, however, interacts with the singleton class a little bit more:
module Foo
def baz
'baz'
end
end
module Blah
def blah
'blah'
end
end
class Bar
extend Foo
include Blah
def self.magic
'magic'
end
end
Extending Foo
in Bar
is identical (right down to its implementation in C) to saying
Bar.singleton_class.send( :include, Foo )
This means that it is the singleton class of Bar
in which the methods and constants of Foo
are essentially embedded, and thus class Bar
, being an instance of its singleton class, will inherit this new functionality as so-called 'class' methods. In Ruby, modules and classes can only have instance methods, and thus the creation of a 'class' method will actually just create an instance method in the class's singleton class, to be inherited in this manner.
Bar.baz
# => 'baz'
Bar.magic
# => 'magic'
Bar.blah
# => NoMethodError
Bar.new.baz
# => NoMethodError
Bar.new.magic
# => NoMethodError
Bar.new.blah
# => 'blah'
Here you can see the differences in behavior of include
and extend
. To verify their behavior with regards to class ancestry, you can ask for the ancestors of Bar
and its singleton class, and it will show you that module Blah
is the immediate parent of Bar
, but module Foo
is the immediate parent of the singleton class.
Bar.ancestors
# => [Bar, Blah, Object, Kernel, BasicObject]
Bar.singleton_class.ancestors
# => [Foo, Class, Module, Object, Kernel, BasicObject]
From these results, you can see how inclusion emulates inheritance in the including class, and how extension is merely inclusion within the singleton class.
You might try looking at the answers I got for this question awhile back; they did an excellent job of explaining the behavior of the include
and extend
functionality.
This article has also helped me understand their differences.
Upvotes: 0
Reputation: 10258
obj.extend SomeModule
is the same as obj.eigenclass.include SomeModule
(Note: this is just pseudocode, but you will get the idea...).
Upvotes: 0
Reputation: 369428
Whats happens internally when a module in extended in Ruby?
When a module M
is included in a class C
, an anonymous proxy class ⟦M′⟧
(called an include class) is created such that its method table pointer points to M
's method table. (Same for the constant table and module variables.) ⟦M′⟧
's superclass is set to C
's superclass and C
's superclass is set to ⟦M′⟧
.
Also, if M
includes other modules, the process is applied recursively.
Actually, that's just the default behavior. What really happens is that include
calls M.append_features(C)
, and you can customize all of that behavior by overriding that method.
I find the source code of Module#append_features
in Rubinius quite readable.
Upvotes: 0