Blaix Blog

Ruby Selfie

2022-04-09

I don't do much ruby these days, but I always loved the object model and nature of self in the language. The more I learned about it, the more cool and fun things I could do. And then there's that special feeling when the infamous extend self and class << self stopped feeling like magic incantations, and just made sense in the context of ruby's object model. It started with the understanding that everything in ruby is an object which is an instance of a class. Even classes themselves are objects that are instances of the class Class.

class Dog
end

puts Dog.class # => Class

When you use the class keyword, you are "opening" a class to define instance methods for any instance of the class:

class Dog
  def bark
    puts "bark!"
  end
end

dog1 = Dog.new
dog2 = Dog.new

dog1.bark # => "bark!"
dog2.bark # => "bark!"

But class instance methods are really class methods on the class's singleton class.

Every object in ruby has an "invisible" singleton class (a metaclass):

puts Dog.singleton_class     # => #<Class:Dog>
puts Dog.new.singleton_class # => #<Class:#<Dog:0x0123...>>
puts Dog.new.singleton_class # => #<Class:#<Dog:0x0456...>>

When you say def <some object>.<some method>, you are defining an instance method on that object's singleton class:

def dog1.bark
  puts "redefined bark!"
end

When you call a method in ruby, it first checks the object's singleton class:

dog1.bark # => redefined bark!
dog2.bark # => bark!

Since classes are objects, the same applies:

def Dog.eat
  puts "yum!"
end

We defined an instance method on the class object's singleton class (wee), and method lookup works the same way. There's nothing special about "class" methods in ruby!

Dog.eat # => "yum!"

Also, there's always an implicit self object:

puts self # => main

and when you open a class, self changes to the class object:

class Dog
  puts self # => Dog
end

So when you define "class methods" in ruby like this:

class Dog
  def self.eat_again
    puts "yummy!"
  end
end

Dog.eat_again # => yummy!

You're using the same def <some object>.<some method> pattern, which defines instance methods on the object's singleton class, and here object is the class object. Radical!

Another way to define methods in ruby is with modules:

module Pisser
  def piss
    puts "pisss"
  end
end

Classes have a method called include which takes a module and adds the module methods to the class as instance methods:

Dog.include(Pisser)
dog1.piss # => pisss
dog2.piss # => pisss

Classes also have a method called extend which is described as adding "class methods" to the class, but to understand extend self, you need to understand that really extend is adding instance methods to the class' singleton class. Class methods are a ruby myth!

Dog.extend(Pisser)
Dog.piss # => pisss

Also remember that using class Whatever is just opening the Whatever class, and self becomes the class, which means this is the same as calling Dog.extend(Pisser):

class Dog
  extend Pisser # => here the implicit `self` is `Dog`
end

The same thing applies to modules....

module AnotherPisser
  extend Pisser
end

AnotherPisser.piss # => pisss

Which means we can define modules to namespace "functions" (that are really just instance methods on the module's singleton class):

module Util
  extend self # => the same as Util.extend(Util)

  # Since Util extends itself, these methods will exist on Util's singleton
  # class, making them callable directly on Util
  def hello
    puts "hello"
  end
end

Util.hello # => hello

Another way to define methods on an object's singleton class is with the class << <object> syntax. For example:

class << dog1
  def pet
    puts "tail wag!"
  end
end

dog1.pet # => tail wag!

Which means...

class Dog
  class << self
    def zen
      puts "ruby is radical!"
    end
  end
end

Inside the Dog class, self is the Dog class object, so class << self opens the Dog class object's singleton class to define methods, and ruby checks the singleton class first on method lookups:

Dog.zen # => ruby is radical!

The easy way to think about it is extend self is a convenient way to define "module methods" and class << self is a convenient way to define "class methods", but understanding the object model and method lookup shows there is nothing special going on. It's all just instance methods on objects.