¿Cómo llamar a los métodos de forma dinámica en función de su nombre?

¿Cómo puedo llamar un método dinámicamente 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 # eg `gets`, `params`, DB access, etc. str #=> "foo" # somehow call `foo` on `obj` using the value in `str`. 

¿Cómo puedo hacer esto? ¿Es un riesgo de seguridad?

Lo que quiere hacer se llama despacho dynamic . 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, use send lugar, pero prefiera public_send .

Este es un posible riesgo de seguridad si el valor de method_name proviene 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 

Existen múltiples formas de lograr un despacho dynamic en Ruby, cada una con sus propias ventajas y desventajas. Se debe tener cuidado de 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 están limitadas a los métodos de llamadas solamente, 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 se evitan por completo .

Acceso privado

Algunas técnicas se limitan a llamar solo a los métodos públicos, mientras que otras pueden llamar a métodos públicos y privados. Idealmente, debe 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, puede usarse fácilmente para acceder a métodos privados a los que de otro modo no podría tener 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 seguro, especialmente si está utilizando valores proporcionados por el usuario. Eliminar es un método público.

Lo más rápido posible

Algunas de estas técnicas pueden ser más efectivas que otras, según tu 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 

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 

enviar

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

Realmente vas a querer tener cuidado con esto. El uso de los datos del usuario para llamar a cualquier método mediante el send podría dejar espacio para que los usuarios ejecuten cualquier método que deseen. send se usa a menudo para llamar dinámicamente a los nombres de los métodos, pero asegúrese de que los valores de entrada sean confiables y no puedan ser manipulados por los usuarios.

La regla de oro nunca confía en ninguna entrada que provenga del usuario.

Use send para llamar a un método de forma dinámica:

 obj.send(str) 

Puede verificar la disponibilidad del método usando respond_to? . Si está disponible, entonces llama a send . Por ejemplo:

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