Ruby/Rails: class_eval не хочет оценивать этот код

Чтобы создать макеты для 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 ответ

Решение

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-методы являются одними из самых плохих) следует использовать только в качестве последнего средства.

Другие вопросы по тегам