¿Cómo llamar a los métodos dinámicamente en función de su nombre?
¿Cómo puedo llamar a un método dinámicamente cuando su nombre está contenido en una variable string? 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? ¿Es un riesgo para la seguridad?
5 answers
Lo que quieres hacer se llama despacho dinámico. Es muy fácil en Ruby, solo tiene que usar 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
en su lugar, pero prefieren public_send
.
Este es un riesgo de seguridad potencial 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-09 19:33:11
Hay múltiples maneras de lograr el despacho dinámico 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 se limitan a llamar a métodos solamente, mientras que otras pueden ejecutar básicamente cualquier cosa. Los métodos que permiten la ejecución de código arbitrario deben ser utilizados con extrema precaución, si no se evita por completo .
Acceso privado
Algunas técnicas se limitan a llamar solo a métodos públicos, mientras que otras pueden llamar tanto a métodos públicos como privados. Idealmente, debe esforzarse por usar 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 usar fácilmente para acceder a métodos privados que de otro modo no tendría acceso a.
Peligroso
Solo porque una técnica no puede ejecutar código arbitrario o llamar a un método privado no significa que sea seguro, particularmente si está utilizando 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 para 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:19
Usted es realmente va a querer tener cuidado con esto. El uso de datos de usuario para llamar a cualquier método a través de send
podría dejar espacio abierto para que los usuarios ejecuten cualquier método que deseen. send
se usa a menudo para llamar a los nombres de los métodos dinámicamente, pero asegúrese de que los valores de entrada sean de confianza y no puedan ser manipulados por los usuarios.
La regla de oro es nunca confiar en ninguna entrada que venga del usuario.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-11 17:55:45
Uso send
para llamar a un método dinámicamente:
obj.send(str)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-10 23:29:44
Puede comprobar la disponibilidad del método usando respond_to?
. Si está disponible, llama a send
. Por ejemplo:
if obj.respond_to?(str)
obj.send(str)
end
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-10 23:30:26