Ruby / Rails:class_eval不想评估这段代码

时间:2012-01-14 14:35:30

标签: ruby-on-rails ruby instance-eval 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 个答案:

答案 0 :(得分:2)

class_evalmodule_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

它将创建两个方法foobar,用于打印各自的值。如您所见,我创建了一个包含函数实际源代码的字符串,并将其传递给class_eval

虽然这是一个非常强大的执行动态创建代码的工具,但必须非常谨慎地使用它。如果你在这里犯错,那么糟糕的事情就会发生。例如。如果在生成代码时使用用户提供的值,请使确实确保变量仅包含您期望的值。基于Eval的函数通常应该用作最后的手段。

更清洁且通常首选的变体是使用define_method,如下所示:

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

(请注意,对于eval变体,MRI的速度要快一些。但与增加的安全性和清晰度相比,大多数情况下这并不重要。)

现在,在您给定的代码中,您可以有效地将代码写入可以直接运行的字符串中。在这里使用class_eval会导致在最顶层对象(在本例中为Kernel)的上下文中执行字符串。由于这是一个特殊的单例对象,无法实现(类似于nil,true和false),因此会出现此错误。

但是,当您直接创建可执行代码时,根本不需要使用class_eval(或任何形式的eval)。只需按循环运行代码即可。并且永远记住:元编程变体(其中eval方法属于最坏的方法)应该只作为最后的手段使用。