はじめに
マルチ検索としてよく用いられるオートコンプリート検索機能を実装します
今回はNodeモジュールを環境構築時に使用している際、どのような形でオートコンプリート検査機能を実装するのかを説明いたします
オートコンプリート検索機能とは
1文字入力するたびに、候補が表示される検索機能の一つです
これにより、ユーザーの入力手間を減らし、入力ミスを減らすことができます
技術スタック
今回は下記の技術スタックでオートコンプリート検索機能を実装します
ツール | 内容 |
---|---|
フロントエンド | Ruby on Rails,Node.js 20.17.0 |
バックエンド | Rails 7.2.1, (ruby 3.2.3 (2024-01-18 revision 52bb2ac0a6) [x86_64-linux]) |
開発環境 | Docker |
参考資料
今回は下記の資料を参考に作成しております
実装方法
オートコンプリート検索機能を実装する前に検索機能が実装できていない人向けに検索機能の実装についてもまとめて説明します。
検索機能実装済みの方はこのコーナーをパスしてください
今回は定番のgem ransackを用いて検索機能を実装します
1.gem 'ransack'導入: 検索機能のベースを作る
まずはransackで“検索の土台”を作っていきましょう!
上記のGitHubの資料を参考にして、Gemfileに gem 'ransack', '3.2.1' を追記して、bundle install(Dockerを使用)を行い、各サーバーを立ち上げ直します
Gemfile
gem 'ransack', '3.2.1'
2.app/controllers/posts_controller.rbのfavoritesアクションを編集する
今回はapp/controllers/favorites_controller.rbのfavorites(お気に入り登録)アクションを編集して以下を満たす実装を行います
- ログインしているユーザーがブックマークした掲示板レコードからタイトル or 本文の部分一致にマッチしたレコードが、ブックマーク一覧画面に表示されること
- 検索が行われていない場合は、ログインしているユーザーがブックマークした掲示板レコードに表示されること
class PostsController < ApplicationController
# 省略
# いいね!した投稿を保存するためのアクション
def favorites
# current_userがいいねしている投稿を取得
# @favorite_posts = current_user.favorites.includes(:post).map(&:post)
@q = Post.joins(:favorites).where(favorites: { user_id: current_user.id }).ransack(params[:q])
@favorite_posts = @q.result.includes(:user)
end
# 省略
end
詳しく説明すると下記になります
@q = Post.joins(:favorites).where(favorites: { user_id: current_user.id }).ransack(params[:q])
current_user
がいいねした投稿を取得し、検索条件を適用し、@favorite_postsの取得方法をransackを用いるよう変更しています
favoritesアクションで検索フォームから送信されたparams[:q]を受け取り、current_userがいいねした投稿の中から検索しています
@favorite_posts = @q.result.includes(:user)
@q.resultで取得された検索結果を@favorite_postsに代入します
3.app/views/posts/_search_form.html.erbを生成・編集する
app/views/posts/_search_form.html.erbを生成して、以下のように編集します
<!-- 検索フォーム -->
<%= search_form_for @q, url: url do |f| %>
<div class="input-group mb-3">
<%= f.search_field :title_or_body_cont, class: 'form-control', placeholder: t('defaults.search_word') %>
<div class="input-group-append">
<%= f.submit class: 'btn btn-primary' %>
</div>
</div>
<% end %>
今回はsearch_form_forというransackが提供するフォームヘルパーを使ってフォームを作成しています
url: urlと記入することで、どこからでも呼び出せる汎用性の高いパーシャルファイルになります。
例えば、すべての掲示板から検索したい場合は、url: boards_pathとrenderに記載。お気に入りしている口コミから検索したい場合は、url: favorites_posts_pathと記載します。
4.app/views/posts/favorites.html.erbを編集する
app/views/posts/favorites.html.erbを以下のように編集し、renderで_search_form.html.erbに紐づけるようにします
... 省略 ...
<div class="container pt-3">
<div class="row">
<div class="col-lg-10 offset-lg-1">
<%= render 'search_form', url: favorites_posts_path, q: @q %>
</div>
</div>
<!-- 掲示板一覧 -->
... 省略 ...
</div>
ここまでが検索機能の実装手順となります
↓ここからがオートコンプリート検索機能の実装です
1. stimulus-autocompleteをインストールする: 文字入力で候補を即表示したい人向け
文字を入力すると検索候補が表示される挙動を実現するために、stimulus-autocompleteを使います
js バンドラーnode_modules(esbuild、rollup.js、Webpack など) を使用している場合は、npm からパッケージをインストールします
※Rails7系以降ではrails newの段階でstimulus-railsが自動でインストールされているので、新たにStimulusをインストールする必要はございません
yarn add stimulus-autocomplete
root@0be8e4763e50:/myapp# yarn add stimulus-autocomplete
yarn add v1.22.22
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ stimulus-autocomplete@3.1.0
info All dependencies
└─ stimulus-autocomplete@3.1.0
Done in 51.36s.
app/javascript/controllers/application.jsにstimulus-autocompleteの設定を追加します
import { Application } from "@hotwired/stimulus"
import { Autocomplete } from 'stimulus-autocomplete' #コンポーネントを読み込むための記述
const application = Application.start()
// Configure Stimulus development experience
application.debug = false
window.Stimulus = application
application.register("autocomplete", Autocomplete) #コンポーネントにあるAutocompleteコントローラを使えるようにするための記述
export { application }
application.register('autocomplete', Autocomplete)
の記述により、HTML内で
data-controller="autocomplete"
の属性をもつ要素に対して、Autocompleteコントローラの機能が適用されます
2. 検索フォームとオートコンプリート部分のビューを紐付ける
ルーティングを設定します
今回はPost側のモデルにオートコンプリート検索のルーティングを追加しています
resources :posts, only: %i[index new create show edit destroy update] do
resources :comments, only: %i[create destroy], shallow: true
resource :favorites, only: [ :create, :destroy ]
collection do
get :autocomplete # ここ追加
get :favorites
get :posts
end
end
非同期のリクエストを受け取ってオートコンプリート部分のビューを返すためにpostsコントローラにおいてautocompleteアクションを追加します
Post(投稿)の検索においてtitleカラムにparams[:q]が含まれているか、最大10件までに制限します
検索結果からtitleカラムのみを配列をJSON形式で返します。
def autocomplete
posts = Post.ransack(title_cont: params[:q]).result.limit(10) # 🔍部分一致&最大10件
render json: posts.pluck(:title)
# タイトルだけ抜き出し、JSON形式で返します
end
autocompleteアクションの大まかな内容は以下のとおりです
役割 | 用途 |
---|---|
入力されたキーワードに部分一致するPostのタイトルを最大10件、JSON配列で返すAPIエンドポイント | オートコンプリート(検索候補表示)用 |
上記のautocompleteアクションについて詳しく説明します。
まずフロントエンド(JavaScriptやstimulus-autocomplete)が、ユーザーの入力値をqというパラメータでサーバーに送信します
例: /posts/autocomplete?q=ジョージア
次にRansackで部分一致検索します。
ここでのtitle_contは「titleカラムにparams[:q]が含まれているか」を意味します(cont=contains)。
Post.ransack(title_cont: params[:q])
[params[:q]](vscode-file://vscode-app/c:/Users/jiant/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html)
が「ジョージア」なら、「ジョージア」を含むタイトルのPostを検索します
.result.limit(10)
オートコンプリート候補として多すぎない数を返すため、Ransackの検索結果を取得し最大10件までに制限します
.pluck(:title)
検索結果からtitleカラムだけを配列で取り出します
例: ["ジョージア料理の魅力", "ジョージア旅行記", ...]
render json: ...
配列をJSON形式で返します
これによりフロントエンドのstimulus-autocompleteがこのJSON配列を受け取り、候補リストとして表示されます
3. 自動補完部分のビューを作る
<%= search_form_for @q, url: url, method: :get, html: { autocomplete: "off", data: { controller: "autocomplete" } } do |f| %>
<div class="input-group mb-3">
<%= f.text_field :title_or_body_cont, class: "form-control", placeholder: t('posts.search_placeholder'), data: { autocomplete_target: "input", action: "input->autocomplete#search", autocomplete_url_value: autocomplete_posts_path } %>
<div class="input-group-append">
<%= f.submit class: 'btn btn-primary' %>
</div>
</div>
<% end %>
上記の自動補完部分のビューについて詳しく説明します。
入力フォームに文字を入力した時に、自動補完させたいので、検索フォーム(search_form)の中に
method: :get, html: { autocomplete: "off", data: { controller: "autocomplete" } }
を追加します
Railsのフォームヘルパーとしてはtext_fieldの方が一般的で、カスタマイズやCSS適用の面でも柔軟なことから、text_fieldに変更します
Autocompleteコントローラが入力フォームを取得するために、
data: { autocomplete_target: "input", action: "input->autocomplete#search", autocomplete_url_value: autocomplete_posts_path }
を追加します
data: { ... }
- Stimulus(stimulus-autocomplete)用の属性を設定します
autocomplete_target: "input"
- このinputがStimulusコントローラーのinputターゲットであることを示します
action: "input->autocomplete#search"
- 入力時(inputイベント)にStimulusのautocomplete#searchアクションを呼び出します
autocomplete_url_value: autocomplete_posts_path
- オートコンプリート候補を取得するAPIエンドポイントのURLを指定します