Read more

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

Felix Eschey
November 06, 2023Software engineer at makandra GmbH

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

Illustration online protection

Rails Long Term Support

Rails LTS provides security patches for old versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2)

  • Prevents you from data breaches and liability risks
  • Upgrade at your own pace
  • Works with modern Rubies
Read more Show archive.org snapshot

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
November 06, 2023Software engineer at makandra GmbH
Posted by Felix Eschey to makandra dev (2023-11-06 12:19)