Question

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)
Was it helpful?

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top