1

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?

1
  • As much as I love the poignant guide, it's hopelessly outdated when it comes to metaprogramming. All _why is trying to do is call define_method on the metaclass object, which is now trivial with define_singleton_method. Commented Oct 15, 2015 at 15:04

2 Answers 2

3

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
Sign up to request clarification or add additional context in comments.

Comments

1

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

4 Comments

But instance_eval defines methods on singleton class of object? Why do I need to pass singleton class to instance_eval?
instance_eval or class_eval or module_eval are very similar. They are just executes a block (in case if you pass a block) within the object they are called. So when you call Creature.instance_eval { define_method(:m) { ... } } it is equal to class Creature; def :m; ...; end;. Because you have Creature class in block's of eval context. You can simple check this by doing Creature.instance_eval { p self }. And in the case of your example, a "class method" is defined but not the instance method of Creature class.
BTW: we now not only have Object#singleton_class, but also Object#define_singleton_method, which makes all this song and dance unnecessary.
Yeah, right. It was just to explain about instance_eval and metaclass

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.