Как я могу вызвать метод динамически, когда его имя содержится в строковой переменной? Например:
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`.
Как я могу это сделать? Это угроза безопасности?
Ответ 1
Что вы хотите сделать, называется динамическая отправка. Его очень просто в Ruby, просто используйте public_send
:
method_name = 'foobar'
obj.public_send(method_name) if obj.respond_to? method_name
Если этот метод является частным/защищенным, используйте send
, но предпочитайте public_send
.
Это потенциальный риск безопасности, если значение method_name
исходит от пользователя. Чтобы предотвратить уязвимости, вы должны проверить, какие методы могут быть фактически вызваны. Например:
if obj.respond_to?(method_name) && %w[foo bar].include?(method_name)
obj.send(method_name)
end
Ответ 2
Существует несколько способов выполнения динамической отправки в Ruby, каждый из которых имеет свои преимущества и недостатки. Необходимо принять меры для выбора наиболее подходящего метода для ситуации.
В следующей таблице описаны некоторые из наиболее распространенных методов:
+---------------+-----------------+-----------------+------------+------------+
| 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 |
+---------------+-----------------+-----------------+------------+------------+
Произвольный код
Некоторые методы ограничены только вызывающими методами, в то время как другие могут выполнять в основном что угодно. Методы, позволяющие выполнять произвольный код, должны быть использованы с особой осторожностью, если вообще не избегать.
Доступ к приватным
Некоторые методы ограничены только вызовом общедоступных методов, в то время как другие могут вызывать как публичные, так и частные методы. В идеале вы должны стремиться использовать метод с наименьшим количеством видимости, отвечающей вашим требованиям.
Примечание. Если метод может выполнять произвольный код, его можно легко использовать для доступа к закрытым методам, к которым у него, возможно, нет доступа.
Dangerous
Просто потому, что метод не может выполнить произвольный код или вызвать частный метод, не означает, что он безопасен, особенно если вы используете предоставленные пользователем значения. Удалить - общедоступный метод.
Самый быстрый на
Некоторые из этих методов могут быть более эффективными, чем другие, в зависимости от вашей версии Ruby. Тесты, которые следует соблюдать...
Примеры
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
отправить
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
Метод
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
Ответ 3
Вы действительно хотите быть осторожным с этим. Использование пользовательских данных для вызова любого метода с помощью send
может оставлять пространство открытым для пользователей для выполнения любого метода, который они хотят. send
часто используется для динамического вызова имен методов, но убедитесь, что входные значения являются надежными и не могут быть обработаны пользователями.
Золотое правило никогда не доверяет никаким данным, поступающим от пользователя.
Ответ 4
Используйте send
для динамического вызова метода:
obj.send(str)
Ответ 5
Вы можете проверить доступность метода с помощью respond_to?
. Если он доступен, вы вызываете send
. Например:
if obj.respond_to?(str)
obj.send(str)
end