Railsで検索機能を実装するならransackというgemが有名どころですね。
ransackを導入すれば簡単に検索機能を実装することができます。
ただ、今回は普通の検索機能ではなく、以下のように入力したらすぐに検索結果を返してくれるインスタント検索機能を実装してみました。
今回インスタント検索機能を実装するにあたり、Rails7.0から標準搭載となった(Turboと相性の良い)StimulusというJavaScriptのライブラリを用いています。
開発環境
- Ruby 3.1.2
- Ruby on Rails 7.0.3
- Bootstrap 5.1.3
- M1 Macbook Air 2020
- mac OS Monterey (ver. 12.4)
- ターミナル bash (Rosetta 2 使用)
ransackで検索機能を実装する流れ
まずは、ransackで以下のような手動の検索機能を実装する流れについてみていきます。
その後にStimulusでインスタント検索を実装していくという流れで説明していきます。
gemの導入
gem "ransack"
上記をGemfileに記述し、$ bundle install
します。
コントローラーに検索結果を返すメソッドを記述
検索フォームを導入するページのコントローラーに、ransackでの検索結果を返すメソッドを記述します。
当記事では、Postモデルのshowページに検索フォームを導入したいので、postsコントローラーのshowメソッドに以下のように記述します。
class PostsController < ApplicationController
・・・
def show
# params[:q]には検索フォームで指定した検索条件が入る
@search = @post.items.ransack(params[:q])
# デフォルトのソートを created_at 降順にする
@search.sorts = "created_at DESC" if @search.sorts.empty?
# @search.result で検索結果となる@itemsを取得する
@items = @search.result
end
・・・
end
viewファイルに検索フォームを設置する
ransackの検索フォームの設置には、search_form_for
(ransackで用意されているメソッド)を用います。
Railsのform_withメソッドと使い方がほぼ同じなので使いやすいですね。
<%= search_form_for @search, url: post_path(@post) do |f| %>
<div class="row g3 mb-3">
<div class="col-4 col-xl-2">
<%= f.label :item_number_start, "番号", class: "form-label fw-bold" %>
<%= f.search_field :item_number_start, class: "form-control" %>
</div>
<div class="col-4 col-xl-2">
<%= f.label :name_cont, "景品名", class: "form-label fw-bold" %>
<%= f.search_field :name_cont, class: "form-control" %>
</div>
<div class="col-4 col-xl-2">
<%= f.label :item_state_eq, "在庫", class: "form-label fw-bold" %>
<%= f.select :item_state_eq, [['あり ◯', "exist" ], ['なし x', "taken"]], {include_blank: '選択なし'} ,class: 'form-select' %>
</div>
</div>
<div>
<%= button_tag(icon_with_text("search","検索"), class: "btn btn-primary") %>
<%= link_to "リセット", post_path(@post), class: "btn btn-outline-secondary" %>
</div>
<% end %>
ここで注目したいのが、各カラム名の後に付いている_start
や_cont
、_eq
といった文字列です。
これらはransackで用意されたメソッドで、それぞれ以下の意味があります。
- _start … 前方一致のとき
- _cont … の部分が一致するとき
- _eq … 完全に一致するとき
つまり、上記のコードでいう:name_cont
とすることで、検索ワードが:name
(アイテム名)と部分一致したら検索結果として返してくれるということです。
基本的にカラム名に上記のメソッドを付け加えるだけで検索条件を指定できるので便利ですね。
ransackにはこの他にも便利なメソッドがたくさんあります。
詳しくは、以下の記事がとても参考になります↓
これで検索フォームが利用できるようになりました。
インスタント検索機能の実装
さて、ここからはいよいよStimulusを使ってインスタントサーチを実装していきます。
Stimulusコントローラーの作成
まず、以下のコマンドでStimulusコントローラーを作成します。
(今回はsearchという名前のコントローラーを作成します)
$ rails g stimulus search
コマンドを実行すると、app/javascript/controllers
ディレクトリ内にsearch_controller.js
ファイルが自動生成されます。
search_controller.js
の中身は以下のようになっています。
import { Controller } from "@hotwired/stimulus"
// Connects to data-controller="search"
export default class extends Controller {
connect() {
}
}
また、同一ディレクトリ内にあるindex.js
には以下のようなコードが追加されます。
// This file is auto-generated by ./bin/rails stimulus:manifest:update
// Run that command whenever you add a new controller or create them with
// ./bin/rails generate stimulus controllerName
import { application } from "./application"
import HelloController from "./hello_controller"
application.register("hello", HelloController)
// searchコントローラー作成時に自動追記される(これがないとStimulusを利用できない)
import SearchController from "./search_controller"
application.register("search", SearchController)
作成したStimulusコントローラーにsubmitアクション(JavaScript)を記述
Stimulusコントローラーの中身を以下のように変更します。
import { Controller } from "@hotwired/stimulus"
// Connects to data-controller="search"
export default class extends Controller {
// コントローラーに紐づく要素(=フォーム)をsubmitするアクション
submit() {
// セットされているTimeoutをクリアする
clearTimeout(this.timeout)
// Timeoutをセットする
// 200ms後にリクエストを実行する
// 連続で実行されるとTimeoutはクリアされるため、最後の処理だけしか実行されない
this.timeout = setTimeout( ()=> {
this.element.requestSubmit()
}, 200)
}
}
requestSubmit()はフォームをサーバーに送信するJavaScriptのメソッドです。
https://www.webdesignleaves.com/pr/jquery/javaascript_04.php#h4_index_14
上記のコード(アクション)では、検索フォームに何かしらの入力(リクエスト)をしてから200ms後にrequestSubmit()
を実行するようにしています。
検索フォーム(search_form_for)にdata-controller
とdata-action
を指定
search_controller.jsに記述したアクションを検索フォームに適用させるため、以下のようにdata-controller
とdata-action
を指定します。
<%= search_form_for @search,
url: post_path(@post),
html: {
data: {
controller: "search",
action: "input->search#submit"
}
} do |f| %>
・・・
<% end %>
これでインスタントサーチが利用できるようになるはずです。
検索ボタンの削除
インスタントサーチが利用できるようになると、検索フォームに設置済みの「検索する」ボタンが不要になるので削除しておきましょう。
<%= search_form_for @search, url: post_path(@post) do |f| %>
・・・
<div>
<!-- 検索ボタンを削除する -->
<!-- <%= button_tag(icon_with_text("search","検索"), class: "btn btn-primary") %> -->
<%= link_to "リセット", post_path(@post), class: "btn btn-outline-secondary" %>
</div>
<% end %>
これでスッキリですね。
以下のようにインスタントサーチが機能すれば完成です。
コメント