Ruby: `extend` extends the singleton class's inheritance chain

Posted 6 months ago. Visible to the public.

In the discussion of the difference between include and extend in Ruby, there is a misconception that extend would add methods to the singleton class of a ruby object as stated in many posts on this topic. But in fact, it is added to the ancestors chain of the singleton class! Even though it is technically not the same, practically this can be considered the same in most use cases.

Example

This means, that we are able to overwrite these methods or call the parent version with super depending in which order and in which way they were added to singleton class.

Consider the following code:

module A
  def foo
    puts "Foo!!"
  end
end

module B
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def foo
      puts "Foo!"
      super
    end
  end
end

class User
  singleton_class.include(A)
  include B
end

User.instance_eval do
  def foo
    puts 'Foo'
    super
  end
end

User.foo # => "Foo Foo! Foo!!"
User.singleton_class.ancestors # => [#<Class:User>, B::ClassMethods, A, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
User.singleton_class.class # => Class
User.singleton_class.ancestors[1].class # => Module

Note that #<Class:User> is the singleton class and as we have seen the parent versions of foo have only been added to the ancestor chain of the singleton class and not to the singleton class itself.

Why does extend include class methods in this example?

The reason why the singleton class defines it's methods as class methods (and not as instance methods) is, because now it is a Module of the singleton class of the actual User class object and not on an instance of User. You can also use this to extend the singleton class of an user to add instance methods.

singleton_methods(all)

This is also the reason why #singleton_methods Show archive.org snapshot has a boolean all parameter to decide whether methods on the singleton inheritance chain should be included.

For example, if module A defined method #bar and Module B defines method #baz:

User.singleton_methods # => [:foo, :bar, :baz] 
User.singleton_methods(false) # => [:foo]

For the technical details have a look at this great card on the Ruby object model.

Felix Eschey
Last edit
6 months ago
Henning Koch
Keywords
metaclass, eigenclass
License
Source code in this card is licensed under the MIT License.
Posted by Felix Eschey to makandra dev (2023-11-06 11:19)