Ruby on Rails3でSQLのLike検索を行う場合、いろいろと参考になるサイトがあります。けれども、複数の列でLike検索を行いたい場合については見つかりませんでした。まぁそんな事はしないという話かもしれませんが…。プラグインも見つけられなかったので試しに作ってみました。
Like検索のクラスだけ取り出すのが面倒なので、moduleすべて載せます。
[sourcecode language="ruby"]
coding: utf-8
#
検索条件を生成するユーティリティモジュールです。
#
Author : ymanabe
Create Date: 11/11/03
Update Date: 11/11/03
#
== Summery
#
== Remarks
なし
# module ConditionUtil private
# 検索条件用のハッシュを生成します。 # # ==引数 # hash:検索条件用のハッシュ # key:ハッシュにセットするキー # value:ハッシュにセットする値 # ただし、範囲検索の場合は範囲検索のFROM # range_to:範囲検索のTO # # ==概要 # 検索条件のハッシュ値を生成しますが。 # def add_item_to_hash(hash, key, value, range_to = nil, table_name = nil)
if hash.nil?
raise ArgumentError, 'param [hash] is nil.'
end
if key.nil?
raise ArgumentError, ' param [key] is nil.'
end
if !key.kind_of?(Symbol) && !key.kind_of?(String)
raise ArgumentError, 'param [key] is not kind of Symbol or String.'
end
if range_to.nil?
if !key.nil? && !value.to_s.empty?
if !table_name.nil? && !table_name.empty?
hash_key = "#{table_name}.#{key}"
else
hash_key = key
end
if !hash.key? hash_key
hash[hash_key] = value
Rails.logger.debug "the hash add item. key:" + hash_key.to_s + " / value:" + hash[hash_key].to_s
end
end
else
if !key.nil? && !value.to_s.empty?
if !table_name.nil? && !table_name.empty?
hash_key = "#{table_name}.#{value}"
else
hash_key = key
end
if !hash.key? hash_key
hash[hash_key] = value .. range_to
Rails.logger.debug "the hash add item. key:" + hash_key.to_s + " / value:" + hash[hash_key].to_s
end
end
end
end
# SqlLikeConditionクラス # # == 概要 # SQLのLike検索条件とハッシュ値に対応したクラスです。add_condition メソッドへ # 検索条件の構成情報をセットすることでLike検索を生成します。 class SqlLikeCondition
# 検索条件
@condition
# プレースフォルダと値のハッシュ値
@data_hash = Hash.new
def initialize
@condition = ""
@data_hash = Hash.new
end
# これ以降 public スコープとする
public
# 全文検索
MATCH_PATTERN_FULL = 1
# 前方一致検索
MATCH_PATTERN_FORWARD = 2
# 検索条件文字列
# add_conditions メソッドで生成した検索条件の文字列
def condition
return @condition
end
# プレースフォルダと値のハッシュ
# add_conditions メソッドで生成した検索条件の文字列のプレースフォルダと値のセットを保持します
def data_hash
return @data_hash
end
# 条件の追加
# == 引数
# table_name テーブル名
# col_name 対象となる列名
# value 対象となる値
# match_pattern 全文検索または前方一致検索を指定
def add_condition(table_name, col_name, value, match_pattern)
if table_name.nil?
raise ArgumentError, ' param [table_name] is nil.'
end
if table_name.empty?
raise ArgumentError, ' param [table_name] is empty.'
end
if col_name.nil?
raise ArgumentError, ' param [col_name] is nil.'
end
if !col_name.kind_of?(Symbol) && !col_name.kind_of?(String)
raise ArgumentError, 'param [col_name] is not kind of Symbol or String.'
end
if match_pattern.nil?
raise ArgumentError, 'param [match_pattern] is nil.'
end
# 1または2以外の場合はエラーとする
if match_pattern != MATCH_PATTERN_FULL && match_pattern != MATCH_PATTERN_FORWARD
raise ArgumentError, "param [match_patten:#{match_pattern}] is out of range."
end
# 検索条件を作成する(table_name.colname like %col_name%)
@condition += ((@condition.empty?)? "" : " AND") + " #{table_name}.#{col_name.to_s} LIKE " + ":#{col_name.to_s}"
if !@data_hash.key? col_name.intern
@data_hash[col_name.intern] = ((match_pattern == MATCH_PATTERN_FULL) ? "%#{value}%" : "#{value}%")
Rails.logger.debug @data_hash[col_name.intern]
end
end
end end [/sourcecode]
次に使用方法です。
[sourcecode language="ruby"]
condition_hash = Hash.new
slc = SqlLikeCondition.new
if !params.nil?
himoku_code = params[:himoku_code]
tekiyo = params[:memo]
pay_code = params[:payment_code]
shiharai_saki = params[:shiharai_saki]
shiharaibi_from = get_date('shiharaibi_from')
shiharaibi_to = get_date('shiharaibi_to')
# 費目IDが0の場合は指定なしのため選択しない(-.-)y-゜゜゜
if himoku_code != "0"
add_item_to_hash condition_hash, :himoku_code, himoku_code, nil, :himokus
end
# 適用は前方一致検索
if !tekiyo.nil? && !tekiyo.empty?
slc.add_condition "ledgers", :tekiyo, tekiyo, SqlLikeCondition::MATCH_PATTERN_FORWARD
#add_item_like_to_hash condition, :tekiyo, tekiyo, 2
end
# 支払方法IDが0の場合は指定なしのため選択しない(゜゜;)エエッ
if pay_code != "0"
add_item_to_hash condition_hash, :pay_code, pay_code, nil, :payments
end
# 支払先は前方一致検索
if !shiharai_saki.nil? && !shiharai_saki.empty?
slc.add_condition :ledgers, :shiharai_saki, shiharai_saki, SqlLikeCondition::MATCH_PATTERN_FORWARD
#add_item_like_to_hash condition, :shiharai_saki, shiharai_saki, 2
end
add_item_to_hash condition_hash, :spent_date, shiharaibi_from.to_s, shiharaibi_to.to_s
end
@ledgers = Ledger.joins('INNER JOIN himokus ON himokus.id = ledgers.himoku_id')
.joins('INNER JOIN payments ON payments.id = ledgers.pay_id')
.select('ledgers.*, himokus.himoku_code, himokus.himoku_name, payments.pay_name')
.where(condition_hash).where(slc.condition,slc.data_hash).where("ledgers.del_flg = 0")
.paginate(:page => params[:page],:per_page=>9)
[/sourcecode]
使用している内容は作っている最中の家計簿なので適当に想像してみてください。単純に扱っているのは、下記のwhereメソッドでハッシュまたはハッシュと検索条件文字列を渡すことで検索条件が生成されます。
[sourcecode language="ruby"] @ledgers = Ledger.joins('INNER JOIN himokus ON himokus.id = ledgers.himoku_id') .joins('INNER JOIN payments ON payments.id = ledgers.pay_id') .select('ledgers.*, himokus.himoku_code, himokus.himoku_name, payments.pay_name') .where(condition_hash).where(slc.condition,slc.data_hash).where("ledgers.del_flg = 0") .paginate(:page => params[:page],:per_page=>9) [/sourcecode]
もし、同じようなことで悩んでいる人がいたら参考になればと思います。また、内容について改善点や間違いなどwelcomです!
※ちなみにテストコードはこちらです。
[sourcecode language="ruby"]
coding: utf-8
require 'test_helper'
検索条件生成ユーティリティモジュールのテストクラス
Author : ymanabe
Create Date: 11/11/03
Update Date: 11/11/03
class ConditionUtilTest < ActiveSupport::TestCase include ConditionUtil
# add_itemのパラメータが正常に終了 test "add_item_to_hash normal" do
# 正常に処理が行われるパラメータを作成
result = Hash.new
key = 'key'
value = 'value'
# 処理の実行
add_item_to_hash result, key, value
assert_equal 1, result.size, 'Hashに保存された件数は1件'
assert_true result.has_key?(key), "key:#{key}が含まれない"
assert_true result.has_value?(value), "値:#{value}が含まれない"
# 同一のデータをセットする場合は何も行われない
add_item_to_hash result, key, value
assert_equal 1, result.size, 'Hashに保存された件数は1件'
assert_true result.has_key?(key), "キー値:#{key}が含まれない"
assert_true result.has_value?(value), "値:#{value}が含まれない"
# 異なるデータをセットする場合は正常に登録される
key = 'key2'
value = 'value2'
add_item_to_hash result, key, value
assert_equal 2, result.size, 'Hashに保存された件数は2件'
assert_true result.has_key?(key), "キー値:#{key}が含まれない"
assert_true result.has_value?(value), "値:#{value}が含まれない"
# 引数を2つ設定する場合は範囲指定文字列を値としてセットする
key = 'key3'
value = 'value_from'
value2 = 'value_to'
add_item_to_hash result, key, value, value2
assert_equal 3, result.size, 'Hashに保存された件数は3件'
assert_true result.has_key?(key), "キー値:#{key}が含まれない"
# 範囲指定オブジェクトを作成する
range_string = "#{value}".."#{value2}"
assert_true result.has_value?(range_string), "範囲:#{range_string}が含まれない"
end
# add_itemのパラメータが正常に終了 test "add_item_to_hash normal with table_name" do
# 正常に処理が行われるパラメータを作成
result = Hash.new
key = 'key'
value = 'value'
table_name = "table_name"
hash_key = "#{table_name}.#{key}"
# 処理の実行
add_item_to_hash result, key, value, nil, table_name
assert_equal 1, result.size, 'Hashに保存された件数は1件'
assert_true result.has_key?(hash_key), "キー値:#{hash_key}が含まれない"
assert_true result.has_value?(value), "値:#{value}が含まれない"
# 同一のデータをセットする場合は何も行われない
add_item_to_hash result, key, value, nil, table_name
assert_equal 1, result.size, 'Hashに保存された件数は1件'
assert_true result.has_key?(hash_key), "キー値:#{hash_key}が含まれない"
assert_true result.has_value?(value), "値:#{value}が含まれない"
# 異なるデータをセットする場合は正常に登録される
key = 'key2'
value = 'value2'
hash_key2 = "#{table_name}.#{key}"
add_item_to_hash result, key, value, nil, table_name
assert_equal 2, result.size, 'Hashに保存された件数は2件'
assert_true result.has_key?(hash_key2), "キー値:#{hash_key2}が含まれない"
assert_true result.has_value?(value), "値:#{value}が含まれない"
# 引数を2つ設定する場合は範囲指定文字列を値としてセットする
key = 'key3'
value = 'value_from'
value2 = 'value_to'
hash_key3 = "#{table_name}.#{key}"
add_item_to_hash result, key, value, value2, table_name
assert_equal 3, result.size, 'Hashに保存された件数は3件'
assert_true result.has_key?(hash_key2), "キー値:#{hash_key2}が含まれない"
# 範囲指定オブジェクトを作成する
range_string = "#{value}".."#{value2}"
assert_true result.has_value?(range_string), "範囲:#{range_string}が含まれない"
end
# add_itemのパラメータがnil test "add_item_to_hash param is nil" do
# 追加するハッシュがnilの場合は例外がスローされることを確認する
assert_raise(ArgumentError){add_item_to_hash(nil,nil,nil)}
# キーがnilの場合は例外がスローされることを確認する
assert_raise(ArgumentError){ add_item_to_hash(Hash.new,nil,nil) }
# キーがSymbolまたはStringでない場合は例外がスローされることを確認する
assert_raise(ArgumentError){ add_item_to_hash(Hash.new, 1, nil)}
end
test "SqlLikeCondition with string params one column and match pattern full" do
# インスタンスが生成できることを確認する
slc = SqlLikeCondition.new
assert_false slc.nil?, "SqlLikeCondition is not instanced."
# 条件の生成
data_value_foo = 'foo_value'
data_col_name = 'fuga_col_name'
table_name = 'hoge_table'
slc.add_condition(table_name, data_col_name, data_value_foo, SqlLikeCondition::MATCH_PATTERN_FULL)
assert_not_nil(slc.data_hash, 'slc.data_hash is nil.')
assert_not_nil(slc.condition, 'slc.condition is nil.')
assert_not_empty(slc.condition, 'slc.condition is empty.')
# 検索条件の一致
EXP_CONDTION = " hoge_table.fuga_col_name LIKE :#{data_col_name}"
assert_equal EXP_CONDTION, slc.condition, 'slc.condition dose not make condition.'
# 検索条件の値の一致
assert_true slc.data_hash.key?(data_col_name.intern), "data_hash dose not include key #{data_col_name}"
assert_true slc.data_hash.value?("%#{data_value_foo}%"), "data_hash dose not include value #{data_value_foo}"
end
test "SqlLikeCondition with string params one column and match pattern forward" do
# インスタンスが生成できることを確認する
slc = SqlLikeCondition.new
assert_false slc.nil?, "SqlLikeCondition is not instanced."
# 条件の生成
data_value_foo = 'foo_value'
data_col_name = 'fuga_col_name'
table_name = 'hoge_table'
slc.add_condition(table_name, data_col_name, data_value_foo, SqlLikeCondition::MATCH_PATTERN_FORWARD)
assert_not_nil(slc.data_hash, 'slc.data_hash is nil.')
assert_not_nil(slc.condition, 'slc.condition is nil.')
assert_not_empty(slc.condition, 'slc.condition is empty.')
# 検索条件の一致
EXP_CONDTION = " hoge_table.fuga_col_name LIKE :#{data_col_name}"
assert_equal EXP_CONDTION, slc.condition, 'slc.condition dose not make condition.'
# 検索条件の値の一致
assert_true slc.data_hash.key?(data_col_name.intern), "data_hash dose not include key #{data_value_foo}"
assert_true slc.data_hash.value?("#{data_value_foo}%"), "data_hash dose not include value #{data_value_foo}"
end
test "SqlLikeCondition with intern params one column" do
# インスタンスが生成できることを確認する
slc = SqlLikeCondition.new
assert_false slc.nil?, "SqlLikeCondition is not instanced."
# 条件の生成
data_value_foo = 'foo_value'
data_col_name = 'fuga_col_name'.intern
table_name = 'hoge_table'
slc.add_condition(table_name, data_col_name, data_value_foo, SqlLikeCondition::MATCH_PATTERN_FULL)
assert_not_nil(slc.data_hash, 'slc.data_hash is nil.')
assert_not_nil(slc.condition, 'slc.condition is nil.')
assert_not_empty(slc.condition, 'slc.condition is empty.')
# 検索条件の一致
EXP_CONDTION = " hoge_table.fuga_col_name LIKE :#{data_col_name}"
assert_equal EXP_CONDTION, slc.condition, 'slc.condition dose not make condition.'
# 検索条件の値の一致
assert_true slc.data_hash.key?(data_col_name.intern), "data_hash dose not include key #{data_value_foo}"
assert_true slc.data_hash.value?("%#{data_value_foo}%"), "data_hash dose not include value #{data_value_foo}"
end
test "SqlLikeCondition with intern params many columns" do
# インスタンスが生成できることを確認する
slc = SqlLikeCondition.new
assert_false slc.nil?, "SqlLikeCondition is not instanced."
# ========================================================
# 条件の生成
# ========================================================
# 1つめのデータ
data_value_foo = 'foo_value'
data_col_name_foo = 'foo_col_name'.intern
table_name_foo = 'foo_table'
# 2つめのデータ
data_value_bar = 'bar_value'
data_col_name_bar = 'fuga_col_name'.intern
table_name_bar = 'hoge_table'
slc.add_condition(table_name_foo, data_col_name_foo, data_value_foo, SqlLikeCondition::MATCH_PATTERN_FULL)
slc.add_condition(table_name_bar, data_col_name_bar, data_value_bar, SqlLikeCondition::MATCH_PATTERN_FULL)
assert_not_nil(slc.data_hash, 'slc.data_hash is nil.')
assert_not_nil(slc.condition, 'slc.condition is nil.')
assert_not_empty(slc.condition, 'slc.condition is empty.')
# 検索条件の一致
EXP_CONDTION = " #{table_name_foo}.#{data_col_name_foo} LIKE :#{data_col_name_foo} "\
"AND #{table_name_bar}.#{data_col_name_bar} LIKE :#{data_col_name_bar}"
assert_equal EXP_CONDTION, slc.condition, 'slc.condition dose not make condition.'
# 検索条件の値の一致
# 1つめのデータが存在する
assert_true slc.data_hash.key?(data_col_name_foo.intern), "data_hash dose not include key #{data_value_foo}"
assert_true slc.data_hash.value?("%#{data_value_foo}%"), "data_hash dose not include value #{data_value_foo}"
# 2つめのデータが存在する
assert_true slc.data_hash.key?(data_col_name_bar.intern), "data_hash dose not include key #{data_value_bar}"
assert_true slc.data_hash.value?("%#{data_value_bar}%"), "data_hash dose not include value #{data_value_bar}"
end end [/sourcecode]