Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

define_method with predefined keyword arguments

I want to define a method that takes keyword arguments. I would like it to raise when keyword arguments are not provided, and I can code this myself - but ideally I would like to let Ruby do that for me. Also I would like to be able to inspect the freshly defined method using Method#parameters. If I use a shorthand double-splat (like **kwargs) the actual structure I expect is not visible to parameters.

I can of course do this:

define_method(:foo) do | foo:, bar: |
  # ...
end

which achieves the desired result:

method(:foo).parameters
# => [[:keyreq, :foo], [:keyreq, :bar]]

but I cannot pass those arguments programmatically, they have to be literally placed in the code. Is there a way I could bypass this?

like image 332
Julik Avatar asked Aug 04 '15 13:08

Julik


People also ask

What is a keyword argument in Ruby?

What are keyword arguments? Keyword arguments are a feature in Ruby 2.0 and higher. They're an alternative to positional arguments, and are really similar (conceptually) to passing a hash to a function, but with better and more explicit errors.

What is Define_method in Ruby?

define_method is a method defined in Module class which you can use to create methods dynamically. To use define_method , you call it with the name of the new method and a block where the parameters of the block become the parameters of the new method.

How do you dynamically define a method in Ruby?

One way to invoke a method dynamically in ruby is to send a message to the object. We can send a message to a class either within the class definition itself, or by simply sending it to the class object like you'd send any other message.


1 Answers

You have to use eval to define arguments dynamically (not just keyword arguments), e.g. using class_eval:

class MyClass
  name = :foo
  args = [:bar, :baz]
  class_eval <<-METHOD, __FILE__, __LINE__ + 1
    def #{name}(#{args.map { |a| "#{a}:" }.join(', ')}) # def foo(bar:, baz:)
      [#{args.join(', ')}]                              #   [bar, baz]
    end                                                 # end
  METHOD
end

MyClass.new.foo(bar: 1, baz: 2)
#=> [1, 2]

MyClass.instance_method(:foo).parameters
#=> [[:keyreq, :bar], [:keyreq, :baz]]
like image 57
Stefan Avatar answered Oct 10 '22 04:10

Stefan