DEV Community

Cover image for Explaining Ruby's Singleton Class (Eigenclass) to confused beginners
Samuel-Zacharie FAURE
Samuel-Zacharie FAURE

Posted on • Updated on

Explaining Ruby's Singleton Class (Eigenclass) to confused beginners

Original of this post on my blog

I got really confused when I started to look up exactly what was the Eigenclass. This confusion was not because of any complexity, but rather because of bad bits of explanations there and there, and no articles to describe the big picture.

So my goal here is to help by explaining exactly what is Ruby's Eigenclass and why it's called "The Singleton Class".

To understand the Eigenclass, you just need to know the very basic concepts of Class and Instances. A reminder:

  • Classes are what we use to define our objects
  • Instances of classes are the objects created by our classes
class MyClass
  # Body of class
end

MyClass # => class
MyClass.new # => Instance of MyClass
Enter fullscreen mode Exit fullscreen mode

What's the Eigenclass ?

Here's the bit of information I had the most trouble to find: the Eigenclass is called the Singleton class because it is a Class that follows the Singleton pattern.

The Singleton pattern

The Singleton pattern is simply an object-oriented programming pattern where you make sure to have 1 and only 1 instance of some class.

Ruby implement the Singleton pattern with a module: just write Include Singleton in your class definition and you're good to go.

This module basically hides the :new method. MySingletonObject.new will always ERROR. Instead, it will give you an instance method that will always return the same unique instance of your class.

require 'singleton'

class NotSingleton
  # 'initialize' is called everytime an instance is created
  def initialize
    puts 'This will be printed many times'
  end
end

class MySingleton
  include Singleton

  # 'initialize' is called everytime an instance is created
  def initialize
    puts 'This will be printed once'
  end
end

NotSingleton.new # => 'This will be printed many times' 
NotSingleton.new # => 'This will be printed many times' 
NotSingleton.new # => 'This will be printed many times' 

MySingleton.instance # => 'This will be printed once'
MySingleton.instance # Nothing is printed
MySingleton.instance # Nothing is printed
Enter fullscreen mode Exit fullscreen mode

This is useful if you ever want to restrict a class so it never creates more than one instance of itself.

The Eigenclass

When you create an instance of a class, Ruby creates a hidden class, basically a copy of the original class, but which is owned exclusively by this instance. This is the Eigenclass. If you modify the Eigenclass of your first instance, it won't change anything for another instance.

# This class is NOT a singleton
class ExampleObject
  def print_hello
    puts 'Hello'
  end
end

object1 = ExampleObject.new
object2 = ExampleObject.new

object1.print_hello # => 'Hello'
object2.print_hello # => 'Hello'

def object2.print_hello
  puts 'Bonjour'
end

object1.print_hello # => 'Hello'
object2.print_hello # => 'Bonjour'
Enter fullscreen mode Exit fullscreen mode

There we go ! object2 is not defined in the scope of the class ExampleObject, it is defined by a copy of its class that it carries around. So by redefining a method in object2, we "open the Eigenclass" and modify properties just for this object.

Since the Eigenclass can exist only in one instance, it is sometimes called the Singleton class, althought ExampleObject is not a Singleton at all. Only the Eigenclasses of its instances are Singletons, because they each are one and unique.

The class << self notation

The previous example can be rewritten this way:

class ExampleObject
  def print_hello
    puts 'Hello'
  end
end

object1 = ExampleObject.new
object2 = ExampleObject.new

class << object2
  def print_hello
    puts 'Aloha'
  end
end

object1.print_hello # => 'Hello'
object2.print_hello # => 'Aloha'
Enter fullscreen mode Exit fullscreen mode

As you can see, the class << object2 syntax is used to access the Eigenclass of object2.

Now for the neat trick: you might know that, in Ruby, classes are objects too. In Ruby. Everything is an object !

And the Class of any class is always, well, Class. I love this about Ruby: it seems to not make any sense, but it actually does, in its own way.

You can try it yourself:

# We can create ExampleObject like this :
class ExampleObject
end

# Or like this :
ExampleObject = Class.new

ExampleObject.new.class # => ExampleObject
ExampleObject.class # => Class
Class.class # => Class
ExampleObject.class.class.class.class # => Class
Enter fullscreen mode Exit fullscreen mode

So, we just saw that the class ExampleObject is an object too, and more precisely, it is an instance of the class Class. This must means that it also have its own Eigenclass !

And if the Eigenclass of an instance of your class 'ExampleObject' is somewhat of a copy of the class 'ExampleObject', then the Eigenclass of the class 'ExampleObject' must be somewhat of a copy of the class 'Class'.

# Instance of class ExampleObject
ExampleObject.new # => Eigenclass : a copy of ExampleObject

# Instance of class Class
ExampleObject # => Eigenclass : a copy of Class
Enter fullscreen mode Exit fullscreen mode

Now, let's say we want to access the Eigenclass of the ExampleObject class.

We can do it this way:

class ExampleObject
  # Either inside the definition of the class
  class << self
    def class_print_hello
      puts 'Hello'
    end
  end
end

ExampleObject.class_print_hello # => 'Hello'

# or outside

class << ExampleObject
  def class_print_hello
    puts 'Sayonara'
  end
end

ExampleObject.class_print_hello # => 'Sayonara'
Enter fullscreen mode Exit fullscreen mode

As you see here, we don't call the method on an instance of the class (ExampleObject.new) but rather on the class itself (ExampleObject). It's okay though, because ExampleObject is itself an instance of Class.

If you're using Class methods, you're really using the Eigenclass

If you do some Rails, you'll often have in your models:

class MyModel
  def self.print_hello
    puts 'Hello'
  end
end

MyModel.print_hello # => 'Hello'
Enter fullscreen mode Exit fullscreen mode

As you can see, defining a method on the self object inside a class definition is exactly the same thing as opening the Eigenclass with class << self.

self.printHello is also called a Class method, to differenciate with an Instance method. See the following:

class MyModel
  # Class Method
  def self.print_hello
    puts 'Hello'
  end

  # Instance Method
  def print_bonjour
    puts 'Bonjour'
  end
end

MyModel.print_hello # => 'Hello'
MyModel.new.print_hello # => ERROR
MyModel.print_bonjour # => ERROR
MyModel.new.print_bonjour # => 'Bonjour'
Enter fullscreen mode Exit fullscreen mode

So as you see, the Class method of your class is actually just an Instance method of the Eigenclass of your class.

Don't hesitate and repeat that last sentence to your company's interns: they'll think you're a Ruby genius !

Latest comments (4)

Collapse
 
zenya profile image
Eugene

Hey Samuel!

This is without exaggeration the best explanation I have ever come across about Eigenclass!

Thanks for your efforts!!

Collapse
 
samuelfaure profile image
Samuel-Zacharie FAURE

You're welcome :)

Collapse
 
pabuisson profile image
Pierre-Adrien Buisson • Edited

hey hello Samuel, super interesting article, thanks for writing this! not the easiest concept in ruby and a pretty confusing one πŸ™‚

Only one tiny detail bothered me: in Ruby, the convention is to use snake_case case for methods and variables, and seeing these camelCase method names really hurts my rubyist brain. It's really a minor thing though but maybe that can be useful, I hope you won't mind me saying ☺️

Thanks again for this great post, keep'em coming πŸ’ͺ

Collapse
 
samuelfaure profile image
Samuel-Zacharie FAURE

Oh indeed, I must have been working on javascript while writing this! I'll correct it soon!