21

有没有一种在 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...

因此,根据许多条件,我想确定在方法链中调用哪些方法。

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

4

10 回答 10

33

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

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) }
于 2009-11-25T14:26:41.547 回答
12

您可以使用tap

my_object.tap{|o|o.method_a if a}.tap{|o|o.method_b if b}.tap{|o|o.method_c if c}
于 2009-11-27T09:41:06.023 回答
4

尽管 inject 方法是完全有效的,但这种 Enumerable 使用确实会让人感到困惑,并且受到无法传递任意参数的限制。

像这样的模式可能更适合这个应用程序:

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
于 2009-11-25T15:31:55.287 回答
4

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

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 ? 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) }
于 2012-04-11T17:30:20.210 回答
2

使用#yield_selfor,从 Ruby 2.6 开始,#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 }
于 2021-02-16T10:11:36.127 回答
1

我使用这种模式:

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()
于 2009-11-25T14:31:13.723 回答
1

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

my_object.method_a if a
my_object.method_b if b
my_object.method_c if c
于 2009-11-25T14:33:29.473 回答
1

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

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

写:

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

或者,带参数:

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

没有在香草红宝石中定义,但它不是很多代码

我不会为 CoffeeScript 的?.操作员提供什么。

于 2013-08-05T22:31:25.353 回答
1

这是一种更具功能性的编程方式。

用于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"
于 2017-11-01T07:00:09.440 回答
0

我最终写了以下内容:

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]
于 2015-12-21T22:31:21.920 回答