Вложенная в Ruby send

Скажем, у меня есть объект с методом, который обращается к объекту:

def foo
   @foo
end

Я знаю, что могу использовать send для доступа к этому методу:

obj.send("foo")  # Returns @foo

Есть ли простой способ сделать рекурсивную отправку, чтобы получить параметр в объекте @foo, например:

obj.send("foo.bar")  # Returns @foo.bar

Ответ 1

Вы можете использовать instance_eval:

obj.instance_eval("foo.bar")

Вы даже можете получить доступ к переменной экземпляра напрямую:

obj.instance_eval("@foo.bar")

Ответ 2

Пока OP уже принял ответ, используя instance_eval(string), я настоятельно призывал бы OP избегать строковых форм eval, если это абсолютно необходимо. Eval вызывает компилятор ruby ​​- он дорого вычисляется и опасен для использования, поскольку он открывает вектор для инъекций инъекций кода.

Как указано, нет необходимости отправлять вообще:

obj.foo.bar

Если действительно имена foo и bar исходят из некоторого нестатического вычисления, то

obj.send(foo_method).send(bar_method)

просто, и все это нужно для этого.

Если методы идут в виде точечной строки, можно использовать split и inject для цепочки методов:

'foo.bar'.split('.').inject(obj, :send)

Уточнение в ответ на комментарии: String eval - одна из самых рискованных вещей, которые можно сделать с точки зрения безопасности. Если какой-либо способ строка создается из введенного пользователем ввода без невероятно тщательного контроля и проверки этого ввода, вы должны просто подумать о своей системе.

send (method), где метод получен из пользовательского ввода, также имеет риски, но существует более ограниченный вектор атаки. Вход пользователя может привести к тому, что любой метод 0-arghument будет отправлен через ресивер. Хорошая практика здесь заключалась бы в том, чтобы всегда включать белый список методов перед отправкой:

VALID_USER_METHODS = %w{foo bar baz}
def safe_send(method)
  raise ArgumentError, "#{method} not allowed" unless VALID_USER_METHODS.include?(method.to_s)
  send(method)
end

Ответ 3

Немного поздно для вечеринки, но мне пришлось сделать что-то похожее, которое должно было объединить как "отправку", так и доступ к данным из хэша/массива за один вызов. В основном это позволяет сделать что-то вроде следующего

value = obj.send_nested("data.foo['bar'].id")

и под капотом это сделает что-то похожее на

obj.send(data).send(foo)['bar'].send(id)

Это также работает с символами в строке атрибута

value = obj.send_nested('data.foo[:bar][0].id')

который будет делать что-то похожее на

obj.send(data).send(foo)[:bar][0].send(id)

В случае, если вы хотите использовать равнодушный доступ, вы можете добавить это как параметр. Например.

value = obj.send_nested('data.foo[:bar][0].id', with_indifferent_access: true)

Поскольку это немного более важно, вот ссылка на суть, которую вы можете использовать, чтобы добавить этот метод к базовому объекту Ruby, (Он также включает тесты, чтобы вы могли видеть, как это работает)