Comment appeler des méthodes dynamicment en fonction de leur nom?

Comment puis-je appeler une méthode de manière dynamic lorsque son nom est contenu dans une variable de chaîne? Par exemple:

class MyClass def foo; end def bar; end end obj = MyClass.new str = get_data_from_user # eg `gets`, `params`, DB access, etc. str #=> "foo" # somehow call `foo` on `obj` using the value in `str`. 

Comment puis-je faire ceci? Est-ce un risque pour la sécurité?

Ce que vous voulez faire est appelé répartition dynamic . C’est très facile dans Ruby, utilisez simplement public_send :

 method_name = 'foobar' obj.public_send(method_name) if obj.respond_to? method_name 

Si la méthode est privée / protégée, utilisez plutôt send , mais préférez public_send .

Il s’agit d’un risque de sécurité potentiel si la valeur de method_name provient de l’utilisateur. Pour éviter les vulnérabilités, vous devez valider les méthodes pouvant être réellement appelées. Par exemple:

 if obj.respond_to?(method_name) && %w[foo bar].include?(method_name) obj.send(method_name) end 

Il existe plusieurs manières d’effectuer une répartition dynamic en Ruby, chacune avec ses avantages et ses inconvénients. Il faut veiller à choisir la méthode la plus appropriée à la situation.

Le tableau suivant détaille certaines des techniques les plus courantes:

 +---------------+-----------------+-----------------+------------+------------+ | Method | Arbitrary Code? | Access Private? | Dangerous? | Fastest On | +---------------+-----------------+-----------------+------------+------------+ | eval | Yes | No | Yes | TBD | | instance_eval | Yes | No | Yes | TBD | | send | No | Yes | Yes | TBD | | public_send | No | No | Yes | TBD | | method | No | Yes | Yes | TBD | +---------------+-----------------+-----------------+------------+------------+ 

Code arbitraire

Certaines techniques sont limitées aux méthodes d’appel uniquement, tandis que d’autres peuvent exécuter pratiquement n’importe quoi. Les méthodes permettant l’exécution de code arbitraire doivent être utilisées avec une extrême prudence, voire évitées .

Accès privé

Certaines techniques se limitent à appeler des méthodes publiques uniquement, tandis que d’autres peuvent appeler à la fois des méthodes publiques et privées. Idéalement, vous devez vous efforcer d’utiliser la méthode avec le moins de visibilité possible pour répondre à vos besoins.

Remarque : Si une technique peut exécuter du code arbitraire, elle peut facilement être utilisée pour accéder à des méthodes privées auxquelles elle n’aurait peut-être pas access autrement.

Dangereux

Le fait qu’une technique ne puisse pas exécuter du code arbitraire ou appeler une méthode privée ne signifie pas qu’elle est sûre, en particulier si vous utilisez des valeurs fournies par l’utilisateur. Supprimer est une méthode publique.

Le plus rapide

Certaines de ces techniques peuvent être plus performantes que d’autres, en fonction de votre version de Ruby. Repères à suivre ….


Exemples

 class MyClass def foo(*args); end private def bar(*args); end end obj = MyClass.new 

eval

 eval('obj.foo') #=> nil eval('obj.bar') #=> NoMethodError: private method `bar' called # With arguments: eval('obj.foo(:arg1, :arg2)') #=> nil eval('obj.bar(:arg1, :arg2)') #=> NoMethodError: private method `bar' called 

instance_eval

 obj.instance_eval('foo') #=> nil obj.instance_eval('bar') #=> nil # With arguments: obj.instance_eval('foo(:arg1, :arg2)') #=> nil obj.instance_eval('bar(:arg1, :arg2)') #=> nil 

envoyer

 obj.send('foo') #=> nil obj.send('bar') #=> nil # With arguments: obj.send('foo', :arg1, :arg2) #=> nil obj.send('bar', :arg1, :arg2) #=> nil 

public_send

 obj.public_send('foo') #=> nil obj.public_send('bar') #=> NoMethodError: private method `bar' called # With arguments: obj.public_send('foo', :arg1, :arg2) #=> nil obj.public_send('bar', :arg1, :arg2) #=> NoMethodError: private method `bar' called 

méthode

 obj.method('foo').call #=> nil obj.method('bar').call #=> nil # With arguments: obj.method('foo').call(:arg1, :arg2) #=> nil obj.method('bar').call(:arg1, :arg2) #=> nil 

Vous allez vraiment vouloir faire attention à cela. L’utilisation de données utilisateur pour appeler n’importe quelle méthode via l’ send peut laisser la place aux utilisateurs d’exécuter n’importe quelle méthode. send est souvent utilisé pour appeler dynamicment les noms de méthodes – mais assurez-vous que les valeurs d’entrée sont fiables et ne peuvent pas être manipulées par les utilisateurs.

La règle d’or est de ne jamais faire confiance à aucune entrée provenant de l’utilisateur.

Utilisez send pour appeler une méthode de manière dynamic:

 obj.send(str) 

Vous pouvez vérifier la disponibilité de la méthode en utilisant respond_to? . S’il est disponible, vous appelez send . Par exemple:

 if obj.respond_to?(str) obj.send(str) end