LoginSignup
0
0

More than 1 year has passed since last update.

「カタカナの半角・全角」「ひらがな・カタカナ」を区別せずに検索する

Last updated at Posted at 2023-04-26

やりたいこと

  • あるテーブルのある VARCHAR 型のカラムで LIKE 検索したい。
  • このとき、検索文字列の「カタカナの半角・全角」「ひらがな・カタカナ」を区別せずに検索したい。
  • せっかくなので色々なテーブルやカラムで使用できるように、特定のモデルのメソッドではなく汎用的なクラスとして実装したい。

バージョン情報

$ ruby -v
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin22]

$ bin/rails -v
Rails 7.0.4.3

$ mysql --version
mysql  Ver 14.14 Distrib 5.7.42, for osx10.18 (x86_64) using  EditLine wrapper

方法

  • カタカナの半角・全角を区別せずに検索するために COLLATE 句utf8mb4_unicode_ci を指定する。
    • COLLATE 句は照合順序を指定するための句。
    • 照合順序とはデータベース内で文字を比較したりソートしたりする際のルールのこと。
    • utf8mb4_unicode_ci は Unicode のコードポイントに基づいて文字列を比較する照合順序のひとつ。
      • この照合順序ではカタカナの半角・全角を区別しないし、ひらがな・カタカナも区別しない
  • quote_table_name メソッドでテーブル名を quote_column_name メソッドでカラム名をバックスラッシュでクォートする。
  • sanitize_sql_like メソッドで LIKE 演算子の値をサニタイズする。
    • 具体的には %_ をエスケープすることで、ユーザの入力値を安全に LIKE 演算子に渡すことができる。
class PerformCaseInsensitiveSearch
  COLLATION = 'collate utf8mb4_unicode_ci'

  delegate :model, to: :@relation, private: true
  delegate :connection, :sanitize_sql_like, to: :model, private: true
  delegate :quote_column_name, :quote_table_name, to: :connection, private: true

  def self.call(...)
    new(...).call
  end

  def initialize(relation, column_name:, value:)
    @relation = relation
    @column_name = column_name
    @value = value
  end

  def call
    @relation.where(
      "#{quoted_table_name}.#{quoted_column_name} #{COLLATION} LIKE ?",
      "%#{sanitized_value}%"
    )
  end

  private

  def quoted_table_name
    quote_table_name(model.table_name)
  end

  def quoted_column_name
    quote_column_name(@column_name)
  end

  def sanitized_value
    sanitize_sql_like(@value)
  end
end

Monster.pluck(:name)
#=> ["リオレウス", "リオレイア", "銀レウス", "るかりお"]
searched_monsters = PerformCaseInsensitiveSearch.call(Monster.all, column_name: :name, value: 'リオ')
searched_monsters.to_sql
#=> "SELECT `monsters`.* FROM `monsters` WHERE (`monsters`.`name` collate utf8mb4_unicode_ci LIKE '%リオ%')"
searched_monsters.pluck(:name)
#=> ["リオレウス", "リオレイア", "るかりお"]

参考

0
0
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
0
0