エンジニアリングにはほど遠い

iPhoneアプリとかサイトとかをつくっていくブログです。

ActiveRecordで複数カラムに対し複数キーワードで探す

割と汎用的かと思ったので載せてみようかと。
orの引数にnilがきたらそのままなのがミソ。

class ActiveRecord::Base         
  def self.search_with_multi(columns:, keywords:)
    where(columns_keywords_cond(columns, keywords))
  end
            
  def self.columns_keywords_cond(columns, keywords)
    columns
    .product(keywords)  
    .reduce(nil) do |cond, (column, keyword)|
      arel_table[column].matches("%#{keyword}%").or cond
    end
  end
end


Book.search_with_multi(columns: [:title, :description], keywords: ["", "休み"])

追記

これだと%と_の文字が埋もれてしまうのと、nilが条件を汚染していたので、以下に修正。 sanitize_sql_likeはRails4.2からは標準で入ってるメソッドとのこと。

class ActiveRecord::Base         
  def self.search_with_multi(columns:, keywords:)
    where(columns_keywords_cond(columns, keywords))
  end

  def self.columns_keywords_cond(columns, keywords)
    columns
    .product(keywords)
    .map { |column, keyword| arel_table[column].matches("%#{sanitize_sql_like(keyword)}%") }
    .reduce(&:or)
  end

  def self.sanitize_sql_like(string, escape_character = "\\")
    pattern = Regexp.union(escape_character, "%", "_")
    string.gsub(pattern) { |x| [escape_character, x].join }
  end
end