红宝石中的条件链接

Dan*_*man 20 ruby chaining

有没有一种在Ruby中有条件地链接方法的好方法?

我想在功能上做的是

if a && b && c
 my_object.some_method_because_of_a.some_method_because_of_b.some_method_because_of_c
elsif a && b && !c
 my_object.some_method_because_of_a.some_method_because_of_b
elsif a && !b && c
 my_object.some_method_because_of_a.some_method_because_of_c

etc...
Run Code Online (Sandbox Code Playgroud)

因此,根据一些条件,我想弄清楚在方法链中调用哪些方法.

到目前为止,我以"好的方式"做到这一点的最佳尝试是有条件地构建方法的串,并使用eval,但肯定有更好的,更红宝石的方式?

joh*_*nes 30

您可以将方法放入arry中,然后执行此数组中的所有内容

l= []
l << :method_a if a
l << :method_b if b
l << :method_c if c

l.inject(object) { |obj, method| obj.send(method) }
Run Code Online (Sandbox Code Playgroud)

Object#send执行具有给定名称的方法.Enumerable#inject迭代数组,同时为块提供最后返回的值和当前数组项.

如果您希望您的方法采用参数,您也可以这样做

l= []
l << [:method_a, arg_a1, arg_a2] if a
l << [:method_b, arg_b1] if b
l << [:method_c, arg_c1, arg_c2, arg_c3] if c

l.inject(object) { |obj, method_and_args| obj.send(*method_and_args) }
Run Code Online (Sandbox Code Playgroud)

  • 我认为这不会起作用,因为obj.send的结果取代了循环中的累加器,这可能不是一个有效的对象,可以在下次运行时发送请求的方法.简单的解决方法:明确返回"obj". (2认同)

MBO*_*MBO 10

你可以使用tap:

my_object.tap{|o|o.method_a if a}.tap{|o|o.method_b if b}.tap{|o|o.method_c if c}
Run Code Online (Sandbox Code Playgroud)

  • @DanSingerman是的,如果你的方法返回不同的对象而不是在内部修改`my_object`,它将无效.我应该写它,但我得到你的评论,那就是你想要的东西 (2认同)

Kel*_*vin 5

用于演示返回复制实例而不修改调用者的链接方法的示例类。这可能是您的应用程序所需的库。

class Foo
  attr_accessor :field
    def initialize
      @field=[]
    end
    def dup
      # Note: objects in @field aren't dup'ed!
      super.tap{|e| e.field=e.field.dup }
    end
    def a
      dup.tap{|e| e.field << :a }
    end
    def b
      dup.tap{|e| e.field << :b }
    end
    def c
      dup.tap{|e| e.field << :c }
    end
end
Run Code Online (Sandbox Code Playgroud)

Monkeypatch:这是您要添加到应用程序中以启用条件链接的内容

class Object
  # passes self to block and returns result of block.
  # More cumbersome to call than #chain_if, but useful if you want to put
  # complex conditions in the block, or call a different method when your cond is false.
  def chain_block(&block)
    yield self
  end
  # passes self to block
  # bool:
  # if false, returns caller without executing block.
  # if true, return result of block.
  # Useful if your condition is simple, and you want to merely pass along the previous caller in the chain if false.
  def chain_if(bool, &block)
    bool ? yield(self) : self
  end
end
Run Code Online (Sandbox Code Playgroud)

使用示例

# sample usage: chain_block
>> cond_a, cond_b, cond_c = true, false, true
>> f.chain_block{|e| cond_a ? e.a : e }.chain_block{|e| cond_b ? e.b : e }.chain_block{|e| cond_c ? e.c : e }
=> #<Foo:0x007fe71027ab60 @field=[:a, :c]>
# sample usage: chain_if
>> cond_a, cond_b, cond_c = false, true, false
>> f.chain_if(cond_a, &:a).chain_if(cond_b, &:b).chain_if(cond_c, &:c)
=> #<Foo:0x007fe7106a7e90 @field=[:b]>

# The chain_if call can also allow args
>> obj.chain_if(cond) {|e| e.argified_method(args) }
Run Code Online (Sandbox Code Playgroud)


Epi*_*ene 5

#yield_self从 Ruby 2.6 开始,使用or #then!

my_object.
  then{ |o| a ? o.some_method_because_of_a : o }.
  then{ |o| b ? o.some_method_because_of_b : o }.
  then{ |o| c ? o.some_method_because_of_c : o }
Run Code Online (Sandbox Code Playgroud)