Как я могу исправить эту NameError? - PullRequest
0 голосов
/ 15 июня 2010

Я хочу использовать значение 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. Как мне это исправить?

Ответы [ 3 ]

1 голос
/ 15 июня 2010

Ваше первое желание - использовать 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, что может оказаться не тем, что вы хотите.

1 голос
/ 15 июня 2010

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

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

0 голосов
/ 15 июня 2010

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