2009-11-25 9 views
19

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を使用することが最善の方法でしたが、確かにより優れたルビがありますか?

+1

なぜ私は条件付きチェインに興味がないのでしょうか。それはかなりのコードをクリーンアップします。 – Kelvin

答えて

28

あなたは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) } 
+1

+1 - これは非常に良いです、私は注射を使用することを考えていなかった – DanSingerman

+0

しかし、メソッドが引数を取る必要がある場合、私はこれを使用することはできますか? – DanSingerman

+2

これはうまくいくとは思えません。obj.sendの結果、ループ内のアキュムレータが置き換えられます。これはおそらく、要求されたメソッドを次の実行時に送信するための有効なオブジェクトではありません。簡単な回避策:明示的に "obj"を返します。 – hurikhan77

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() 
+0

これはどのように役立つのか分かりません。あなたは伸ばせますか? – DanSingerman

+0

私の考えを説明するために私の例を変更しました。または、あなたの質問を理解できず、メソッドのリストを動的に作成したいのですか? – demas

+0

デモはおそらく、 'some_method_because_of_a'の中に' if ... 'テストを入れて、チェーン全体を呼び出して、何をするのかをメソッドに任せることを暗示することを意図しています。 –

1

たぶん、あなたの状況はこれよりも複雑ですが、なぜしません:

+0

my_object.method_a.method_bは、my_object.method_a my_object.method_b – DanSingerman

+0

と同じではありません。私はmy_object.method_a!などの点でもっと考えていたと思います。 –

3

注入メソッドは完全に有効ですが、そのような種類のEnumerableは、peopl任意のパラメータを渡すことができないという制限があります。

このようなパターンは、このアプリケーションのためのより良いことがあります私が見つけた

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 
+0

スコープでのフィルタリングは、まさにこの問題が発生したユースケースでした。 – DanSingerman

+1

条件に直接パラメータを適用するには、以下のスニペットが便利です。Person.all(:conditions => params.slice(:country、:age)) – hurikhan77

+0

完全にマップされた場合、それはちょっとしたトリックです! – tadman

7

あなたはtapを使用することができます。

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

これはレールで、バニラ・ルビーではありませんが、そうではありませんか? – DanSingerman

+1

実際には、レールは '戻る '、'タップは純粋なRuby 1.8.7と1.9からです。 – MBO

+0

Brilliant - これは私が望むものを達成するための最良の方法だと思います。プラス1.8.6では、タップメソッドを定義するために簡単にサルのパッチを適用することができます(これはちょうど試したものですが、うまく動作するように思われました) – DanSingerman

2

Sampleクラスの呼び出し元を変更せずにコピーされたインスタンスを返すメソッドをチェーン実証します。 これはあなたのアプリに必要なライブラリかもしれません。

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 

モンキーパッチ:これはあなたがRailsのを使用している場合、あなたは#tryを使用することができますが、条件付き

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

を有効にするには、あなたのアプリケーションに追加したいものです。

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

または、引数を持つ:

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

バニラルビーで定義されていませんが、それisn't a lot of code代わり

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

書き込み。

私はCoffeeScriptの?.オペレータには何を与えませんか。

+0

私はこれが古い質問の古い答えであることを知っていますRubyにはRuby 2.3以降のSafe Navigationオペレータと同等の機能があります!それは '&.'です(Rubyトランクのこの機能の問題を参照してください:https://bugs.ruby-lang.org/issues/11537) – wspurgin

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] 
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"