🐙
Administrate gemのキーワード検索はカラムごとにOR検索だった
administrateのデモアプリで実装されている検索機能はOR検索。また、複数語指定の場合はすべての語句をいずれかのカラムが含む検索となっている。
具体的なSQLのクエリ構築箇所は、以下。
query_template
メソッドで各attributesをループしてORのSQLクエリを構築している。
def query_template
search_attributes.map do |attr|
table_name = query_table_name(attr)
searchable_fields(attr).map do |field|
column_name = column_to_query(field)
"LOWER(CAST(#{table_name}.#{column_name} AS CHAR(256))) LIKE ?"
end.join(" OR ")
end.join(" OR ")
end
OR検索を検証してみる。複数語指定の場合はすべての語句が含まれていることが検索条件。
# query.rb
require 'active_record'
require 'sqlite3'
# ロギング
ActiveRecord::Base.logger = Logger.new($stdout)
# データベース接続の設定
ActiveRecord::Base.establish_connection(
adapter: 'sqlite3',
database: 'db/development.sqlite3'
)
# テーブルの作成
ActiveRecord::Schema.define do
drop_table :customers, if_exists: true
end
ActiveRecord::Schema.define do
create_table :customers do |t|
t.string :name, null: false
t.string :email, null: false
t.string :phone
t.text :notes
t.boolean :active, default: true, null: false
t.timestamps
t.index [:email], name: "index_customers_on_email", unique: true
end
end
class Customer < ActiveRecord::Base
SEARCHABLE_COLUMNS = [:name, :email, :phone, :notes]
def self.search_template
SEARCHABLE_COLUMNS.map do |column_name|
"LOWER(CAST(#{column_name} AS CHAR(256))) LIKE ?"
end.join(' OR ')
end
def self.query_values(query)
["%#{query.downcase}%"] * SEARCHABLE_COLUMNS.length
end
def self.search(query)
where(search_template, *query_values(query))
end
end
Customer.create(name: 'Alice', email: 'alice@example.com', phone: '123-4567', notes: 'This is Alice.')
Customer.create(name: 'Bob', email: 'bob@example.com', phone: '123-4567', notes: 'This is Bob.')
puts Customer.search('alice').count
puts Customer.search('alice bob').count
検索結果は以下の通り。
puts Customer.search('alice').count
=> 1
puts Customer.search('alice bob').count
=> 0
複数語のいずれかが含まれていれば検索対象となるようにORクエリを語句分生成するように修正する。
class Customer < ActiveRecord::Base
SEARCHABLE_COLUMNS = [:name, :email, :phone, :notes]
def self.search_template(query)
query.split.flat_map do |word|
SEARCHABLE_COLUMNS.map do |column_name|
"LOWER(CAST(#{column_name} AS CHAR(256))) LIKE ?"
end.join(' OR ')
end.join(' OR ')
end
def self.query_values(query)
query.split.flat_map do |word|
["%#{word.downcase}%"] * SEARCHABLE_COLUMNS.length
end
end
def self.search(query)
where(search_template(query), *query_values(query))
end
end
Customer.create(name: 'Alice', email: 'alice@example.com', phone: '123-4567', notes: 'This is Alice.')
Customer.create(name: 'Bob', email: 'bob@example.com', phone: '123-4567', notes: 'This is Bob.')
puts Customer.search('alice').count
puts Customer.search('alice bob').count
puts Customer.search('alice').count
=> 1
puts Customer.search('alice bob').count
=> 2
参考
デモアプリソースコードは
に配置されている。
Discussion