Techioz Blog

Ruby で複数のフィルターを適用する最もエレガントな方法 #select

概要

アプリケーションに Array#select メソッドでデータをフィルタリングするクエリ オブジェクトがあります

class QueryObject
  def call(filters)
    data = get_data # returns array 
    data = by_param1(data, filter[:param1])
    data = by_param2(data, filter[:param2])
  end

  private

  def by_param1(data, filter)
    data.select { |d| #filtering goes here }
  end

  def by_param2(data, filter)
    data.select { |d| #filtering goes here }
  end
end

すべてのフィルターを適用してチェーンするには、フィルター ブロックを Enumerable に渡すにはどうすればよいでしょうか? 私はそのようなことができることを知っています:

data.select { |d| by_param1 && by_param2 }

でも私の場合はフィルターがたくさんあるのでエレガントではありません

解決策

ここで #reduce を使用すると、大きな効果が得られます。

例えば、

filter_1 = -> (thing) { thing > 2 }

filter_2 = -> (thing) { thing < 5 }

def better_filter(input)
  input * 7 < 23
end

filters = [filter_1, filter_2, method(:better_filter), :positive?]

array = (0..9).to_a

filters.reduce(array) { |result, filter| result.select(&filter) }

# [3]

あなたの質問の文脈では、次のようになります。

class QueryObject
  def call(filters)
    data = get_data # returns array 
    filters.reduce(data) { |result, filter| result.select(&filter) }
  end
end

これは、フィルターが #call に応答するオブジェクトの配列として渡されることを前提としています。例で示したように、それはラムダ、単純な Ruby クラス、さらにはシンボルである可能性があります。