内部結合を利用したあいまい検索時にsyntax errorで詰まった話
概要
whereメソッドとjoinメソッドを使用して、内部結合を利用したあいまい検索をした時、syntax error
で詰まった話。
失敗時のコード
掲示板に関して、作成ユーザー、掲示板タグ、掲示板タイトル、掲示板コンテンツ(記事内容)で検索を行おうとして以下のような実装をした所、掲示板コンテンツの部分でsyntax errorが表示された。作成ユーザー、掲示板タグはセレクトボックスによる選択、掲示板タイトル、掲示板コンテンツはフリーワード検索で行えるようにしてある。
# models/board.rb class Board < ApplicationRecord belongs_to :user has_many :board_tags has_many :contents has_many :tags, through: :board_tags scope :by_user, ->(user_id) { where(user_id: user_id) } scope :by_board_tag, ->(tag_id) { joins(:board_tags).where(board_tags: { tag_id: tag_id }) } scope :title_contain, ->(word) { where('title LIKE ?', "%#{word}%") } scope :body_contain, ->(word) { joins(:contents).where(contents: { 'contents.body LIKE ?', "%#{word}%" }) } end
# controllers/boards_controller.rb class BoardsController < ApplicationController def index @search_boards = SearchBoards.new(search_params) @boards = @search_boards.search.order(id: :desc) end private def search_params params[:q]&.permit(:title, :body, :tag_id, :user_id) end
# forms/search_boards.rb class SearchBoards include ActiveModel::Model include ActiveModel::Attributes attribute :user_id, :integer attribute :tag_id, :integer attribute :title, :string attribute :body, :string def search relation = Board.distinct relation = relation.by_user(user_id) if user_id.present? relation = relation.by_board_tag(tag_id) if tag_id.present? title_words.each do |word| relation = relation.title_contain(word) end body_words.each do |word| relation = relation.body_contain(word) end relation end private def title_words title.present? ? title.split(nil) : [] end def body_words body.present? ? body.split(nil) : [] end end
# views/boards/index.html.slim .ul.list-inline li = form_with model: @search_boards, scope: :q, url: boards_path, method: :get, html: { class: 'form-inline' } do |f| => f.select :user_id, User.pluck(:name, :id) , { include_blank: true }, class: 'form-control' => f.select :tag_id, Tag.pluck(:name, :id) , { include_blank: true }, class: 'form-control' .input-group = f.search_field :title, placeholder: "タイトル", class: 'form-control' .input-group = f.search_field :body, placeholder: "本文", class: 'form-control' span.input-group-btn = f.submit '検索', class: %w[btn btn-default btn-flat]
エラーを見ると、Boardモデルに書いたscope :body_contain
の部分で構文エラーが発生していたみたい。
SyntaxError - syntax error, unexpected ',', expecting => ...ents: {'contents.body LIKE ?', "%#{word}%"}) } ... ^ /Users/yoshitaka/Desktop/app/models/article.rb:74: syntax error, unexpected ')', expecting end ...nts.body LIKE ?', "%#{word}%"}) } ... ^: app/models/board.rb:74:in `' app/controllers/boards_controller.rb:7:in `index'
原因
何故こうなったかを調べると、内部結合を利用して結合先のテーブルで文字列検索を実行する時は一致検索時とは異なり、.where(結合先のテーブル名: { }
の結合先テーブル部分を書く必要がないことが原因だった。
セレクトボックスで一致検索をする場合、内部結合を利用した一致検索を行うコードは以下のように、whereメソッドの引数に「結合先のテーブル名: { カラム名: 値 }」を指定する事で検索できます。
モデル名.joins(:関連名).where(結合先のテーブル名: { カラム名: 値 }) # article_tagsテーブルのtag_idカラムが1のレコードを全て取得 Board.joins(:board_tags).where(board_tags: { tag_id: 1 }) }
しかしフリーワードであいまい検索をする場合、whereの条件部分に.where(結合先のテーブル名.カラム名 LIKE 〜)
といったように、結合先のテーブル名を書くことで結合先のテーブルに対して検索を行なってくれます。
# sentencesテーブルのbodyカラムに「hoge」という文字が入っているレコードを全て取得 Board.joins(:contents).where('contents.body LIKE ?', "%hoge%") }
解決方法
なので、whereの後ろに結合先のテーブル名を書かずに文字列検索用のコードを直後に書いたらエラーが解消した。
scope :body_contain, ->(word) { joins(:contents).where(contents: { 'contents.body LIKE ?', "%#{word}%" }) } => scope :body_contain, ->(word) { joins(:contents).where('contents.body LIKE ?', "%#{word}%") }