#ruby
#ruby
Вопрос:
У меня есть метод класса ::add_method(name, params = {})
, с помощью которого создается метод экземпляра define_method
.
Мне нужно, чтобы параметры определенного метода были аргументами ключевого слова в зависимости от params
.
class Whatever
def self.add_method(name, params = {})
# do something with params
define_method name do |?|
# some business
end
end
end
Цель состоит в том, чтобы при ::add_method
вызове с:
params = {
foo: { required: false, default: 0 },
bar: { required: true }
}
Whatever.add_method(:hello, params)
затем он создает этот метод:
def hello(foo: 0, bar:)
# some business
end
Примечание: это не настоящий бизнес, я слишком упростил его, чтобы вопрос было легче понять.
Комментарии:
1. На данный момент я не уверен, как сгибать
define_method
. Но вы всегда можете создать определение своего метода в виде строки иclass_eval
ее.2. @SergioTulentsev разве это не опасно? Знаете ли вы некоторые драгоценные камни, использующие этот шаблон, чтобы я мог проверить, как они справляются с этим?
3. Опасно, только если вы разрешаете оценивать пользовательский ввод. Если вы контролируете то, что оценивается, то дополнительной опасности нет (вы уже можете нанести большой ущерб без eval). Rails часто использует его. Например, github.com/rails/rails/blob /…
Ответ №1:
Итак, как мне посоветовали, я пошел class_eval
.
class Whatever
class << self
def add_method(name, parameters = {})
class_eval <<-RUBY, __FILE__, __LINE__ 1
def #{name}(#{method_parameters(parameters)})
#{method_body(parameters)}
end
RUBY
end
# method_parameters({
# foo: { required: false, default: 0 },
# bar: { required: true }
# })
# => "foo: 0, bar:"
def method_parameters(parameters)
parameters.map do |key, options|
value = options[:required] ? '' : " #{options[:default] || 'nil'}"
"#{key}:#{value}"
end.join(', ')
end
# method_parameters({
# foo: { required: false, default: 0 },
# bar: { required: true }
# })
# => "[foo, bar]"
def method_body(parameters)
"[#{parameters.keys.map(amp;:to_s).join(', ')}]"
end
end
end
params = {
foo: { required: false, default: 0 },
bar: { required: true }
}
Whatever.add_method(:hello, params)
Whatever.new.hello(bar: true) # => [0, true]
Whatever.new.hello(foo: 42, bar: true) # => [42, true]
Whatever.new.hello # missing keyword: bar (ArgumentError)