1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

マルチ検索としてよく用いられるオートコンプリート検索機能を実装します
今回はNodeモジュールを環境構築時に使用している際、どのような形でオートコンプリート検査機能を実装するのかを説明いたします


Image from Gyazo

オートコンプリート検索機能とは

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を指定します
1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?