ActiverecordのためのCriteriaをつくる

Rubyクックブックのレシピを参照にして(半分真似して)、Activerecordのfindメソッド用のまいCriteriaをつくってみました。

クラスの実装

以下のような実装にしました。
ANDやORがネストしたものも対応できるよう、クライテリアがネストした構造をとれるよう実装しました。

# Activerecordのfindメソッドのに条件として渡す配列を生成するためのクライテリア
class Criteria 

  # 個々の条件を格納
  class Condition

    attr_accessor :field, :value, :operation

    def initialize(field, value, operation)
      @field = field
      @value = value
      @operation = operation
    end

  end

  def initialize()
    @nested_criteria = nil
    @conditions = []
  end

  public
  
  attr_reader   :operation,:nested_criteria,:conditions

  protected

  attr_writer  :operation

  public
  
  # 条件を追加,selfを返す
  def add(field, value = nil, operation = '=')
    @conditions << Condition.new(field, value, operation)
    self
  end

  # 他のクライテリアをANDで連結,selfを返す
  def and(criteria)
    @nested_criteria ||= [] 
    criteria.operation = 'AND'
    @nested_criteria << criteria
    self
  end

  # 他のクライテリアをORで連結,selfを返す
  def or(criteria)
    @nested_criteria ||= [] 
    criteria.operation = 'OR'
    @nested_criteria << criteria
    self
  end

  # findに渡す条件配列を返す。
  # [sql文, 値, 値...]という形式の配列となる。
  def to_where_clause
    sql = []
    values = []
    @conditions.each do |condition|
      if  condition.value.is_a?(Array)  then
        sql << '(' + condition.to_str + ')'
      elsif condition.value.nil?
        if condition.operation then
          sql << "#{condition.field} #{condition.operation} "
        end
      else  
        sql << condition.to_str
      end
      values << condition.value if condition.value
    end
    sql =  sql.join(' AND ') 
    if nested_criteria then
      nested_criteria.each do |c|
        nested_where =   c.to_where_clause
        logic_operation = c.operation
        sql = "( #{sql} ) #{logic_operation} #{nested_where.shift }"
        values += nested_where
      end
    end
    values.unshift(sql)
  end

end

使い方

以下のメソッドを使います。

add(field, value, operatin)

条件を追加。テーブルのカラム(フィールド)名、カラム値、演算子(=,> ..など。省略すると=) を指定します。また、valueに配列を指定するとIN句を生成することになります。

and(criteira)

他のクライテリアとAND条件を作る。

or(criteria)

他のクライテリアとOR条件を作る。

to_where_clause

結果を配列で返す。0個目の要素が置換付きの条件句で1個目の要素が置換変数の値の配列です。

テスト

以下のようにして簡単なテストをしてみる。

criteria = Criteria.new
criteria.add('sex', 'female' )
criteria.add('city', 'osaka', '=' )

criteria2 = Criteria.new
criteria2.add('age', [21,22,23] , 'IN')
criteria2.add('from', 'tokyo', '=' )

criteria3 = Criteria.new
criteria3.add('sex' , nil, 'IS NULL' )
criteria3.add('city', nil, 'IS NOT NULL' )

criteria.and(criteria2).or(criteria3)
p criteria.to_where_clause

でとりあえず、こんなかんじでできている。

["( ( sex = ? AND city = ? ) AND (age IN ?) AND from = ? ) OR sex IS NULL  AND city IS NOT NULL ", "female", "osaka", [21, 22, 23], "tokyo"]

まだ、実際にActiverecordに適用していませんが..