Ruby mixins: extend and include
extend, include, inheritance, ruby
Solution
The difference is that `include` will add the included class to the ancestors of the including class, whereas `extend` will add the extended class to the ancestors of the extending classes' singleton class. Phew. Let's first observe what happens:
Bacon.ancestors
#=> [Bacon, Object, Kernel, BasicObject]
Bacon.singleton_class.ancestors
#=> [Bar, Baz, Class, Module, Object, Kernel, BasicObject]
Bacon.new.singleton_class.ancestors
#=> [Bacon, Object, Kernel, BasicObject]
Bacon.is_a? Bar
#=> true
Bacon.new.is_a? Bar
#=> false
And for the `Egg` class
Egg.ancestors
#=> [Egg, Bar, Baz, Object, Kernel, BasicObject]
Egg.singleton_class.ancestors
#=> [Class, Module, Object, Kernel, BasicObject]
Egg.new.singleton_class.ancestors
#=> [Egg, Bar, Baz, Object, Kernel, BasicObject]
Egg.is_a? Bar
#=> false
Egg.new.is_a? Bar
#=> true
So what `foo.is_a? Klass` actually does is to check whether `foo.singleton_class.ancestors` contains `Klass`. The other thing happening is that all the ancestors of a class become ancestors of an instances' singleton class when the instance is created. So this will evaluate to true for all newly created instances of any class:
Egg.ancestors == Egg.new.singleton_class.ancestors
So what does all this mean? `extend` and `include` do the same thing on different levels, i hope the following example makes this clear as both ways to extend a class are essentially equivalent:
module A
def foobar
puts 'foobar'
end
end
class B
extend A
end
class C
class << self
include A
end
end
B.singleton_class.ancestors == C.singleton_class.ancestors
#=> true
where `class << self` is just the odd syntax to get to the singleton class. So `extend` really just is a shorthand for `include` in the singleton class.
Problem
I've been reading a few articles about Ruby's mixin methods, `extend` and `include`, and I am still not quite sure about the behavior. I understand that `extend` will add the instance methods of the given module as singleton methods to the module doing the extending, and that `include` will essentially append the contents of a module (methods, constants, variables) to the one doing the including, effectively defining them in the receiver. However, after some tinkering, trying to get a feel for how the behavior will manifest, I've got a few questions. Here is my testing setup: ``` module Baz def blorg puts 'blorg' end end module Bar include Baz def blah puts 'blah' end end module Foo extend Bar end class Bacon extend Bar end class Egg include Bar end ``` So as I would expect, module `Bar` gains the instance methods defined in `Baz` (`#blorg`) as if they'd been defined in itself due to the inclusion method, and class `Bacon` gains the singleton methods `Bacon::blah` and `Bacon::blorg` by extension. ``` Bacon.blah # => blah Bacon.blorg # => blorg ``` And class `Egg` gains the methods defined in `Bar` (`#blah` and now `#blorg`) as instance methods. ``` Egg.new.blah # => blah Egg.new.blorg # => blorg ``` I get all that, so that's good. However, I don't understand the responses I get from using the `#ancestors` and `#is_a?` methods. ``` Bacon.ancestors # => [Bacon, Object, Kernel, BasicObject] Bacon.is_a? Bar # => true Egg.ancestors # => [Egg, Bar, Baz, Object, Kernel, BasicObject] Egg.is_a? Bar # => false ``` It would seem that extending a module causes the `#is_a?` method to return `true` when queried about that module, but it is not added to the ancestors of the class, and vice versa with regards to inclusion: the ancestors of the class contains the modules being included, but the `#is_a?` method returns `false` when queried. Why does this happen?