Help us understand the problem. What is going on with this article?

Rails インクリメンタルサーチの実装

まえがき

インクリメンタルサーチを実装するにあたって結構な時間を費やしてしまったので備忘録として書きます。実装をしてる方の役に立てると幸いです。

インクリメンタルサーチとは

どんなサイトにも当たり前のように付いている機能ですが、検索フォームに文字を打ち込んだ際に、enterを押さなくても自動的に結果が表示されるものです。有名なもので言うと、googleの検索欄なんかがインクリメンタルサーチになっています。

実装の流れ

・ルーティングを設定
・コントローラーにアクションを設定
・jbuilderの作成、記述
・フォームを作成
・発火させるようにjsに記述
・jsに追記
・modelにsearchメソッドを定義
・controllerに追記
・done関数の処理の記述
・非同期通信のビュー作成
・htmlをビュー上に描画

ルーティングを設定

sample.rb
resources :samples, only: :index
  collection do 
      get 'search'
  end

今回はsearchメソッドをモデルで定義して使うので、今のうちにsearchのルーティングを設定しておきます。

コントローラーにアクションを設定

先ほど設定したsearchアクションをコントローラーに記述し、編集していきます。

sample_controller.rb
def search
  respond_to do |format|
    format.html
    format.json
  end
end

この記述により、サーバーはjson形式で値をかえし、jbuilderファイルを使えるようになります。
この後出てくるjbuilderで@sampleと言う変数を使っていますが、また後述しますのでいったんこのまま進めます。

jbuilderの作成、記述

先ほどコントローラーに記述したrespond_toで分岐した情報がもしもjsonで送られた時はその情報はjbuilderへと流れてきます。
jbuilderの役割としては、html形式だった情報をjsonに変換して、非同期でhtml上で表示をさせることができる形に変換することです。
jbuilderの記載方法は以下です。

sample.json.jbuilder
json.array! @samples do |sample|
  json.title sample.title
  json.content sample.content
  json.name sample.user.name
end

今回は検索フォームということで配列として一つ一つの情報を順番に取り出せるようにしました。
json.array! @samples do |sample|
という部分で配列にするという指定をしています。
実際に変換をしているコードを説明していきます。
json.title sample.title
この部分で説明をすると、まず、arrayで指定をした一つ一つの値を|sample|として書いているのでその変数sampleの中に入っているtitleをjsonのtitleという名前に置き換えています。半角空白から右側が変換前、左側が変換後です。このjbuilderで変換した値は次はコントローラーへ送られ、その後jsファイルの.doneへと送られるので覚えておきましょう。

フォームを作成

次に実際に検索の文字を打ち込む検索フォームを作っていきます。

index.html.haml
.search_form
  = form_with(url: search_samples_path, local: true, method: :get, class: "search-form") do |form|
  = form.text_field :keyword, placeholder: "検索", class: "search-input"

今回はform_withを使って記述しています。
現在form_withが最も推奨されたフォームなので、form_forやform_tagなどを使っている方は早めにform_withを使えるようにしておきましょう。
= form.submit "検索", class: "search-btn"
本来このような検索ボタンがフォームには付いていますが、今回はインクリメンタルサーチと言うことで、あえて外しました。

発火できるようにjsに記述

sample.js
$(function() {
  $(".search-input").on("keyup", function() {
    let input = $(".search-input").val();
    console.log(input);
  });
});

発火を確認できるようにconsole.log(input)を記述しておきました。
このコードの意味としては.search-inputkeyup(キーボードから指あげた時)にその値を.valで取得し、inputに代入後、console.log(input)でデバッグを行っています。
もし発火していたら、ブラウザ上のプロパティツールでconsoleを選択し、入れた値を確認することができます。

jsに追記

sample.js
$(function() {
  $(".search-input").on("keyup", function() {
    let input = $(".search-input").val();
     $.ajax({
      type: 'GET',
      url: '/sample/search',
      data: { keyword: input },
      dataType: 'json'
  });
});

一つ一つ説明をしていきます。

$ajaxはコントローラーにリクエストを送る処理のことです。
type:ではHTTPメソッドを指定しています。
url:ではデータを送る先のurlを指定しています。
data:ではコントローラーに送るデータを指定しています。
data: { keyword: input },
の部分では、inputという変数を:keywordというキーをつけて送るよということが書いてあります。
dataType:は送るdataの形式を指定しています。

modelにsearchメソッドを定義

ここでsearchメソッドを定義していきます。

sample.rb
  def self.search(search)
    return Sumple.all() unless search
    Sumple.where('title LIKE(?)', "%#{search}%")
  end

titleと引数で持ってきたserachを比べて曖昧検索をするというメソッドを定義しました。
これで.searchというメソッドを使えるようになりました。

controllerに追記

sample_controller.rb
def search
  @input = Sample.search(params[:keyword])
  respond_to do |format|
    format.html
    format.json
  end
end

コントローラーにサーチメソッドを使う処理を書いていきます。ここでsearchメソッドに引数で
(params[:keyword])
を送ることでフォームで打ち込んだ文字が検索にかけられるようになっています。

done関数の処理の記述

sample.js
$(function() {
  $(".search-input").on("keyup", function() {
    let input = $(".search-input").val();
    $.ajax({
      type: 'GET',
      url: '/tweets/search',
      data: { keyword: input },
      dataType: 'json'
    })
    .done(function(samples) {
      $(".contents.row").empty();
      if (samples.length !== 0) {
        samples.forEach(function(sample){
          appendSample(sample);
        });
      }
      else {
        appendErrMsgToHTML("一致するツイートがありません");
      }
    })
  });
});

このような形で記述します。
.doneとはjbuilderから送られてきた情報を受け取るところです。受け取った情報をどのように使うかという表記をしています。

非同期通信のビュー作成

index.html.erb
 var search_list = $(".contents.row");

  function appendSample(sample) {
    var current_user = sample.user_sign_in && sample.user_sign_in.id == sample.user_id ? 
                              `<li>
                                <a href="/samples/${sample.id}/edit" data-method="get" >編集</a>
                              </li>
                              <li>
                                <a href="/samples/${sample.id}" data-method="delete" >削除</a>
                              </li>` : "";

    var html = `<div class="content_post" style="background-image: url(${sample.image});">
                  <div class="more">
                    <span><img src="/assets/arrow_top.png"></span>
                    <ul class="more_list">
                      <li>
                        <a href="/samples/${sample.id}" data-method="get" >詳細</a>
                      </li>
                      ${current_user}
                    </ul>
                  </div>
                  <p>${sample.text}</p><br>
                  <span class="name">
                    <a href="/users/${sample.user_id}">
                      <span>投稿者</span>${sample.nickname}
                    </a>
                  </span>
                </div>`
    search_list.append(html);
   }
   function appendErrMsgToHTML(msg) {
       var html = `<div class='name'>${ msg }</div>`
       search_list.append(html);
     }

この記述をjsファイル上部に記載します。この記載がjson形式のデータが入るインクリメンタルサーチが動作したときに表示されるビューファイルです。

sample.js
 .fail(function() {
      alert('error');
    });

最後に.done表記の下にもしデータ送信が失敗したときの対処も記載しておきましょう。

あとがき

以上でインクリメンタルサーチの実装は終了です。MVCモデルやajaxをしっかりと理解していないとどこからどこに情報が流れているかということがわからなくなってしまうので、情報の流れや、どの変数を使用しているのかということをしっかりと考えることがすごく大切です。もしわからないところがあれば随所調べるようにしましょう。
では、お疲れ様でした!!

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away