¿Cómo llamar a métodos dinámicamente según su nombre? [duplicar]

Resuelto user502052 asked hace 13 años • 5 respuestas

¿Cómo puedo llamar dinámicamente a un método cuando su nombre está contenido en una variable de cadena? Por ejemplo:

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`.

¿Cómo puedo hacer esto? ¿Hacerlo es un riesgo para la seguridad?

user502052 avatar Mar 18 '11 15:03 user502052
Aceptado

Lo que quieres hacer se llama despacho dinámico . Es muy fácil en Ruby, solo usa public_send:

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

Si el método es privado/protegido, utilícelo senden su lugar, pero prefiera public_send.

Este es un riesgo potencial para la seguridad si el valor de method_nameproviene del usuario. Para evitar vulnerabilidades, debe validar qué métodos se pueden llamar realmente. Por ejemplo:

if obj.respond_to?(method_name) && %w[foo bar].include?(method_name)
  obj.send(method_name)
end
David avatar Mar 18 '2011 09:03 David

Hay varias formas de lograr el despacho dinámico en Ruby, cada una con sus propias ventajas y desventajas. Se debe tener cuidado para seleccionar el método más apropiado para la situación.

La siguiente tabla desglosa algunas de las técnicas más comunes:

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

Código arbitrario

Algunas técnicas se limitan a llamar a métodos únicamente, mientras que otras pueden ejecutar básicamente cualquier cosa. Los métodos que permiten la ejecución de código arbitrario deben usarse con extrema precaución, si no evitarse por completo .

Acceso Privado

Algunas técnicas se limitan a llamar únicamente a métodos públicos, mientras que otras pueden llamar a métodos tanto públicos como privados. Idealmente, debería esforzarse por utilizar el método con la menor cantidad de visibilidad que cumpla con sus requisitos.

Nota : Si una técnica puede ejecutar código arbitrario, se puede utilizar fácilmente para acceder a métodos privados a los que de otro modo no tendría acceso.

Peligroso

El hecho de que una técnica no pueda ejecutar código arbitrario o llamar a un método privado no significa que sea segura, especialmente si utiliza valores proporcionados por el usuario. Eliminar es un método público.

Más rápido en

Algunas de estas técnicas pueden tener más rendimiento que otras, dependiendo de su versión de Ruby. Puntos de referencia a seguir....


Ejemplos

class MyClass
  def foo(*args); end

  private

  def bar(*args); end
end

obj = MyClass.new

evaluar

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

evaluación_instancia

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 

enviar

obj.send('foo') #=> nil 
obj.send('bar') #=> nil 

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

envío_publico

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étodo

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
Brad Werth avatar Oct 09 '2014 17:10 Brad Werth

Realmente querrás tener cuidado con esto. El uso de datos de usuario para llamar a cualquier método sendpodría dejar espacio abierto para que los usuarios ejecuten cualquier método que deseen. sendse usa a menudo para llamar nombres de métodos dinámicamente, pero asegúrese de que los valores de entrada sean confiables y no puedan ser manipulados por los usuarios.

La regla de oro es nunca confiar en ninguna información que provenga del usuario.

nzifnab avatar Mar 18 '2011 08:03 nzifnab

Úselo sendpara llamar a un método dinámicamente:

obj.send(str)
sawa avatar Mar 18 '2011 08:03 sawa