どうもお久しぶりです。
morimotoです。
今日は複雑なActiveRecordの条件句を作成する際に(もしかしたら)楽になるのではないかと思いconditionを生成するクラスを作ってみました。
class ARCond
module OPERATION
EQUAL = '='
NOT_EQUAL = '<>'
IN = 'IN'
GREATER = '>'
GREATER_OR_EQUAL = '>='
LESS = '< '
LESS_OR_EQUAL = '<='
end
attr_reader :conditions, :parameters, :separator, :table_name
def initialize(separator="AND", table_name=nil)
@conditions = []
@parameters = []
@separator = separator
@table_name = table_name
end
# column = ?
def equal(column, value, table=nil)
add(column, value, OPERATION::EQUAL, table)
end
# column <> ?
def not_equal(column, value, table=nil)
add(column, value, OPERATION::NOT_EQUAL, table)
end
# column IN (?,?,?...)
def in(column, value, table=nil)
add(column, value, OPERATION::IN, table)
end
# column > ?
def greater(column, value, table=nil)
add(column, value, OPERATION::GREATER, table)
end
# column >= ?
def greater_or_equal(column, value, table=nil)
add(column, value, OPERATION::GREATER_OR_EQUAL, table)
end
# column < ?
def less(column, value, table=nil)
add(column, value, OPERATION::LESS, table)
end
# column <= ?
def less_or_equal(column, value, table=nil)
add(column, value, OPERATION::LESS_OR_EQUAL, table)
end
def << (other)
cond = nil
param = []
if other.kind_of? ARCond
c = other.build
cond = c.delete_at(0)
param = c
elsif other.kind_of? String
cond = other
elsif other.kind_of? Array
cond = other.delete_at(0)
param = other
end
unless cond.blank?
@conditions << cond
@parameters.concat(param)
end
return self.build
end
# [conditions, param, param, param,...]
def build
[join_conditions].concat(@parameters)
end
private
def join_conditions
sep = " #{@separator} "
"(#{@conditions.join(sep)})"
end
def column_fullname(column, table=nil)
unless column =~ /^[\w]+\.[\w]+$/
if table_name = table.blank? ? @table_name : table
return "#{table_name}.#{column}"
end
end
column
end
def add(column, value, operation, table=nil)
parts = [column_fullname(column, table), operation]
param = [value].flatten
parts <<1", true, nil, 31, 32, 33]
# 連結文字を変更する
c2 = ARCond.new("OR")
# 指定した連結文字で結合されていく
c2.greater("col4", 100)
# => ["(col4 > ?)", 100]
c2.less("col4", 200)
# => ["(col4 > ? OR col4 < ?)", 100, 200]
# 配列みたいに追加していく
c1 << c2
# => ["(col1 = ? AND col2 <> ? AND col3 IN (?,?,?) AND (col4 > ? OR col4 < ?))", true, nil, 31, 32, 33, 100, 200]
# 引数なしはANDで連結
c3 = ARCond.new()
# いつもどおりの配列の conditions もくっつけれる
c3 << ["col5 = ? AND col6 IN (?,?)", 11,22,33]
# => ["(col5 = ? AND col6 IN (?,?))", 11, 22, 33]
# 任意文字列もくっつけれる
c3 < < "col7 > col8"
# => ["(col5 = ? AND col6 IN (?,?) AND col7 > col8)", 11, 22, 33]
# テーブル名を指定する
c4 = ARCond.new("OR", "sample")
# カラム名の頭にテーブル名が付与される
c4.greater_or_equal("col9", 300)
# => ["(sample.col9 >= ?)", 300]
c4.less_or_equal("col9", 400)
# => ["(sample.col9 >= ? OR sample.col9 < = ?)", 300, 400]
# くっつけるときにテーブル名を指定する
c4.equal("col10", false, "banana")
# => ["(sample.col9 >= ? OR sample.col9 < = ? OR banana.col10 = ?)", 300, 400, false]
c3 << c4
# => ["(col5 = ? AND col6 IN (?,?) AND col7 > col8 AND (sample.col9 >= ? OR sample.col9 < = ? OR banana.col10 = ?))", 11, 22, 33, 300, 400, false]
c1 << c3
# => ["(col1 = ? AND col2 <> ? AND col3 IN (?,?,?) AND (col4 > ? OR col4 < ?) AND (col5 = ? AND col6 IN (?,?) AND col7 > col8 AND (sample.col9 >= ? OR sample.col9 < = ? OR banana.col10 = ?)))", true, nil, 31, 32, 33, 100, 200, 11, 22, 33, 300, 400, false]
# 最終的に使いたいタイミングで build してあげたらよい
c1.build
# => ["(col1 = ? AND col2 <> ? AND col3 IN (?,?,?) AND (col4 > ? OR col4 < ?) AND (col5 = ? AND col6 IN (?,?) AND col7 > col8 AND (sample.col9 >= ? OR sample.col9 < = ? OR banana.col10 = ?)))", true, nil, 31, 32, 33, 100, 200, 11, 22, 33, 300, 400, false]
作っておいてなんですが、テーブルが複数あったり、集計クエリが絡んできたりと、複雑になればなるほど自分でwhere句を書いたほうが早い気がします。
以上、morimoto でした。
- operation == OPERATION::IN) ? "(#{param.map{'?'}.join(',')})" : "?")
self < < [parts.join(' ')].concat(param)
end
end
使い方の例)
# 基本スタイル c1 = ARCond.new("AND") c1.equal("col1", true) # => ["(col1 = ?)", true] c1.not_equal("col2", nil) # => ["(col1 = ? AND col2 <> ?)", true, nil] c1.in("col3", [31,32,33]) # => ["(col1 = ? AND col2 <> ? AND col3 IN (?,?,? [↩]