Skip to content

Instantly share code, notes, and snippets.

@pinzolo
Created December 10, 2015 06:02
Show Gist options
  • Save pinzolo/3ec384302b0539446281 to your computer and use it in GitHub Desktop.
Save pinzolo/3ec384302b0539446281 to your computer and use it in GitHub Desktop.
Arel を使って複数テーブルから or 検索
# こんなテーブルがあるとします
create_table :users do |t|
t.string :name
t.string :email
end
create_table :belongings do |t|
t.integer :user_id
t.string :company_name
t.string :dept_name
end
# 1つのキーワードであらゆる属性をあいまい検索したい場合、Arelを使えばこう書けます
# left join も Arel で書けますが、冗長なので eager_load で代用してます
match_key = "%#{keyword}%"
name_matches = User.arel_table[:name].matches(match_key)
email_matches = User.arel_table[:email].matches(match_key)
company_name_matches = Belonging.arel_table[:company_name].matches(match_key)
dept_name_matches = Belonging.arel_table[:dept_name].matches(match_key)
User.eager_load(:belonging).where(name_matches.or(email_matches).or(company_name_matches).or(dept_name_matches))
# or のメソッドチェーンがなんかやだね。こうすれば対応カラムが増えた時 matchers の要素を増やすだけで済むぞ
match_key = "%#{keyword}%"
matchers = [User.arel_table[:name].matches(match_key),
User.arel_table[:email].matches(match_key),
Belonging.arel_table[:company_name].matches(match_key),
Belonging.arel_table[:dept_name].matches(match_key)]
condition = matchers.inject { |cond, matcher| cond.or(matcher) }
User.eager_load(:belonging).where(condition)
# これならシンプルに inject(&:sym) でいけるね
match_key = "%#{keyword}%"
matchers = [User.arel_table[:name].matches(match_key),
User.arel_table[:email].matches(match_key),
Belonging.arel_table[:company_name].matches(match_key),
Belonging.arel_table[:dept_name].matches(match_key)]
User.eager_load(:belonging).where(matchers.inject(&:or))
# matchers も定型的なので冗長だよね
class_columns_set = { User => [:name, :email], Belonging => [:company_name, :dept_name] }
matchers = class_columns_set.map do |cls, cols|
cols.map { |col| cls.arel_table[col].matches("%#{keyword}%") }
end.flatten
User.eager_load(:belonging).where(matchers.inject(&:or))
# なんかシンプルなパターンなら切り出せそう
class SimpleFinder
def initialize(base_class, class_columns_set)
@base_class = base_class
@class_columns_set = class_columns_set
end
def find(keyword)
matchers = @class_columns_set.map do |cls, cols|
cols.map { |col| cls.arel_table[col].matches("%#{keyword}%") }
end.flatten
foreign_tables = @class_columns_set.keys.reject { |cls| cls == @base_class }.map(&:table_name)
@base_class.eager_load(*foreign_tables).where(matchers.inject(&:or))
end
end
SimpleFinder.new(User, User => [:name, :email], Belonging => [:company_name, :dept_name]).find('foobar')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment