ruby-on-railsrubyinstance-evalclass-eval

Ruby/Rails: class_eval doesn't want to evaluate this code


To generate mocks for Omniauth, I Added this method to 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

then I call in the same file:

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

But I get:

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

Solution

  • class_evaland module_eval (which are equivalent) are used to evaluate and immediately execute strings as Ruby code. As such they can be used as a meta-programming facility to dynamically create methods. An example is

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

    It will create two methods foo and bar which print their respective values. As you can see, I create a string containing the actual source code of the function and pass it into class_eval.

    While this is a very capable instrument of executing dynamically created code, it must be used with great care. If you make errors here, BAD THINGS WILL HAPPEN™. E.g. if you use user-supplied values when generating code, make really sure the variables contain only values you expect. Eval-based function should generally be used as the last resort.

    A cleaner and generally preferred variant is to use define_method like so:

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

    (Note that MRI is a wee bit faster with the eval variant. But that doesn't matter most of the time when compared to the added safety and clarity.)

    Now in your given code, you effectively write code into a string that can be run directly. Using class_eval here leads to the string being executed in the context of the top-most object (Kernel in this case). As this is a special singleton object which can't be instanciated (similar to nil, true and false), you get this error.

    However, as you create directly executable code, you don't need to use class_eval (or any form of eval) at all. Just run your code in a loop as is. And always remember: meta-programming variants (of which the eval methods are among the most bad-ass) should only be used as the last resort.