Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby mixins: extend and include

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?

like image 542
Daniel Brady Avatar asked Jul 09 '13 16:07

Daniel Brady


People also ask

What is difference between include and extend in Ruby?

In simple words, the difference between include and extend is that 'include' is for adding methods only to an instance of a class and 'extend' is for adding methods to the class but not to its instance.

What is include in Ruby?

include? is a String class method in Ruby which is used to return true if the given string contains the given string or character.

How do you use mixins in Ruby?

Mixins in Ruby allows modules to access instance methods of another one using include method. Mixins provides a controlled way of adding functionality to classes. The code in the mixin starts to interact with code in the class. In Ruby, a code wrapped up in a module is called mixins that a class can include or extend.

What does prepend do in Ruby?

In Ruby, we can use the prepend() method to prepend one string or multiple strings to another string.


1 Answers

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.

like image 178
Patrick Oscity Avatar answered Oct 13 '22 11:10

Patrick Oscity