Rails3になってからArelが導入されてnamed_scopeが便利だなぁ〜と思いつつも、where条件をORで接続するにはどうすんだ?って ことで調べてみた。

■今回の要件
1.Employeeテーブルから名前(漢字)で検索できる。
2.Employeeテーブルから名前(カナ)で検索できる。
3.カナでも漢字でも検索できる。
4.検索対象フィールド(姓、名、セイ、メイ)を動的に指定できる。

例えばEmployeesテーブルが以下のよう場合

class CreateEmployees < ActiveRecord::Migration
def self.up
create_table :employees do |t|
t.string :family_name, :null => false
t.string :first_name, :null => false
t.string :family_name_kana, :null => false
t.string :first_name_kana, :null => false
t.timestamps
end
end

def self.down
drop_table :employees
end
end

このテーブルに対して漢字orカタカナで名前を検索したい場合

name = "マエジマ"
Employee.where(:family_name => name).where(:first_name => name).to_sql
#=> "SELECT `employees`.* FROM `employees` WHERE `employees`.`family_name` = 'マエジマ' AND `employees`.`first_name` = 'マエジマ'"

上記の様に普通のメソッドチェーンで書いた場合はANDなのでダメ。
ORで検索したい場合の方法としてRails2のconditions風に書く事もできる。

Employee.where(["family_name = ? or family_name_kana = ?", name, name]).to_sql
#=> "SELECT `employees`.* FROM `employees` WHERE (family_name = 'マエジマ' or family_name_kana = 'マエジマ')"

しかし、これでは条件を動的に指定きない。(要件4)
Rails3風は以下のように書ける。
arel_table = Employee.arel_table
Employee.where(arel_table[:family_name].eq(name).or(arel_table[:family_name_kana].eq(name))).to_sql
#=>"SELECT `employees`.* FROM `employees` WHERE ((`employees`.`family_name` = 'マエジマ' OR `employees`.`family_name_kana` = 'マエジマ'))"


う〜ん、なんか分かりにくいコードになってしまってるような...
で、こちらで動的にOR条件を追加できるようにします。
例えばfamily_nameだけで検索したいとか、first_nameとfirst_name_kanaも検索対象に入れたい場合。

例:検索対象のフィールドがtargets配列で指定する場合
fields = [:family_name, :family_name_kana]

conditions = nil
fields.each do |field|
if conditions.nil?
conditions = arel_table[field].eq(name)
else
conditions = conditions.or(arel_table[field].eq(name))
end
end

Employee.where(conditions).to_sql
#=>"SELECT `employees`.* FROM `employees` WHERE ((`employees`.`family_name` = 'マエジマ' OR `employees`.`family_name_kana` = 'マエジマ'))"

これをコントローラーにダラダラ書けないのでわかりやくすモデルのscopeを作りま す。
class Employee < ActiveRecord::Base
scope :search_for_name, lambda { |fields, name|
conditions = nil
fields = [fields] if fields.class == Symbol
fields.each do |field|
if conditions.nil?
conditions = arel_table[field].eq(name)
else
conditions = conditions.or(arel_table[field].eq(name))
end
end
where(conditions)
}
end

これで以下のよう書けます。

Employee.search_for_name([:family_name, :family_name_kana], "マエジマ").to_sql
#=>"SELECT `employees`.* FROM `employees` WHERE ((`employees`.`family_name` = 'マエジマ' OR `employees`.`family_name_kana` = 'マエジマ'))"

すっきりしたコードになりました
言語を検出 » Japanese

も同時に
言語を検出 » Japanese