ruby中的条件链接

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

我想要在function上做什么

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... 

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

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

您可以将方法放入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) } 

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

你可以使用tap

 my_object.tap{|o|o.method_a if a}.tap{|o|o.method_b if b}.tap{|o|o.method_c if c} 

虽然注入方法是完全有效的,但是这种可枚举的使用确实使人困惑并且受到不能传递任意参数的限制。

这样的模式对于此应用程序可能更好:

 object = my_object if (a) object = object.method_a(:arg_a) end if (b) object = object.method_b end if (c) object = object.method_c('arg_c1', 'arg_c2') end 

我发现这在使用命名范围时很有用。 例如:

 scope = Person if (params[:filter_by_age]) scope = scope.in_age_group(params[:filter_by_age]) end if (params[:country]) scope = scope.in_country(params[:country]) end # Usually a will_paginate-type call is made here, too @people = scope.all 

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

 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 

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 

样品用法

 # sample usage: chain_block >> cond_a, cond_b, cond_c = true, false, true >> f.chain_block{|e| cond_a ? ea : e }.chain_block{|e| cond_b ? eb : e }.chain_block{|e| cond_c ? ec : e } => # # 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) => # # The chain_if call can also allow args >> obj.chain_if(cond) {|e| e.argified_method(args) } 

也许你的情况比这更复杂,但为什么不呢:

 my_object.method_a if a my_object.method_b if b my_object.method_c if c 

我使用这种模式:

 class A def some_method_because_of_a ... return self end def some_method_because_of_b ... return self end end a = A.new a.some_method_because_of_a().some_method_because_of_b() 

如果您使用的是Rails,则可以使用#try 。 代替

 foo ? (foo.bar ? foo.bar.baz : nil) : nil 

写:

 foo.try(:bar).try(:baz) 

或者,带参数:

 foo.try(:bar, arg: 3).try(:baz) 

在vanilla ruby​​中没有定义,但它不是很多代码 。

我不会为CoffeeScript提供?. 运营商。

这是一种function更强大的编程方式。

使用break来获取tap()以返回结果。 (如同在另一个答案中提到的那样,点击仅在轨道中)

 'hey'.tap{ |x| x + " what's" if true } .tap{ |x| x + "noooooo" if false } .tap{ |x| x + ' up' if true } # => "hey" 'hey'.tap{ |x| break x + " what's" if true } .tap{ |x| break x + "noooooo" if false } .tap{ |x| break x + ' up' if true } # => "hey what's up" 

我最后写了以下内容:

 class Object # A naïve Either implementation. # Allows for chainable conditions. # (a -> Bool), Symbol, Symbol, ...Any -> Any def either(pred, left, right, *args) cond = case pred when Symbol self.send(pred) when Proc pred.call else pred end if cond self.send right, *args else self.send left end end # The up-coming identity method... def itself self end end a = [] # => [] a.either(:empty?, :itself, :push, 1) # => [1] a.either(:empty?, :itself, :push, 1) # => [1] a.either(true, :itself, :push, 2) # => [1, 2]