Динамически устанавливаемые параметры в define_method

#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)