Question
I've missed the memo somewhere, and I hope you'll explain this to me.
Why is the eigenclass of an object different from self.class
?
class Foo
def initialize(symbol)
eigenclass = class << self
self
end
eigenclass.class_eval do
attr_accessor symbol
end
end
end
My train of logic that equates the eigenclass with class.self
is rather
simple:
class << self
is a way of declaring class methods, rather than instance
methods. It's a shortcut to def Foo.bar
.
So within the reference to the class object, returning self
should be
identical to self.class
. This is because class << self
would set self
to
Foo.class
for definition of class methods/attributes.
Am I just confused? Or, is this a sneaky trick of Ruby meta-programming?
Answer
class << self
is more than just a way of declaring class methods (though it
can be used that way). Probably you've seen some usage like:
class Foo
class << self
def a
print "I could also have been defined as def Foo.a."
end
end
end
This works, and is equivalent to def Foo.a
, but the way it works is a little
subtle. 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. So def a
creates a new method called a
in
Foo
's eigenclass, accessible by the normal method call syntax: Foo.a
.
Now let's look at a different example:
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
This example is the same as the last one, though it may be hard to tell at
first. 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. We could also have overridden
methods of String (very useful in certain tricky testing scenarios).
Now we're equipped to understand your original example. Inside Foo
's
initialize method, self
refers not to the class Foo
, but to some
particular instance of Foo
. Its eigenclass is a subclass of Foo
, but it
is not Foo
; it couldn't be, or else the trick we saw in the second example
couldn't work. So to continue your example:
f1 = Foo.new(:weasels)
f2 = Foo.new(:monkeys)
f1.weasels = 4 # Fine
f2.monkeys = 5 # Also ok
print(f1.monkeys) # Doesn't work, f1 doesn't have a 'monkeys' method.