conditional chaining in ruby

后端 未结 9 2164
一生所求
一生所求 2020-12-13 20:30

Is there a good way to chain methods conditionally in Ruby?

What I want to do functionally is

if a && b && c
 my_object.some_method_b         


        
相关标签:
9条回答
  • 2020-12-13 21:12

    Here's a more functional programming way.

    Use break in order to get tap() to return the result. (tap is in only in rails as is mentioned in the other answer)

    '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"
    
    0 讨论(0)
  • 2020-12-13 21:13

    Sample class to demonstrate chaining methods that return a copied instance without modifying the caller. This might be a lib required by your app.

    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: this is what you want to add to your app to enable conditional chaining

    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

    # 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) }
    
    0 讨论(0)
  • 2020-12-13 21:16

    Although the inject method is perfectly valid, that kind of Enumerable use does confuse people and suffers from the limitation of not being able to pass arbitrary parameters.

    A pattern like this may be better for this application:

    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
    

    I've found this to be useful when using named scopes. For instance:

    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
    
    0 讨论(0)
提交回复
热议问题