Ruby / Rails: class_eval ne veut pas évaluer ce code
-
28-10-2019 - |
Question
Pour générer des simulations pour Omniauth, j'ai ajouté cette méthode à 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
alors j'appelle dans le même fichier:
provides_mocks_for :facebook, :twitter, :github, :meetup
Mais j'obtiens:
3.1.3/lib/active_support/core_ext/kernel/singleton_class.rb:11:in `class_eval': can't create instance of singleton class (TypeError)
La solution
class_eval
an et module_eval
(qui sont équivalents) sont utilisés pour évaluer et exécuter immédiatement des chaînes en tant que code Ruby. En tant que tels, ils peuvent être utilisés comme une installation de méta-programmation pour créer dynamiquement des méthodes. Un exemple est
class Foo
%w[foo bar].each do |name|
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{name}
puts '#{name}'
end
RUBY
end
end
Il créera deux méthodes foo
et bar
qui afficheront leurs valeurs respectives. Comme vous pouvez le voir, je crée une chaîne contenant le code source réel de la fonction et je le passe dans class_eval
.
Bien que ce soit un instrument très capable d'exécuter du code créé dynamiquement, il doit être utilisé avec beaucoup de précaution. Si vous faites des erreurs ici, de mauvaises choses arriveront ™. Par exemple. si vous utilisez des valeurs fournies par l'utilisateur lors de la génération de code, assurez-vous vraiment que les variables ne contiennent que les valeurs que vous attendez. La fonction basée sur l'évaluation doit généralement être utilisée en dernier recours.
Une variante plus propre et généralement préférée consiste à utiliser define_method
comme ceci:
class Foo
%w[foo bar].each do |name|
define_method name do
puts name
end
end
end
(Notez que l'IRM est un peu plus rapide avec la variante eval. Mais cela n'a pas d'importance la plupart du temps par rapport à la sécurité et à la clarté accrues.)
Maintenant, dans votre code donné, vous écrivez effectivement du code dans une chaîne qui peut être exécutée directement. L'utilisation de class_eval
ici conduit à l'exécution de la chaîne dans le contexte de l'objet le plus haut (Kernel
dans ce cas). Comme il s'agit d'un objet singleton spécial qui ne peut pas être instancié (similaire à nil, true et false), vous obtenez cette erreur.
Cependant, comme vous créez du code directement exécutable, vous n'avez pas du tout besoin d'utiliser class_eval
(ou toute forme d'eval). Exécutez simplement votre code en boucle tel quel. Et rappelez-vous toujours: les variantes de méta-programmation (dont les méthodes eval sont parmi les plus mauvaises) ne doivent être utilisées qu'en dernier recours.