如何传递方法发送的所有参数

时间:2019-02-19 15:56:32

标签: ruby

问题

在一个方法中,如何将作为该方法发送的一部分提供的所有参数传递给另一个方法发送?应该在没有*args之类的占位符/大写字母的意义上命名所有参数,但是它们可以混合使用关键字参数和非关键字参数。另外,内部方法send不是super

示例(伪代码)

def some_method(a_parameter, a_named_parameter:)
  ...do something...
  some_other_method([send with original parameters])
  ...do something else...
end

相关问题

Is there a way to access method arguments in Ruby?在7年前被问到。

丑陋的黑客

根据我的发现,可以对关键字参数执行以下操作:

def some_method(a_named_parameter:, another_named_parameter:)
  ...do something...

  params = method(__method__)
           .parameters
           .map { |p| [p[1], binding.local_variable_get(p[1])] }
           .to_h

  some_other_method(**params)
  ...do something else...
end

对于非关键字参数,这是

def some_method(a_named_parameter, another_named_parameter)
  ...do something...

  params = method(__method__)
           .parameters
           .map { |p| binding.local_variable_get(p[1]) }

  some_other_method(*params)
  ...do something else...
end

基于method(__method__).parameters返回的信息,一个人也可以找到一种对两者都适用的解决方案,但是即使可以将所有这些信息提取到一个帮助程序中,也很复杂。

3 个答案:

答案 0 :(得分:2)

这是一个非常有趣的问题,因此首先,谢谢您。

鉴于Binding是一个对象,就像其他对象一样,我们可以构建一个类来提供这种功能,并利用原始方法的绑定将所有参数委派给下一个方法,如下所示:< / p>

class ArgsBuilder 
  attr_reader
  def initialize(b)
    @standard_args, @kwargs = [],{}
    @binding = b
    build!
  end
  def delegate(m)
    @binding.receiver.send(m,*@standard_args,**@kwargs,&@block)
  end
  private
    def build!
      set_block(&@binding.eval('Proc.new')) if @binding.eval('block_given?')
      @binding.eval('method(__method__)') 
        .parameters.each do |type,name|
          next if type == :block
          val = @binding.local_variable_get(name)
          if type =~ /key/
            @kwargs.merge!(type == :keyrest ? val : {name => val})
          else  
            type == :rest ? @standard_args.concat(val) : @standard_args << val
          end
        end
    end
    def set_block(&block)
      @block = block
    end
end

用法:

class B 

  def some_method(a,b,c, *d, e:, f:, g: nil, **h)
    ArgsBuilder.new(binding).delegate(:some_other_method)      
  end

  def some_other_method(a,b,c, *d, e:, f:, g: , **h)
     yield __method__ if block_given?
    "I received: #{[a,b,c,d,e,f,g,h].inspect}"
  end
end

B.new.some_method(1,2,3,4,5,6, e: 7, f: 8, p: 9, n: 10) do |m| 
  puts "called from #{m}"
end
# called from some_other_method
#=> "I received: [1, 2, 3, [4, 5, 6], 7, 8, nil, {:p=>9, :n=>10}]"
#                 a  b  c  ----d----  e  f   g   -------h-------

看起来很简单,可以成为一个助手,并且还可以处理通过方法传递的命名和匿名块。

TL; DR

这显然需要在委托方法中与super相似的方式匹配或至少接受类似的签名。但是,我们可以采取许多步骤,并为参数类型[:req,:opt,:rest,:keyreq,:key,:keyrest,:block]创建类,然后将它们放入集合中,然后查询要委派的方法以确定要传递的正确args。但是我不确定此类示例是否适合SO帖子。

附加说明:因为build!initialize中被调用,所以局部变量绑定在ArgsBuilder类的实例化点是静态的。例如

 def foo(n) 
   builder = ArgsBuilder.new(binding) 
   n = 17
   builder.delegate(:bar)
 end 
 def bar(n) 
   n 
 end 

  foo(42) 
  #=> 42

但是

 def foo(n) 
   n = 17
   ArgsBuilder.new(binding).delegate(:bar)
 end 

 foo(42) 
 #=> 17

这并不意味着可变对象无法更改

 def foo(n)
   builder = ArgsBuilder.new(binding) 
   n.upcase!
   builder.delegate(:bar)
 end

 foo('a')
 #=> "A"

您显然可以通过将呼叫移至build!

来更改此设置

答案 1 :(得分:1)

也许你是这个意思

def some_method *args, **kwargs, &block
  ...
  some_other_method(*args, **kwargs, &block)
  ...
end

但是我认为在some _other_method上使用方法委托更为简洁。

答案 2 :(得分:1)

出于好奇,您可能会借助Module.prepend

class Origin
  def m1(p, *args, **kw)
    m2()
  end

  def m2(p, *args, **kw)
    puts "p: #{p}, args: #{args.inspect}, kw: #{kw.inspect}"
  end
end

module Wrapper 
  def m1(*args, **kw)
    @__args__, @__kw__ = args, kw
    super
  end

  def m2(*)
    super(*@__args__, **@__kw__)
  end
end

Origin.prepend(Wrapper)

Origin.new.m1(:foo, 42, bar: :baz)
#⇒ p: foo, args: [42], kw: {:bar=>:baz}