代码之家  ›  专栏  ›  技术社区  ›  DanSingerman

ruby中的条件链

  •  20
  • DanSingerman  · 技术社区  · 16 年前

    在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 ,但肯定有更好、更红宝石的方法吗?

    9 回复  |  直到 16 年前
        1
  •  32
  •   johannes    16 年前

    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) }
    
        2
  •  12
  •   MBO    16 年前

    你可以用 tap :

    my_object.tap{|o|o.method_a if a}.tap{|o|o.method_b if b}.tap{|o|o.method_c if c}
    
        3
  •  4
  •   Kelvin    14 年前

    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) }
    
        4
  •  3
  •   tadman    16 年前

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

    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
    
        5
  •  1
  •   Alison R.    16 年前

    my_object.method_a if a
    my_object.method_b if b
    my_object.method_c if c
    
        6
  •  1
  •   ceth    16 年前

    我使用这种模式:

    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()
    
        7
  •  1
  •   nornagon    12 年前

    如果您使用的是Rails,那么可以使用 #try . 而不是

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

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

    或者,使用参数:

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

    在香草ruby中没有定义,但是 isn't a lot of code .

    我不会为咖啡脚本付出什么 ?. 操作人员

        8
  •  1
  •   Ryo    8 年前

    这里有一种更实用的编程方法。

    使用 break 为了得到 tap() 返回结果。(如另一个答案中所述,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"
    
        9
  •  0
  •   Matt Willhite    10 年前

    我最后写了以下内容:

    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]
    
        10
  •  0
  •   Epigene    5 年前

    #yield_self #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 }
    
    推荐文章