在一个方法中,如何将作为该方法发送的一部分提供的所有参数传递给另一个方法发送?应该在没有*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
返回的信息,一个人也可以找到一种对两者都适用的解决方案,但是即使可以将所有这些信息提取到一个帮助程序中,也很复杂。
答案 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}