Ruby / Rails: class_eval не хочет оценивать этот код - PullRequest
1 голос
/ 14 января 2012

Для генерации макетов для Omniauth я добавил этот метод к config/environments/development.rb

  def provides_mocks_for(*providers)
    providers.each do |provider|
      class_eval %Q{
        OmniAuth.config.add_mock(provider, {
          :uid => '123456',
          :provider => provider,
          :nickname => 'nickname',
          :info => {
            'email' => "#{provider}@webs.com",
            'name' => 'full_name_' + provider
          }
        })
      }
    end
  end

, затем я вызываю в тот же файл:

provides_mocks_for :facebook, :twitter, :github, :meetup

Но я получаю:

3.1.3/lib/active_support/core_ext/kernel/singleton_class.rb:11:in `class_eval': can't create instance of singleton class (TypeError)

1 Ответ

2 голосов
/ 14 января 2012

class_eval и module_eval (которые эквивалентны) используются для оценки и немедленного выполнения строк в виде кода Ruby. Как таковые они могут использоваться как средство метапрограммирования для динамического создания методов. Примером является

class Foo
  %w[foo bar].each do |name|
    self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
      def #{name}
        puts '#{name}'
      end
    RUBY
  end
end

Будет создано два метода foo и bar, которые печатают свои соответствующие значения. Как видите, я создаю строку, содержащую фактический исходный код функции, и передаю ее в class_eval.

Хотя это очень эффективный инструмент для выполнения динамически создаваемого кода, его следует использовать с большой осторожностью. Если вы допустите ошибки здесь, ПЛОХИЕ ВЕЩИ БУДУТ ПРОИЗОЙТИ. Например. если вы используете пользовательские значения при генерации кода, убедитесь, что действительно , что переменные содержат только те значения, которые вы ожидаете. В качестве последнего средства обычно следует использовать функцию на основе Eval.

Более чистым и обычно предпочтительным вариантом является использование define_method примерно так:

class Foo
  %w[foo bar].each do |name|
    define_method name do
      puts name
    end
  end
end

(Обратите внимание, что MRI немного быстрее в варианте eval. Но это не имеет значения в большинстве случаев по сравнению с дополнительной безопасностью и ясностью.)

Теперь в вашем данном коде вы эффективно пишете код в строку, которую можно запустить напрямую. Использование class_eval здесь приводит к выполнению строки в контексте самого верхнего объекта (Kernel в данном случае). Поскольку это специальный одноэлементный объект, который не может быть создан (аналогично nil, true и false), вы получаете эту ошибку.

Однако, когда вы создаете непосредственно исполняемый код, вам вообще не нужно использовать class_eval (или любую форму eval). Просто запустите ваш код в цикле, как есть. И всегда помните: варианты метапрограммирования (из которых методы eval являются одними из самых плохих) следует использовать только в качестве последнего средства.

...