web-dev-qa-db-fra.com

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

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

class MyClass
  def foo; end
  def bar; end
end

obj = MyClass.new
str = get_data_from_user  # e.g. `gets`, `params`, DB access, etc.
str  #=> "foo"
# somehow call `foo` on `obj` using the value in `str`.

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

91
user502052

Ce que vous voulez faire s'appelle dispatch dynamique . C’est très facile avec 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.

Ceci constitue un risque potentiel pour la sécurité si la valeur de method_name vient de l'utilisateur. Pour éviter les vulnérabilités, vous devez valider les méthodes pouvant être appelées. Par exemple:

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

Il existe plusieurs façons d’effectuer la répartition dynamique dans Ruby, chacune avec ses propres avantages et inconvénients. Il faut prendre soin de choisir la méthode la plus appropriée à la situation.

Le tableau suivant présente 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 se limitent à appeler des méthodes, alors que d'autres peuvent exécuter pratiquement n'importe quoi. Les méthodes permettant l'exécution de code arbitraire doivent être tilisées avec une extrême prudence, si elles ne sont pas totalement évitées .

Accès privé

Certaines techniques sont limitées à l'appel de méthodes publiques, alors que d'autres peuvent appeler des méthodes publiques et privées. Idéalement, vous devriez vous efforcer d'utiliser la méthode avec le moins de visibilité possible pour répondre à vos exigences.

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

Dangereux

Le fait qu’une technique ne puisse pas exécuter de 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. Delete est une méthode publique.

Le plus rapide

Certaines de ces techniques peuvent être plus performantes que d’autres, selon votre version de Ruby. Critè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
88
Brad Werth

Vous êtes vraiment allez vouloir faire attention à cela. L'utilisation de données utilisateur pour appeler n'importe quelle méthode via send pourrait laisser de la place à l'utilisateur pour qu'il puisse exécuter la méthode de son choix. send est souvent utilisé pour appeler des noms de méthodes de manière dynamique - mais assurez-vous que les valeurs saisies sont fiables et ne peuvent pas être manipulées par les utilisateurs.

La règle d'or est de ne jamais faire confiance aux entrées qui proviennent de l'utilisateur.

13
nzifnab

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

obj.send(str)
10
sawa

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
7
RameshVel