¿Cómo llamar a métodos dinámicamente según su nombre? [duplicar]
¿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?
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 send
en su lugar, pero prefiera public_send
.
Este es un riesgo potencial para la 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
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
Realmente querrás tener cuidado con esto. El uso de datos de usuario para llamar a cualquier método send
podría dejar espacio abierto para que los usuarios ejecuten cualquier método que deseen. send
se 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.
Úselo send
para llamar a un método dinámicamente:
obj.send(str)