LoginSignup
17
10

More than 3 years have passed since last update.

ActiveRecordで複数属性を LIKE 検索したい

Last updated at Posted at 2020-04-29

ActiveRecord は素晴らしいライブラリです。しかし、現実は複雑なデータを取り扱うため、どうしても ActiveRecord 単体では辛くなる時が必ずあります。

この記事を書くきっかけもそんな一幕から始まりました。

□ 目的

users テーブルの nameemailで LIKE 検索がしたい!

□ 前提情報( users table )

column_name data_type
id integer
name string
email string
created_at datetime
updated_at datetime

□ (初めに)単一属性の LIKE 検索

これは、直感的に理解しやすいかと思います。

# email であいまい検索する場合
scope :search_email, ->(params) do
  where("email LIKE ?", "%#{params}%")
end

# 検索する属性を動的に変更する場合
scope :search_column, ->(column, params) do
  where("#{column} LIKE ?", "%#{params}%")
end

□ 複数属性の LIKE 検索( or 使用 )

ActiveRecord にはorメソッドがあるので、下記の通りでも実現できます。
けど、これでは同じコードを繰り返して不穏な感じがしますね...

scope :search_columns, ->(params) do
  where("name LIKE ?", "%#{params}%").
    or(where("email LIKE ?", "%#{params}%"))
end

□ 複数属性の LIKE 検索( Arel 使用 )

複雑な検索には、Arel を使用するケースがあるそうです。
Arel とは、普段使用する ActiveRecord のメソッドを内部で良い感じに変換する用途で使用されます。

# 単一属性で検索したい場合
scope :search_column, ->(column, params) do
  where(arel_table[column].matches("%#{params}%"))
end

# 複数属性で検索したい場合( columns に配列で属性を渡して複数条件の OR 検索を実現 )
scope :search_columns, ->(columns, params) do
  search_word = "%#{params}%"
  conditon = nil
  columns.each do |column|
    condition = condition.nil? ? arel_table[column].matches(search_word) : condition.or(arel_table[column].matches(search_word))
  end

  where(condition)
end
  • ↓ を見ると matches 以外にもメソッドがあるため、色々な検索方法が出来そうですね。
rails/activerecord/lib/arel/predications.rb
def matches(other, escape = nil, case_sensitive = false)
  Nodes::Matches.new self, quoted_node(other), escape, case_sensitive
end

def matches_regexp(other, case_sensitive = true)
  Nodes::Regexp.new self, quoted_node(other), case_sensitive
end

def matches_any(others, escape = nil, case_sensitive = false)
  grouping_any :matches, others, escape, case_sensitive
end

def matches_all(others, escape = nil, case_sensitive = false)
  grouping_all :matches, others, escape, case_sensitive
end

□ 複数属性の LIKE 検索( SQL 使用 )

ここまで書きましたが、Arel は使わない方が良いです。

複雑になると直感的に理解しづらい感じがします...やはり広く知られている SQL を使用して、実装しましょう。

## Case 1 (Array が返り値)
scope :search_columns, ->(params) do
  find_by_sql([<<-SQL, "%#{params}%", "%#{params}%"])
    SELECT
      *
    FROM
      users
    WHERE
      email LIKE ?
      OR name LIKE ?
    SQL
end

## Case 2 (Array が返り値)
scope :search_columns, ->(params) do
  find_by_sql([<<-SQL, { search_word: "%#{params}%" }])
    SELECT
      *
    FROM
      users
    WHERE
      email LIKE :search_word
      OR name LIKE :search_word
    SQL
end

## Case 3 (ActiveRecord::Relation を維持)
scope :search_columns, ->(params) do
  where(<<-SQL, search_word: "%#{params}%")
    email LIKE :search_word
    OR name LIKE :search_word
    SQL
end

あとがき

初めは複雑な SQL を避けようとして、Arel に手を染めようとしましたが、伊藤さんの記事を見て思い止まりました( 今思うと結構単純だったけど... )。

他にも.find_by_sqlの説明において、複数テーブルに関わる検索等の複雑性を持つクエリに焦点を当てている記述があること、また、 Arel はあくまで内部 API であり、使用を非推奨化されているとも方々で非推奨の足跡が多いので、サービス開発時には使わない方が良いのでしょう。

Rails エンジニアは SQL が苦手だという風説は、私のような初心者から始まっているのかもしれない...

□ 参考URL

17
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
10