Reputation: 129
I am reading why's poignant guide to ruby, and in chapter 6, he used this code:
class Creature
# Get a metaclass for this class
def self.metaclass; class << self; self; end; end
...
def self.traits( *arr )
# 2. Add a new class method to for each trait.
arr.each do |a|
metaclass.instance_eval do
define_method( a ) do |val|
@traits ||= {}
@traits[a] = val
end
end
end
end
end
Why is he calling instance_eval
on metaclass of class Creature
? Since instance_eval
adds methods to metaclass
, he can just do this:
def self.metaclass; self; end;
Or am I wrong? Are there any more elegant solutions to this?
Upvotes: 1
Views: 912
Reputation: 22315
The simpler way to write _why's code would be just
def self.traits( *arr )
# 2. Add a new class method to for each trait.
arr.each do |a|
metaclass.define_method(a) do |val|
@traits ||= {}
@traits[a] = val
end
end
end
The only problem here is that you get an error:
private method `define_method' called for metaclass (NoMethodError)
A private method can only be called with an implicit receiver i.e. the problem is the explicit metaclass.
before the method call. But if we remove that, the implicit receiver (self
) is Creature
! So how do we change self
to a different object? instance_eval
:
metaclass.instance_eval do
define_method(a) do |val|
...
end
end
So it's really just a way of bypassing the fact that define_method
is private. Another way to hack it would be to use send
metaclass.send(:define_method, a) do |val|
...
end
But these days all of that is totally unnecessary; you are allowed to define methods in the metaclass (AKA singleton class) without hacking around private methods:
def self.traits( *arr )
# 2. Add a new class method to for each trait.
arr.each do |a|
define_singleton_method(a) do |val|
@traits ||= {}
@traits[a] = val
end
end
end
Upvotes: 3
Reputation: 831
If you do
def self.metaclass; self; end;
you will have a reference to Creature
class. So, in that case, the methods will be defined not to the singleton class of object Creature
but to the class Creature
itself(to the list of instance methods of class Creature
). The method
def self.metaclass; class << self; self; end; end
is a simple way to retrieve singleton class of object Creature
in ruby < 1.9. In ruby 1.9+ was implemented method singleton_class
which is shortcut of class << self
. So that code can be simplified as:
class Creature
...
def self.traits( *arr )
# 2. Add a new class method to for each trait.
arr.each do |a|
singleton_class.instance_eval do
define_method( a ) do |val|
@traits ||= {}
@traits[a] = val
end
end
end
end
end
Upvotes: 1