I noticed something odd while I was adding methods to Kernel
to make them available globally. It's interesting, and I'm looking for some documentation or good explanation.
Let's look at the code:
file: ./demo.rb
# example 1
module Kernel
def foo
puts "I'm defined inside the module!"
end
end
# example 2
module Bar
def bar
puts "I'm included! (bar)"
end
end
Kernel.send :include, Bar
# example 3
module Baz
def baz
puts "I'm included! (baz)"
end
end
module Kernel
include Baz
end
Then, in bash and IRB
$ irb -r ./demo.rb
> foo
# I'm defined inside the module!
> bar
# NameError: undefined local variable or method `bar' for main:Object
> baz
# NameError: undefined local variable or method `baz' for main:Object
>
> self.class.ancestors
# => [Object, Kernel, BasicObject]
>
> include Kernel
>
> self.class.ancestors
# => [Object, Kernel, Baz, Bar, BasicObject]
>
> foo
# I'm defined inside the module!
> bar
# I'm included! (bar)
> baz
# I'm included! (baz)
foo
works as expected, and is available for all objects that include Kernel
. bar
and baz
, on the other hand, are not immediately available.
I imagine it's because the evaluation context of IRB (an Object
) already includes Kernel
, and including a module A inside a module B will not "reload" all previous inclusions of B.
Ok, it makes perfect sense, and in fact re-including Kernel
will add the other two methods.
Then, my questions are:
Kernel
work? (example 1)What happens when you call foo.bar
in Ruby? Something like this:
foo.class.ancestors.each do |klass|
if klass.public_instance_methods.include? :bar
return klass.instance_method(:bar).bind(foo).call
end
end
raise NameError
i.e. Ruby searches through the ancestors to find a matching instance method.
And what happens when you call A.include B
in Ruby? Something like this:
B.ancestors.each do |mod|
A.ancestors << mod unless A.ancestors.include? mod
end
B
and all of its ancestors become ancestors of A
. These two behaviors explain everything:
Kernel
works because it's included in Object
and is thus an ancestor of every object, meaning its methods (including new ones) can be searched whenever you call a method on any object.Kernel
's ancestors are only searched when it is included, which was before you added new ancestors to it.