Techioz Blog

Heroku アドオン データベースの運用環境で Filterrific Search が機能しないのはなぜですか?

概要

私は Rails アプリに filterrific gem (現在 5.2.3) をよく使用しており、とても気に入っています。奇妙な問題に遭遇しました。開発チームが Heroku の追加アドオンとして書き込むためのセカンダリ Postgres データベースがあります。私のフィルタリフィック設定は実稼働環境のすべてのスコープ フィルタでうまく機能しますが、検索ではエラーが返されます。 SQL シード データを使用した開発では、すべてが完璧に機能します。

モデル.rb

filterrific(
   default_filter_params: { },
   available_filters: [
     :sorted_by,
     :with_search_please,
     :with_manufacturer,
     :with_boot_type,
     :with_firm,
     :with_monitor_count,
     :with_ethernet_count,
   ],
 )

scope :with_search_please, ->(search_string) {
  return nil  if search_string.blank?
  terms = search_string.to_s.downcase.split(/\s+/)
  terms = terms.map { |e|
      #('%' + e + '%').gsub(/%+/, '%')
      ('%' + e.gsub('*', '%') + '%').gsub(/%+/, '%')
    }
  num_or_conds = 6
  self.where(
      terms.map { |term|
        "(
        LOWER(Terminals.Model) LIKE ?
        OR LOWER(Terminals.TermcapModel) LIKE ?
        OR LOWER(Manufacturers.Name) LIKE ?
        OR LOWER(TerminalType.Type) LIKE ?
        OR LOWER(Note.Description) LIKE ?
        OR LOWER(FirmwarePackage.Version) LIKE ?
        )"
      }.join(' AND '),
      *terms.map { |e| [e] * num_or_conds }.flatten
    )
    .joins(:Manufacturers).references(:ManufacturerIds)
    .joins(:TerminalType).references(:TypeIds)
    .includes(:Notes).references(:TerminalIds)
    .joins(:FirmwarePackages).references(:TerminalFirmwarePackages)
}

ログエラー

Completed 500 Internal Server Error in 15ms (ActiveRecord: 3.2ms | Allocations: 6057)
2023-09-07T12:56:11.955033+00:00 app[web.1]: F, [2023-09-07T12:56:11.954949 #2] FATAL -- : [9e6cf5c0-457c-47aa-9099-a5923e41b1ce]
2023-09-07T12:56:11.955035+00:00 app[web.1]: [9e6cf5c0-457c-47aa-9099-a5923e41b1ce] ActionView::Template::Error (PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "terminals"
2023-09-07T12:56:11.955035+00:00 app[web.1]: LINE 2:         LOWER(Terminals.Model) LIKE '%a%'
2023-09-07T12:56:11.955036+00:00 app[web.1]: ^

私は検索クエリを書き換えて、条件を削除したり、単純な SQL クエリを作成したりするいくつかの方法を試しました。開発環境ではすべてが問題なく機能し、運用環境ではすべてのフィルタリング スコープが機能するため、これは特に困難です。これまでにこの問題に遭遇したことがないので、セカンダリ アドオン データベースに関係しているはずだとわかっています。

解決策

あなたの問題は、「端末」テーブルがクエリの一部ではないことです。

また、Postgres の二重引用符で囲まれた識別子は大文字と小文字が区別されることにも注意してください。 「端末」対「端末」。

クエリ全体と、このコード ブロックが定義されている場所に関するコンテキストを投稿していただければ、サポートできる可能性があります。

以上のことを踏まえた上で、このクエリは非常に厄介に思われるため、このクエリを少し整理するお手伝いをさせていただきたいと思います、IMO。

投稿から、あなたの意図は次を検索することであるようです: 「検索文字列内のすべての単語が次の列のいずれかに含まれるレコード: Terminals.Model、Termins.TermcapModel、Manufacturings.Name、terminalType.Type、Note.Description、ファームウェアパッケージ.バージョン」。

次のように、より簡単な方法でこれを実現できます (おそらくその過程で問題を解決できます)。

scope :with_search_please, ->(search_string) {
  return none if search_string.blank? # A scope should not return nil

  search_columns = [
    Terminal.arel_table[:Model],
    Terminal.arel_table[:TermcapModel],
    Manufacturer.arel_table[:Name],
    TerminalType.arel_table[:Type],
    Note.arel_table[:Description],
    FirmwarePackage.arel_table[:Version]
  ]

  terms = search_string.to_s
          .gsub("*","%")
          .split
          .map {|term| "%#{term}%"}

  where(search_columns.map {|c| c.matches_any(terms)}.reduce(:and))
    .joins(:Manufacturers)
    .joins(:TerminalType)
    .left_joins(:Notes)
    .joins(:FirmwarePackages)
}

たとえば、search_string が「A Sun*ny Day」の場合、次のような WHERE 句が生成されます。

(Terminals.Model ILIKE '%A%' OR Terminals.Model ILIKE '%Sun%ny%' OR Terminals.Model ILIKE '%Day%') AND 
(Terminals.TermcapModel ILIKE '%A%' OR Terminals.TermcapModel ILIKE '%Sun%ny%' OR Terminals.TermcapModel ILIKE '%Day%') AND 
(Manufacturers.Name ILIKE '%A%' OR Manufacturers.Name ILIKE '%Sun%ny%' OR Manufacturers.Name ILIKE '%Day%') AND 
(TerminalType.Type ILIKE '%A%' OR TerminalType.Type ILIKE '%Sun%ny%' OR TerminalType.Type ILIKE '%Day%') AND 
(Note.Description ILIKE '%A%' OR Note.Description ILIKE '%Sun%ny%' OR Note.Description ILIKE '%Day%') AND 
(FirmwarePackage.Version ILIKE '%A%' OR FirmwarePackage.Version ILIKE '%Sun%ny%' OR FirmwarePackage.Version ILIKE '%Day%')

全文検索を使えば、これをさらに進めることもできます。