Last post I explained the difference between extend and include. But more digging around and low and behold, extend and include are pretty much the same thing, in different scopes. Okay so bare with me here.
Every object in ruby has two sets of methods, instance and class methods. Instance methods are passed down to any new instance. What ruby really does in the background every time you do Klass.new it creates a 2 objects. One being your instance and another being a metaclass or a singleton_class. The reason behind this is because you can define methods on instances on the fly. I.e.
class Foo end foo = Foo.new foo.hello #=> no method error def foo.hello 'hello!' end foo.hello #=> "hello!" bar = Foo.new bar.hello #=> no method error
Without a metaclass in between the instance and the class we would have to open up Foo again and define the instance method. But this will affect all instances of Food not just foo. This is why the metaclass exists between the instance and the class. When we define a method on an instance we’re actually opening the instance’s metaclass and defining a instance method in it.
I lost you there.
Foo.new #=> instance of Foo + metaclass (hidden)
Right now inheritance is now Foo => instance_metaclass => foo (instance)
When we do def foo.hi we’re actually placing an instance method on instance_metaclass and not Foo.
Not to get weirder but a class also has a metaclass. So inheritance again:
otherstuff => otherstuff_metaclass => Object => Object_metaclass => Class => Class_metaclass => Foo
Because we do class Foo end, what we’re really doing is Foo = Class.new and as we have already discussed. Whenever ruby makes a new object, it actually makes 2 objects, the obj + a metaclass.
Okay let’s go over from the end. Say we have:
module B def hi 'hi' end end
And we extend it in class A. A.hi will work. But notice if we did:
class A extend B def self.hi super end end
A.hi will still return ‘hi’. super implies that it is calling the method on an inherited class. But if we do A.ancestors, B does not appear on the list. Weird? nope because if we look at it’s meta class, it’s there.
meta = class << A self end meta.ancestors #=> [A, B, ....]
Now to prove back to the point that extend really does include but in a different scope we’re going to extend A with module C and you’ll see C is in the list of A’s ancestors.
So really extend is include, just in a different scope.