Ruby’s Anonymous Eigenclass: Putting the “Ei” in Team

This week was my first real dive into the works of Ruby. I learned a TON. But something about Ruby still bothered me: what is it? Hoe does it really work? We’ve been told that Ruby is an Object-oriented-language — but what does that even mean? Well it turns out, in Ruby, almost everything is an Object. In fact the whole of Ruby can be broken down into a handful of different Objects and how they interact with each other:

Classes

Some famous classes you may recognize:

Hash.class = Class
Array.class = Class
Symbol.class = Class
Integer.class = Class
Class.class = Class

(Yes, even Class is a class!) In fact even when you’re in your main environment in the Terminal you are still in a class. Let’s try these in IRB and check out the return values:

self
=>main
self.class
=>Object

Now what’s so cool about classes, you ask? Well, we can create an instance of a class that has all of the attributes and methods we’ve defined within the class! Even cooler is that while some classes are built into Ruby, such as the aforementioned classes, we Rubyists can create our own classes! Cool!

Other parts of Ruby include Methods or, simply put: how Objects interact with one another, and finally: Keywords. For a full account of all forty-three keywords found in Ruby, check out this handy dandy keyword list. Keywords in Ruby are reserved words within the Ruby source code and are defined in relation to digital technology as follows:

[A] word used to classify or organize digital content, or to facilitate an online search for information: Search the database for the keyword “Ireland.”. — Dictionary.com
The keywords are not objects but defined in the parser which can be found in parse.y in the Ruby source. — Stack Overflow

Back to the Content!

Okay so now that we’ve got a basic understanding of Ruby’s structure, let’s get back to that weird word that we’re interested in today: Eigenclass. You may have already heard this concept before and not even know it. It has been called many things and there is no formal consensus on which is correct. It’s been called the Singleton Class, the Anonymous Class, the Ghost Class, Virtual Class, Shadow Class, Metaclass, Object-specific Class… you name it. Literally. It has no formal name - so get creative and make it stick and maybe one day we could be naming this class after you, true believer!

However, for the purposes of this blog and all things dorkly, I prefer to use the aforementioned “Eigenclass” here. Coming from the german word eigen, meaning ‘own’, the Eigenclass is a metaclass within Ruby. We can take it to literally mean ‘the class unto itself.’ Whoooah, meta! Scary, right? Nah. Lets take a peek under the hood:

class << self      
self
end

This self here is an instance of the Eigenclass. We can even write a method to access this instance:

def eigenclass    
class << self
self
end
end

You’re probably saying to yourself now, “Cool!” PSYCH! You’re still saying, “The class unto itself? What?! What does any of that even mean? Why do I even want an Eigenclass??”

All valid points. So lets take a closer look at what this method might return. Lets imagine we have a class called Thing and within it we insert this #eigenclass method:

class Thing
def eigenclass
class << self
self
end
end
end

Now we’ll create a new instance of Thing and call the instance method of #eigenclass on thing1, creating a class unto itself that we can access:

thing1 = Thing.new
=> #<Thing:0x007fb1a99224c8>
thing1.eigenclass
=>#<Class:#<Thing:0x007fb1a99224c8>>

Now remember, these instances have no other discernible differentiating features save for the unique object id with which they have been initialized. Let’s note that Eigenclass is a class type and that Eigenclass has a superclass — this superclass is the class it came from when initialized:

thing1.eigenclass.class
=> Class
thing1.eigenclass.superclass
=> Thing

This class belongs to the Thing class it was initialized with and defined in. More importantly, like the object id we see in the return value, the Eigenclass is unique. Even though the #eigenclass method is shared throughout all instances of Thing, the Eigenclass of the thing itself is not shared. Don’t believe me? Lets try another instance:

thing2 = Thing.new
=> #<Thing:0x007fb1aa81d860>
thing2.eigenclass
=> #<Class:#<Thing:0x007fb1aa81d860>>
thing2.eigenclass != thing1.eigenclass
=> true

So still going, “BUT WHAT IS AN EIGENCLASS”, right? The Eigenclass, the class unto itself, is unique to the object instance and is always present. To clarify, when we define our #eigenclass method in Thing, we are not creating an Eigenclass — it is, was and will always be there with the Object; every Object has an Eigenclass. Our #eigenclass method merely allows us to see and access what was always there but what was always hidden from view. Think of it like a ghost or, for the spiritual folk, a soul:

Sorry. Not that kind of soul. Once we have access to the class that is fundamentally unique to our instance, we can manipulate it, adding in unique instance methods that only the instance possesses depending on what we insert within the #eigenclass method’s “class << self” statement.

Now, why do we care? Well, because we use the eigenclass ALL OF THE TIME without even knowing it. Let’s add another method to our Thing class:

class Thing
def eigenclass
class << self
self
end
end
def self.what_am_i?
"I'm a thing!"
end
end
Thing.what_am_i?
=> "I'm a thing!"

When we add a class method such as #what_am_i? to an Object here and call it with dot notation, we’re actually using the Eigenclass! Within Ruby is a method called #singleton_class that we can call and prove this:

Thing.what_am_i?
=> "I'm a thing!"
Thing.singleton_methods
=> [:what_am_i?]
Thing.methods
=> [:what_am_i?, :new, :allocate, :superclass, :<=>, :module_exec, :class_exec, :<=, :>=, :==, :===, :include?, :included_modules, :ancestors, :name, :public_instance_methods, :instance_methods, :private_instance_methods, :protected_instance_methods, :const_get, :constants, :const_defined?, :const_set, :class_variables, :class_variable_get, :remove_class_variable, :class_variable_defined?, :class_variable_set, :private_constant, :public_constant, :singleton_class?, :deprecate_constant, :freeze, :inspect, :module_eval, :const_missing, :prepend, :method_defined?, :class_eval, :public_method_defined?, :private_method_defined?, :<, :public_class_method, :>, :protected_method_defined?, :private_class_method, :to_s, :autoload, :autoload?, :instance_method, :public_instance_method, :include, :lm, :lim, :instance_of?, :public_send, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :private_methods, :kind_of?, :instance_variables, :tap, :define_singleton_method, :is_a?, :public_method, :extend, :singleton_method, :to_enum, :enum_for, :=~, :!~, :eql?, :respond_to?, :display, :object_id, :send, :method, :nil?, :hash, :class, :singleton_class, :clone, :dup, :itself, :taint, :tainted?, :untaint, :untrust, :trust, :untrusted?, :methods, :protected_methods, :frozen?, :public_methods, :singleton_methods, :!, :!=, :__send__, :equal?, :instance_eval, :instance_exec, :__id__]

We can see here that the singleton_methods called on the class of Thing returns only our unique method! Whereas if we call the perhaps previously known method of #self.methods that is also built into Ruby, we’ll return all methods that can be called on this class and not necessarily the ones specific to it. Hm…

What this is really stating is that the class method is not actually defined on the class itself but rather on the Eigenclass, or the class unto itself! To put it another way:

This is effectively an anonymous class that Ruby creates and inserts into the inheritance hierarchy to hold the class methods (thus not interfering with the instances that are created from the class). — Mark McDonnell Senior Software Engineer @BuzzFeed

As McDonnell later puts it:

The reason understanding the Eigenclass is important is because it helps to clarify how to make some methods private when they otherwise would seem impossible to make private.

But when would we explicitly want to use it? Well, McDonnell points out the necessity for private methods:

class Foo
def self.bar
p "I'm public"
end

private
def self.baz
p "I'm private"
end
end

Foo.bar # => I'm public
Foo.baz # => I'm private

In the above example, McDonnell notes that although we’ve declared #self.baz to be private, we can in fact still call it. Why? Because private methods only apply to instance methods! And although class methods are instance methods (of the Eigenclass as we’ve now learned!), the delcaration of private in the above example applies only to the Foo class. Oh no! That means anyone can call #self.baz!

Not to worry! We can use our knowledge of the Eigenclass to get around this:

class Foo
def self.bar
puts "I'm public"
end
  class << self
private
def baz
"I'm private"
end
end
end
Foo.bar # => I'm public
Foo.baz # => NoMethodError: private method `baz' called for Foo:Class

Here we’ve accessed the Eigenclass of Foo to place a class method as if it were an instance method (within the Eigenclass instance!) and now we can make it private! Awesome!

Using this tactic we can even create attribute accessors. Attribute Accessors, also known as macros, are programs that read and write methods for us without us having to explicitly do so within our class — however, they only work for instance variables to create instance methods. They do not apply to class methods or variables.

Based on what we just learned however, we can turn a class method into an instance method by placing it within the instance of the Eigenclass that we exposed and accessed! Lets insert an attr_accessor within Foo and try to call the variable “baz” using name spacing notation:

class Foo
def self.bar
p "I'm public"
end
  @baz = "I'm a class variable!"
  class << self
attr_accessor :baz
end
end
Foo.bar
=> "I'm public"
Foo::baz
=>"I'm a class variable!"

We can do this without specifying an instance using name spacing to declare that “baz” is defined within the Eigenclass instance of the class Foo! Lets check out some more documentation and another example:

The secret is that self, in that context, refers to the object Foo, whose class is a unique, anonymous subclass of Class. This subclass is called Foo's eigenclass. — David Seiler, Stack Overflow

Now let’s look at a different example from Stack Overflow:

str = "abc"
other_str = "def"
class << str
 def frob
 return self + "d"
 end
end
print str.frob # => "abcd"
print other_str.frob # => raises an exception, 'frob' is not defined on other_str
frob is defined, not on the String class, but on the eigenclass of str, a unique anonymous subclass of String. So str has a frob method, but instances of String in general do not. — David Seiler, Stack Overflow

By the time we get ti ActiveRecord and ActiveSupport we will have moved past these concepts and into using things like “cattr_accessor” or class attribute accessors. But for now in pure Ruby, the secret sauce of the fundamentals can still be fundamentally fun :)

The Eigenclass has nothing inherent to itself, and asks nothing in return; no arguments or parameters, no definition or even a thank you card. Silent, anonymous and constant, it provides that essential aspect of any individual or collective: a unique identity.

BONUS:

Of course, Ruby being as wonderful as Ruby is, we already have another way to allow us to make class methods private. And of course, being the generous spirits that we are as Rubyists and following the code of Matz is Nice so We are Nice (MINSWAN), here is your shortcut that you’ve now earned:

class Foo
def self.bar
p "im public"
end

def self.baz
p "im private"
end

private_class_method :baz
end

Foo.bar # => im public
Foo.baz # => NoMethodError: private method `baz' called for Foo:Class

Thanks for reading!