Monday, October 6, 2008

One thing to keep in mind when extending Ruby classes

I was reading this post in Mauricio Fernandez's blog (rcov) and I got surprised about the behavior I'm summarizing in the following code:
class A
def foo
"A#foo"
end

def singleton_class
class << self
self
end
end
end

a = A.new
puts a.foo #=> A#foo
puts a.singleton_class.ancestors.inspect #=> [A, Object, Kernel]

module X
def foo
"X#foo"
end
end

class A
include X
end

a.extend X
puts a.foo #=> A#foo (and not X#foo!!!!!!!)
puts a.singleton_class.ancestors.inspect #=> [A, X, Object, Kernel] (and not [X, A, X, Object, Kernel])

So remember, you cannot extend or include a class with a module that is already present in the ancestors chain, it will be ignored!

2 comments:

Anonymous said...

> puts a.foo #=> A#foo (and not X#foo!!!!!!!)

Calling .ancestors should have given you a hint but to quote Programming Ruby "mixed-in modules effectively behave as superclasses". If you changed your 'foo' method definition to:

def foo
super rescue "A#foo"
end

You'll see X#foo as you expect after mixing in X.

This also may help explain why Ruby ignores your a.extend X since X is already mixed in and behaving as a superclass.

Daniel Cadenas said...

That's right.

My mistake comes from implicitely thinking that include or extend where not much than this:

ancestor_chain.add(module)

So it seems they are not as dumb and it's not possible to do weird things like [X, X, X, Object]. They don't just follow orders.