Ruby Metaclass, Eigenclass, Singleton

Igor Guzak
3 min readJan 8, 2020

--

In the previous post about Class I’ve described the fact that it’s instance and now it’s time to describe how we define new functionality for such instances and instances which they could produce. Example:

class Person
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end

def full_name
"#{@first_name} #{@last_name}"
end
end
Person.instance_methods(false) # => [:full_name]person = Person.new("Bob", "Martin")
person.full_name # => Bob Martin

As we can see there is a definition of Class instance Person which describe functionality of instances it could produce. Method new is part of Class implementation which is responsible for creating new instance and after such instance is created the private method initialize will be invoked on it. So, both methods initialize and full_name are called instance methods simply because they are available for instances produced by Person, but in the same way method new is instance method of Person instance produced by Class, such methods, available for Class instances we call simply as “class methods”.

So, if we need more functionality for instances produced by Person we could define it in place of creation Person instance, but how we could add some functionality to Person instance itself which is produced by Class instance? Do we need to define it in place of creation Class instance? but where is such a place? — The answer is that we don’t need to modify Class instance to add functionality to the single instance produced by it, such as Person, at list because we need to define some functionality only for Person instance not for all instances produced by Class instance. The solution is that every instance has one more instance behind it and it is linked to the initial one and could be used for such purpose:

class Person
def self.info
"Some description"
end
end
# orclass Person
class << self
def info
"Some description"
end
end
end
# ordef Person.info
"Some description"
end
# orclass << Person
def info
"Some description"
end
end
Person.singleton_class.instance_methods(false) # => [:info]Person.info # => Some description

So, if we need some “class methods” for our Class instances such as Person instance then we use the second instance behind it which is called as Metaclass or Eigenclass or Singleton.

def Person.new; endPerson.new # => nil

As you can see we redefine new method with our own implementation located in Metaclass. The next question could be — if every instance has some instance behind it then how it works when we try it on instances different from Class instances?

bob = Person.new("Bob", "Martin")
ricky = Person.new("Ricky", "Martin")
def bob.full_name
"Robert Cecil #{@last_name}"
end
# orclass << bob
def full_name
"Robert Cecil #{@last_name}"
end
end
bob.full_name # => Robert Cecil Martin
ricky.full_name # => Ricky Martin

So, we redefine bob instance full_name method definition with custom implementation placed in Metaclass which just highlight the way how it’s linked to initial instance, at the same time instance ricky didn’t change behaviour simply because it has own Metaclass. While this works fine there is no much reason to use it and confuse everyone why instances produced by the same Person instance have different behaviour, so remember that it’s solution to have different behaviour for instances produced by Class instance such as Person, File, Dir, etc. which are produced from the same Class instance but needs different functionality.

If there is still a need to define some functionality which need to be available for all Class instances or we need to change already existed Class instance then we could simply “re-open” it, but there is no much sense in such monkey patching, which could bring a lot of pain:

class Class
def info
"#{self.name} instance produced by Class instance"
end
end
class Person
def full_name
"patched full_name" # original method need to be updated instead
end
end
Person.info # => Person instance produced by Class instance
Person.new(nil, nil).full_name # => patched full_name

Again there is some special cases with “unique” values such as symbols and numbers which doesn’t have additional instance behind it because there is no much sense to define specific functionality only for single symbol or number, at same time we have such ability for true, false, nil values:

def true.info; enda = :something
def a.info; end
# => TypeError (can't define singleton)

From the message, it’s obvious that we try to define Metaclass but it was rejected, so Metaclasses are created automatically only for instances produced by Class instance, while for other instances Metaclasses are created when they are needed.

--

--