views:

29

answers:

3

I want to use the value v inside of an instance method on the metaclass of a particular object:

v = ParserMap[kind][:validation]   # We want to use this value later.
s = ParserMap[kind][:specs]
const_set(name, lambda {
  p = Parser.new(&s)

  # This line starts a new scope...
  class << p
    define_method :validate do |opts|
      v.call(self, opts)  # => NameError! The `class` keyword above
                          #    has started a new scope and we lost
                          #    old `v`.
    end
  end
  p
})

Unfortunately, the class keyword starts a new scope, so I lose the old scope and I get a NameError. How do I fix this?

+1  A: 

Replace class << p with class << p; self end.class_eval do and it will work.

class << p; self end will return the metaclass of p, so you can call class_eval on it. The block given to class_eval will then execute in the context of the metaclass (same as it did before), but without starting a new scope.

sepp2k
+1  A: 

Your first inclination might be to use class_eval on p, like this:

p.class_eval {
  ...
}

Alas, that won't work, because class_eval is a method defined on Module, not on Object. Since p is an instance of an object, not of a Module or Class, it doesn't have a class_eval method.

The trick is to get p's singleton class first, and then run class_eval on that. Since that is a Class (and by extension, a Module), it has a class_eval method. If you're in 1.9.2 or later, there's a singleton_class method you can use:

p.singleton_class.class_eval {
  ...
}

Otherwise, you can just get the singleton class directly:

(class << p; self; end).class_eval {
  ...
}

As Jorg points out, you can also use define_singleton_method:

p.define_singleton_method :validate { |opts|
  v.call(self, opts)
}

But note that if you do this, the resulting validate method will be private, which may not be what you want.

John Feminella
If you're on 1.9.2, you could just use `define_singleton_method` instead.
Jörg W Mittag
@Jorg: Good point! I'll amend my answer.
John Feminella
A: 

Just for kicks, here's how it would look like in Ruby 1.9.2:

v = ParserMap[kind][:validation]
s = ParserMap[kind][:specs]
const_set(name, ->{
  Parser.new(&s).tap {|p|
    p.define_singleton_method :validate do |opts| v.(self, opts) end
  }
})
  • replace the explicit return of p at the end with the K combinator (Object#tap) introduced in Ruby 1.8.7 and 1.9.0
  • replace the lambda method call with a proc literal introduced in Ruby 1.9.0
  • replace obj.call(args) with obj.(args) introduced in Ruby 1.9.0
  • most importantly: use Object#define_singleton_method introduced (or more precisely: going to be introduced) in Ruby 1.9.2
Jörg W Mittag
Note: #define_singleton_method makes `validate` private, so this isn't exactly equivalent.
Kyle Kaitan