LoginSignup
28
20

stimulus-autocompleteを使ってオートコンプリート付き検索機能を作ってみた!

Last updated at Posted at 2023-10-18

はじめに

初めまして!未経験からエンジニア転職を目指し、学習中のやましょうと申します。
ポートフォリオの作成中に、検索機能を追加する際、ユーザがフォームに文字を入力すると検索候補が表示されるオートコンプリート機能をstimulus-autocompleteというコンポーネントを使って実装しました。このコンポーネントを利用することで、簡単に実装ができました。その経験をもとに、備忘録としての手順を共有します!初めての記事執筆となりますので、ご意見やフィードバックをいただけると幸いです!

対象者

  • 検索機能をちょっとパワーアップさせたい方
  • プログラミング初心者

完成イメージ

Image from Gyazo

目次

タイトル
1 開発環境・使用技術
2 セットアップ
3 Scaffoldを実行
4 検索の実装
5 stimulus-autocompleteをインストール
6 オートコンプリート部分のビューを作成
7 検索フォームとオートコンプリート部分のビューを紐づける
8 おわりに
9 参考文献

1章 開発環境・使用技術

最近自分がよく使っているからという理由でtailwind cssを採用してます。

  • Rails 7.0.8
  • Ruby 3.2.2
  • tailwind css
  • ransack 4.0.0

2章 セットアップ

  • 今回はstimulus-autocompleteをimportmapを使ってインストールするので、JSのバンドラーをデフォルトのimportmapにしました。
    cat-stimulus-autocompleteというアプリ名で作成。
rails new cat-stimulus-autocomplete --css=tailwind --skip-action-mailer --skip-action-mailbox --skip-aciton-text --skip-action-storage --skip-aciotn-cable --skip-test

DB作成

bin/rails db:create

サーバーを起動する

bin/dev

起動できていることがわかった!
Image from Gyazo

3章 Scaffoldを実行

rails g scaffoldを実行。

rails g scaffold Cat name:string age:integer
rails db:migrate

サーバーを起動後、http://localhost:3000/catsで一覧画面にアクセスする。

bin/dev

Image from Gyazo

その後、適当に初期データを追加しておく。

Image from Gyazo

4章 検索の実装

今回は、ransackという簡単に検索機能を作れる仕組みを提供してくれるgemを使います。
Gemfileにransackを追加後、bundle install。

gem 'ransack'
bundle install

一覧画面に検索フォームを設置します。
index.html.erbに検索フォームを追加する。検索対象のカラムはnameにしています。

<div class="w-full">
  <% if notice.present? %>
    <p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
  <% end %>

  <div class="flex justify-between items-center">
    <h1 class="font-bold text-4xl">Cats</h1>
    <%= link_to "New cat", new_cat_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %>
  </div>
  
  <!-- 検索フォームを追加 -->
  <%= search_form_for @cat, url: cats_path do |f| %>
    <div class="relative flex rounded-md">
		<%= f.search_field :name_cont, class: "w-[300px] py-3 px-4 pl-11 block border-gray-200 shadow-sm rounded-md text-sm focus:z-10 focus:border-blue-500 focus:ring-blue-500 dark:bg-slate-900 dark:border-gray-700 dark:text-gray-400", placeholder: "名前を入力してください" %>
		<%= f.hidden_field :name %>
		<ul class="list-group bg-white absolute w-full md:text-sm max-w-max" data-autocomplete-target="results"></ul>
        <div class="absolute inset-y-0 left-0 flex items-center pointer-events-none pl-4">
          <svg class="h-4 w-4 text-gray-400" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
            <path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
          </svg>
        </div>
		<%= f.submit "検索", class: "ml-4 py-3 px-4 inline-flex flex-shrink-0 justify-center items-center gap-2 rounded-md border border-transparent font-semibold bg-blue-500 text-white hover:bg-blue-600 focus:z-10 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all text-sm" %>
    </div>
  <% end %>
  <!-- ここまで -->

  <div id="cats" class="min-w-full">
    <%= render @cats %>
  </div>
</div>

cats_controller.rbのindexアクションを修正。

class CatsController < ApplicationController
  before_action :set_cat, only: %i[ show edit update destroy ]

  # GET /cats or /cats.json
  def index
    @cat = Cat.ransack(params[:q])
    @cats = @cat.result
  end

    以下省略
end

models/cat.rbを修正。

class Cat < ApplicationRecord
  def self.ransackable_attributes(auth_object = nil)
    ["age", "created_at", "id", "name", "updated_at"]
  end
end
  • ransack '4.0.0'から、検索対象のテーブル(catsテーブル)のカラムをモデル(Cat.rb)に明示しないと検索してくれないので記述しています。この記述を忘れてしまうと、以下のようなエラーが出るので気をつけましょう!
ActionView::Template::Error (Ransack needs Cat attributes explicitly allowlisted as
19:07:25 web.1  | searchable. Define a `ransackable_attributes` class method in your `Cat`
19:07:25 web.1  | model, watching out for items you DON'T want searchable (for
19:07:25 web.1  | example, `encrypted_password`, `password_reset_token`, `owner` or
19:07:25 web.1  | other sensitive information). You can use the following as a base:
19:07:25 web.1  | 
19:07:25 web.1  | ```ruby
19:07:25 web.1  | class Cat < ApplicationRecord
19:07:25 web.1  | 
19:07:25 web.1  |   # ...
19:07:25 web.1  | 
19:07:25 web.1  |   def self.ransackable_attributes(auth_object = nil)
19:07:25 web.1  |     ["age", "created_at", "id", "name", "updated_at"]
19:07:25 web.1  |   end
19:07:25 web.1  | 
19:07:25 web.1  |   # ...
19:07:25 web.1  | 
19:07:25 web.1  | end
19:07:25 web.1  | ```

一覧画面を開くと検索フォームが表示されて、猫の名前で検索できるようになりました!
まだ、フォームに文字を入力すると検索候補が表示されるようになっていません。
ここからが本番です!
Image from Gyazo

5章 stimulus-autocompleteをインストール

ここでフォームに文字を入力すると検索候補が表示される挙動を実現するために、
stimulus-autocompleteを使います。

※Rails7系ではrails newの段階でstimulus-railsが自動でインストールされているので新たにStimulusをインストールする必要はありません。

今回はimportmapでstimulus-autocompleteをインストールしています。

./bin/importmap pin stimulus-autocomplete

実行後のconfig/importmap.rb

# Pin npm packages by running ./bin/importmap

pin "application", preload: true
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
pin "@hotwired/stimulus", to: "https://ga.jspm.io/npm:@hotwired/stimulus@3.2.2/dist/stimulus.js" #追加された
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"
pin "stimulus-autocomplete", to: "https://ga.jspm.io/npm:stimulus-autocomplete@3.1.0/src/autocomplete.js" #追加された
  • app/javascript/controllers/application.jsにstimulus-autocompleteの設定を追加する。
import { Application } from "@hotwired/stimulus"
import { Autocomplete } from 'stimulus-autocomplete'  #コンポーネントを読み込むための記述

const application = Application.start()
application.register('autocomplete', Autocomplete)  #コンポーネントにあるAutocompleteコントローラを使えるようにするための記述

// Configure Stimulus development experience
application.debug = false
window.Stimulus   = application

export { application }
  • application.register('autocomplete', Autocomplete)の記述により、HTML内でdata-controller="autocomplete"の属性をもつ要素に対して、Autocompleteコントローラの機能が適用されます。

6章 自動補完部分のビューを作る

Image from Gyazo

views/cats/search.html.erbを新たに作成する

<% @cats.each do |cat| %>
  <li class="w-80 flex items-center gap-x-3.5 py-2 px-3 rounded-md text-sm text-gray-800 hover:bg-blue-200 focus:ring-2 focus:ring-blue-500 dark:text-gray-400 dark:hover:bg-blue-200 dark:hover:text-gray-300" role="option" data-autocomplete-value="<%= cat.id %>" data-autocomplete-label="<%= cat.name %>">
    <%= cat.name %>
  </li>
<% end %>

7章 検索フォームとオートコンプリート部分のビューを紐付ける

検索フォームを修正

  • 入力フォームに文字を入力した時に、自動補完させたいのでindex.html.erbの入力フォームを<div data-controller="autocomplete" data-autocomplete-url-value="/cats/search" role="combobox"></div>で囲む。

  • Autocompleteコントローラが入力フォームを取得するために、以下を追加する。

<%= f.search_field :name_cont ... %>data: { autocomplete_target: 'input' }を追加する。
<%= f.hidden_field :name ... %>data: { autocomplete_target: 'hidden' }を追加する。

  <!-- 検索フォームを追加 -->
  <%= search_form_for @cat, url: cats_path do |f| %>
    <div class="relative flex rounded-md">
      <div data-controller="autocomplete" data-autocomplete-url-value="/cats/search" role="combobox">
		<%= f.search_field :name_cont, data: { autocomplete_target: 'input' }, class: "w-[300px] py-3 px-4 pl-11 block border-gray-200 shadow-sm rounded-md text-sm focus:z-10 focus:border-blue-500 focus:ring-blue-500 dark:bg-slate-900 dark:border-gray-700 dark:text-gray-400", placeholder: "名前を入力してください" %>
		<%= f.hidden_field :name, data: { autocomplete_target: 'hidden' } %>
		<ul class="list-group bg-white absolute w-full md:text-sm max-w-max" data-autocomplete-target="results"></ul>
        <div class="absolute inset-y-0 left-0 flex items-center pointer-events-none pl-4">
          <svg class="h-4 w-4 text-gray-400" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
            <path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
          </svg>
        </div>
	  </div>
	  <%= f.submit "検索", class: "ml-4 py-3 px-4 inline-flex flex-shrink-0 justify-center items-center gap-2 rounded-md border border-transparent font-semibold bg-blue-500 text-white hover:bg-blue-600 focus:z-10 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all text-sm" %>
    </div>
  <% end %>
  <!-- ここまで -->

処理の流れ

  1. 検索フォームがブラウザで表示されると、data-controller="autocomplete"が読み込まれ、Autocompleteコントローラのconnectメソッドが呼び出され、connectメソッド内に定義されている各イベントを監視する。
  2. 入力フォームの内容が変更されると、connectメソッド内のthis.inputTarget.addEventListener("input", this.onInputChange)が発火し、onInputChangeメソッドを呼び出す。
  3. onInputChange内で入力内容が一定の長さ(this.minLengthValue)以上である場合、fetchResultsメソッドが呼び出される。
  4. this.hasUrlValueがtrueである場合(data-autocomplete-url-value属性が設定されている場合)に、サーバーからオートコンプリートの結果を取得する。このメソッド内で、buildURLメソッドを使用してリクエストのURLを構築し、そのURLに対して非同期のリクエストを行う。

searchアクションとルーティングを設定する

  • Autocompletコントローラのメソッドから、非同期のリクエストを受け取ってオートコンプリート部分のビューを返すためにcatsコントローラにsearchアクションを追加。また、ルーティングを設定する。

これにより検索フォームに文字が入力されると、/cats/searchにリクエストが行われ、オートコンプリートの結果が取得されるようになります。

class CatsController < ApplicationController

  省略

  # 追加
  def search
    @cats = Cat.where("name like ?", "%#{params[:q]}%")
    respond_to do |format|
      format.js
    end
  end

  private

  # Use callbacks to share common setup or constraints between actions.
  def set_cat
    @cat = Cat.find(params[:id])
  end
  # Only allow a list of trusted parameters through.
  def cat_params
    params.require(:cat).permit(:name, :age, :cat_image, :cat_image_cache)
  end
end
resources :cats do
  get :search, on: :collection
end

そうすると、完成!!!
Image from Gyazo

8章 おわりに

初めて学んだ知識を記事に書き起こしてみたのですが、実装している際に気づかなかったことに気づけたり、Stimulusに関する理解がより深まったと思うので書いてよかったなと思っています。
また、新たに機能を付け加える際には、便利なコンポーネントやライブラリがないか探すようにしようと思えるようにもなりました。この記事が皆さんのポートフォリオ作りのお役に立つことができれば幸いです。

9章 参考文献

https://zenn.dev/nagan/articles/e627918c192265
https://github.com/afcapel/stimulus-autocomplete
https://blog.to-ko-s.com/stimlus-introduce/

28
20
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
28
20