Reputation: 1880
I come from a Python background, and I was trying to understand how Ruby includes compare to Python's multiple inheritance. Specifically, I was setting up two modules with the same method to see how super
work.
I included Mod
, then Mod2
, then Mod
again. However, A
still seems to be referencing Mod2
.
module Mod
def foo(x)
x**2
end
end
module Mod2
def foo(x)
x*2
end
end
class A
include Mod
def foo(x)
super(x) + 1
end
end
A.new.foo(5) == 26 # true
class A
include Mod2
def foo(x)
super(x) + 1
end
end
A.new.foo(5) == 11 # true
class A
include Mod
def foo(x)
super(x) + 1
end
end
A.new.foo(5) == 26 # false. Is 11
Why isn't the third A.new.foo(5)
return 26
?
Upvotes: 0
Views: 354
Reputation: 2575
Ruby finds super
by searching the class/module's ancestry chain one class/module at a time until it finds the definition of the method it's looking for. Sort of like a bus with many stops, looking for the right place to get off, but it always gets off at the first opportunity. If it gets all the way up to BasicObject
and doesn't find it's stop it will raise a NoMethodError
.
When you include a module, you're adding it to the ancestry chain just above the including class/module. You can find the ancestors, in order, by calling Module.ancestors
.You can include the same module many times, but it will only be added to the ancestry chain once. Consider this:
module Mod
def foo(x)
x**2
end
end
module Mod2
def foo(x)
x*2
end
end
class A
include Mod
def foo(x)
super(x) + 1
end
end
A.ancestors # => [A, Mod, Object, Kernel, BasicObject]
class A
include Mod2
def foo(x)
super(x) + 1
end
end
A.ancestors # => [A, Mod2, Mod, Object, Kernel, BasicObject]
class A
include Mod
def foo(x)
super(x) + 1
end
end
A.ancestors # => [A, Mod2, Mod, Object, Kernel, BasicObject]
Mod
was already in the ancestry chain the second time it was included, so nothing new happened. It stays put, and super
still hits Mod2
first. In short, you can't "override" newer modules by including older modules that were already there. There are other things you can do though. You could define a Mod3
, or maybe ModOverride
, with the same definition of foo
as was in Mod
and include that, or look into refinements Ruby 2.0 How do I uninclude a module out from a module after including it?
As other answers noted, you don't need to redefine A#foo
every time if the definition is staying the same.
Upvotes: 0
Reputation: 38
I think what you may be missing is that the second class A ... end
is not redefining the class, it's adding to it. You’re allowed to "open" a class multiple times in ruby and (re-)define whatever you want. That, and what you seem to have already figured out, which is that include
more or less just drops the code from the module into that spot in the class definition, unless that module has already been included in which case it does nothing.
You can do this with any class at all (whether advisable or not)
class Hash
def to_a
'lol you probably wanted an array here'
end
end
{foo: :bar}.to_a # Returns above string instead of [:foo, :bar]
Upvotes: 1
Reputation: 1880
Defining class A(object)
in PythonCreates an object with a type
of type
and __name__
of A
, then assigns that object to the label A
. A second class A(object)
definition creates a whole new type
object that is then put into label A.
Classes in ruby aren't stored in labels, and they can be opened to add more functionality. A good example of this is 2.days
. Rails defines days
on integer. Without Rails, 2.days
fails.
The 3 class definitions above are roughly equivalent to:
class A
include Mod
def foo(x)
super(x) + 1
end
include Mod2
def foo(x)
super(x) + 1
end
include Mod
def foo(x)
super(x) + 1
end
end
When ruby sees the second include Mod
, it knows Mod
has already been included on the class and skips it, leaving foo
from Mod2
as the most recent definition.
If you remove foo
from later definitions, A
still has access to it:
class A
include Mod
def foo(x)
super(x) + 1
end
end
A.new.foo(5) == 26 # true
class A
include Mod2
end
A.new.foo(5) == 11 # true
class A
include Mod
end
A.new.foo(5) == 11
Upvotes: 1