2010-06-15 2 views
0

Я хочу использовать значение v внутри методу экземпляра на метаклассе конкретного объекта:Как исправить это имя?

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 
}) 

К сожалению, class ключевого слова начинает новую область, так что я потерял старую область, и я получаю NameError , Как это исправить?

ответ

1

Замените class << p на class << p; self end.class_eval do и он будет работать.

class << p; self end вернет метакласс p, поэтому вы можете позвонить ему class_eval. Затем блок, заданный для class_eval, будет выполняться в контексте метакласса (как и раньше), но без запуска новой области.

1

Ваш первый наклон может быть использование class_eval на p, как это:

p.class_eval { 
    ... 
} 

Увы, это не будет работать, потому что class_eval является метод, определенный на Module, а не на Object. Поскольку p является экземпляром объекта, а не Module или Class, он не имеет метода class_eval.

Трюк состоит в том, чтобы сначала получить однотонный класс p, а затем запустить class_eval. С тех пор является a Class (и по расширению, Module), он имеет метод class_eval. Если вы в 1.9.2 или более поздней версии, есть singleton_class метод, который вы можете использовать:

p.singleton_class.class_eval { 
    ... 
} 

В противном случае, вы можете просто получить класс синглтона непосредственно:

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

В точках Йорга , вы можете также использовать define_singleton_method:

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

Но учтите, что если вы сделаете это, в результате чего validate метод будет private, который может и не быть тем, что вы хотите.

+0

Если вы на 1.9.2, вы можете просто использовать 'define_singleton_method' вместо этого. –

+0

@Jorg: Хороший момент!Я исправлю свой ответ. –

0

Просто для Пинки, вот как это будет выглядеть в Руби 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 
    } 
}) 
  • заменить явное возвращение p в конце с K комбинатора (Object#tap) введен в Ruby 1.8. 7 и 1.9.0
  • заменить lambda вызов метода с ргосом буквального введенным в Руби 1.9.0
  • заменить obj.call(args) с obj.(args) введенного в Руби 1.9.0
  • самое главное: использовать Object#define_singleton_method введены (или более точно: будет введено) в Руби 1.9.2
+0

Примечание: #define_singleton_method делает 'validate' приватным, поэтому это не совсем эквивалентно. –