すでにメンバーの場合は

無料会員登録

GitHubアカウントで登録 Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

登録がまだの方はこちらから

Pikawakaにログイン

GitHubアカウントでログイン Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

Rails

【Rails】 Ajaxチュートリアル(Rails+jQuery)~処理の流れを理解しよう

ぴっかちゃん
ぴっかちゃん

少し難しいイメージがあるAjax実装ですが、この記事では「Rails + jQuery」を使った実装手順や処理の流れをチュートリアル形式で1つ1つ丁寧に解説します。是非Ajax実装をマスターしましょう!

「Rails + jQuery」でAjaxを実装すると、以下のアプリケーションのように画面遷移なしで検索したタイトルを表示する事も出来ます。

Ajax実装をしたインクリメンタルサーチの完成図

Ajaxとは、JavaScriptでサーバー側との通信を「非同期」で行い、通信結果によって「動的にページの一部だけ書き換える手法のこと」です。

そもそも「Ajaxって何だろう?」という方や「Ajaxの仕組み」について理解出来ていない方は、こちらの「Ajaxとは?初心者向けに仕組みを徹底解説!」を読んでからAjax実装に入りましょう。

Ajaxを実装する方法とは?

RailsでAjaxを実装する場合は、「form系のヘルパーメソッドにremote: trueを設定して実装する方法」と「jQueryの$.ajax()などで実装する方法」の2つの実装方法があります。

前者のremote: trueは簡単なAjaxの処理には対応できますが、複雑な処理になる場合は後者の$.ajax()などを使って実装します。

今回は、Ajaxを用いて以下のような「検索窓に文字を入力するたびにタイトルの候補を表示する(インクリメンタルサーチ)」といった少し複雑な処理を実装するので、後者の$.ajax()を使います。

インクリメンタルサーチの例

Ajaxを用いてインクリメンタルサーチを実装する手順は、以下の通りです。

  1. 準備をしよう
  2. Viewを変更しよう
  3. データを送信しよう
  4. データをControllerで受け取ろう
  5. 検索処理を記述してレスポンスを返そう
  6. Controllerから受け取った内容をjsで反映させよう

Ajaxの実装に対して「なんだか難しそう」というイメージを持つ方が多いと思いますが、処理の流れを把握する事が出来れば、Ajax実装はそこまで難しいものではありません。

この記事では、アプリケーションを作成してAjaxの実装手順や処理の流れを1つ1つ詳しく解説します。手を動かす事で理解が一層深まるので、是非一緒に作成してみてください。

簡単なAjax処理をremote: trueで実装したい方は「remote: trueでAjax実装する方法」を参考にしてください。

1.準備をしよう

まずは、サンプルアプリケーションの用意や初期データの投入、そしてjQueryの導入やjsファイルの作成などAjaxの実装をする前に必要なものを準備します。

データをseedで入れた後のアプリケーションの様子

サンプルアプリケーションの用意

Ajax実装するサンプルアプリケーションは、メッセージのタイトルと内容を管理する事が出来るアプリケーションを作成します。

最初に、以下のrails newコマンドでsample_appアプリケーションを作成します。

ターミナル | データベースをMySQLを指定してアプリケーションを作成する -->
1
rails _5.2.1_ new sample_app -d mysql

次に、scaffoldでMessageモデルなど投稿メッセージを管理する為の雛形を作成します。メッセージのタイトルと内容が保存できるように、カラムはtitleとbodyを指定します。

ターミナル | 投稿メッセージを管理する為の雛形を作成 -->
1
2
3
cd sample_app # ディレクトリを移動
rails g scaffold Message title:string body:string # scaffoldを実行
# rails g scaffold モデル名 カラム名:型

そして、以下のコマンドでデータベースの作成とscaffoldの内容を元にして作成されたマイグレーションを実行します。

ターミナル | データベース作成とマイグレーションを実行-->
1
bundle exec rails db:create && bundle exec rails db:migrate 

rails serverでサーバーを起動させてlocalhost:3000/messagesにアクセスすると、以下のようにメッセージの登録・一覧表示・編集の簡単な機能が作成されます。

scaffoldで作成したサンプルアプリケーションの挙動

上記のアプリケーションでは、以下のコードで背景色や入力フォームのサイズのスタイルを追加しています。

app/assets/stylesheets/messages.scss | 見やすいようにスタイルを変更する -->
1
2
3
4
5
6
7
8
body {
  background-color: #cad8ff;
}

input[type="text"] {
  width: 300px;
  height: 30px;
}

これは解説の為に追加したスタイルなので、特に追加する必要はありません。

初期データの投入

作成したばかりのアプリケーションは、データが空の状態なのでseedを使って初期データを投入します。

db/seeds.rbに以下の内容をコピーして保存しましょう。

db/seeds.rb | 初期データを設定する-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 基本的な書き方
#モデル名.create!(
#  カラム名: '値',
#  カラム名: '値'
#)

# 以下のコードからコピーしてください。
Message.create!(
  [
    {
      title: 'アウトプットの大切さ',
      body: 'アウトプットするとは、インプットしたものを自分の価値観や考え方を通して、整理・発信したり自分に応用できる様に落とし込むこと。'
    },
    {
      title: '「衣」で気持ちを服用する',
      body: '自分の目で服用するという事が大事なので、外出用の洋服に着替えなくても、何か家の中や自分を元気にする薬を身近なところでこの状況下を乗り切りたいと感じた。'
    },
    {
      title: '目的に関知しない',
      body: '目的が規定されたツールを使う以上、僕たちが生み出せるものはその目的の範疇に収まってしまいがちであることに気づいた。ツールに操られていることを意識することで、少しでもそれに抗えるのではないかと思う。'
    },
    {
      title: '「サービスの体験をよくする」事で考えること',
      body: 'ユーザー体験は6段階に分けられて、これをUXピラミッドというらしいです。私は今まで漠然と何をすればいいかは把握してましたが、Lv1〜Lv6までに分けて低いところから満たしていくのは皆んなにわかりやすいと感じました。....'
    },
    {
      title: '快の転移の仕組み',
      body: 'ビールが飲めるようになったとき、コーヒーが美味しく感じるようになったとき、よく大人になったと言いますが、実はこういう仕組みだったのか...大人と全然関係ないじゃないか......'
    },
  ]
)

そして、以下のコマンドをターミナルで実行します。

ターミナル | gemをインストールする -->
1
bundle exec rails db:seed

再び「localhost:3000/messages」にアクセスすると、以下の動画のようにdb/seeds.rbに定義したデータが投入されて反映する事が出来ています。

データをseedで入れた後のアプリケーションの様子

このように、db/seeds.rbにデータを定義してコマンド実行するだけで初期データを簡単に投入する事が出来ます。

jQueryの導入

次にjQueryを導入するために、以下のコードをGemfileに追加してターミナルでbundle installを実行します。

Gemfile | Gemを追加する-->
1
gem "jquery-rails"
ターミナル | jquery-railsをインストール-->
1
bundle install

bundle installしたら、インストールしたgemを読み込むためにサーバーを再起動させましょう。bundle execについては、「bundle execとは?」を参考にしてください。

ターミナル | サーバーを再起動する -->
1
bundle exec rails server

そしてJavaScriptでjQueryのライブラリを読み込んで有効化するために、以下のコードをapplication.jsに追加します。

app/assets/javascripts/application.js | jQueryを有効化する-->
1
//= require jquery
ポイント

上記のコードをapplication.jsに記述する際には、//= require_tree .の前に記述するように気をつけてください。

コードの配置の注意点

//= require_tree .の後に記述してしまうと、ファイルが読み込まれずjQueryが動作しません。

jsファイルの用意

最後にAjaxの処理を記述するmessages.jsファイルを用意します。

scaffoldを実行すると、app/assets/javascripts/messages.coffeeが自動的に作成されますが、今回は使わないのでこちらのファイル名をmessages.jsにリネームします。

ターミナル | messages.coffeeをmessages.jsにリネームする-->
1
2
mv app/assets/javascripts/messages.coffee app/assets/javascripts/messages.js
# mv [変更前ファイル名] [変更後ファイル名]

ファイル変更

コマンドを実行すると、上記のようにファイル名がmessages.jsに変更されます。このファイル内に記述されている全てのコード削除して以下のコードに書き換えておきます。

app/assets/javascripts/messages.js | 初期コードを記述する-->
1
2
3
$(function () {
  // ここに処理を記述する
});

それでは、jQueryが導入出来ているか確かめるために以下のコードを記述しましょう。

app/assets/javascripts/messages.js | jQueryが導入出来ているか確認する-->
1
2
3
$(function () {
  $("h1").css("color", "#0000FF");
});

localhost:3000/messages」でページ再読み込み(⌘ + r)して、h1の文字色が変更されたらjQueryの導入が成功しています。

jQuery導入出来ているか確認

反映されていない場合は、もう一度以下の項目を確かめてみて下さい。

  • gemをinstallした後にサーバー再起動したか?
  • application.jsで追加したコード(//= require_tree .)の位置はあっているか?
  • messages.coffeeが存在していないか?(存在していた場合は削除する)
  • ページ再読み込み出来ているか?

これでAjax実装前の準備は終了しました。
次のセクションからいよいよAjax実装に入ります。この調子で1つ1つクリアしていきましょう!

2.Viewを変更しよう

今回は、用意したアプリケーションにAjaxを用いて「検索窓に文字を入力するたびにタイトルの候補を表示させる」というインクリメンタルサーチを実装します。

まずは、以下のようにscaffoldで用意したメッセージ一覧画面に検索窓を設置し、メッセージのタイトル一覧に変更します。

view変更前と変更後のイメージ

検索窓の設置

検索窓はフォームに文字が入力出来れば良いので、form_withなどのフォームヘルパーではなくtext_field_tagを使って作成します。

まずは、index.html.erbを以下のコードに書き換えます。

app/views/messages/index.html.erb | インクリメンタルサーチ出来るようにViewを変更する
1
2
3
4
5
6
<h1>タイトル一覧</h1>
<div class="contents">
  <%= text_field_tag :title,'', class: 'js-text_field', placeholder: 'タイトルを入力してください' %>
</div>

<%= link_to 'New Message', new_message_path %>

上記で検索窓を作成するために記述したtext_field_tagの部分は、以下のようにコンパイルされます。

text_field_tagのコンパイル前とコンパイル後の比較
1
2
3
4
5
<!-- コンパイル前 -->
<%= text_field_tag :title,'', class: 'js-text_field', placeholder: 'タイトルを入力してください' %>

<!-- コンパイル後 -->
<input type="text" name="title" id="title" value="" class="js-text_field" placeholder="タイトルを入力してください">

text_field_tagの第二引数には値を指定する必要があるので、コンパイル前のコードでは第二引数に''を指定しています。そして、値が空なのでvalue=""にコンパイルされます。

部分テンプレートの追加

そして、index.html.erbに更に以下のコードを追加します。

app/views/messages/index.html.erb | インクリメンタルサーチ出来るようにViewを変更する
1
2
3
4
5
6
7
8
9
10
11
12
<h1>タイトル一覧</h1>
<div class="contents">
  <%= text_field_tag :title,'', class: 'js-text_field', placeholder: 'タイトルを入力してください' %>

  <!-- 以下のコードを追加する-->
  <ul class="js-messages">
    <%= render @messages %><!-- 部分テンプレートを呼び出し-->
  </ul>

</div>

<%= link_to 'New Message', new_message_path %>

上記で追加した<%= render @messages %>は、部分テンプレート呼び出しの省略した書き方です。

これで呼び出されるファイルはapp/views/messages/_message.html.erbですが、ファイルがないので以下のコマンドで作成します。

ターミナル | 部分テンプレートを作成する-->
1
touch app/views/messages/_message.html.erb

作成したファイル内には、以下のコードを記述します。

app/views/messages/_message.html.erb | index.html.erbで呼び出される部分テンプレート
1
<li class="message"><%= link_to message.title, message %></li>

上記のmessageには、呼び出し側で渡している@messages(全てのメッセージの情報を格納)の中身が1つ1つ渡されます。中身が全て渡されるまでこの部分テンプレートが呼び出されるので、以下のように全てのメッセージのタイトルのリンクを表示されます。

タイトル一覧のリンクを確認

ここまでのView変更の完成図は以下の通りです。
まだ機能していない検索窓の設置と部分テンプレートでタイトル一覧のリンクを表示する事が出来ました。

View変更までの完成図

部分テンプレートの使い方について詳しくは、こちらの「部分テンプレートの使い方」を参考にしてください。

3.データを送信しよう

次は、検索窓に入力した値を取得して、そのデータをRails側にリクエストを送信するところまで実装します。

データ送信のイメージ図

検索窓に入力された値を取得

まずは、検索窓に入力された値を取得していきましょう。

検索窓のinput要素には、以下のようにjs-text_filedのクラスが付与されています。このクラスを使って値を取得する事が出来そうです。

検索窓のクラスを確認する

以下のようにChromeデベロッパーツールのConsoleに、jQueryで.js-text_filedを指定してval()を使用する事で、検索窓の値を取得出来ることが分かります。

検索窓の値を取得

これによりタイトル一覧ページの読み込みが完了してから実行するmessages.jsファイルの$(function(){}内には、以下のように記述する事で検索窓の値を取得出来ます。

app/assets/javascripts/messages.js | 検索窓の値を取得する-->
1
2
3
4
5
6
$(function () {
  var textField = $('.js-text-field');

  // $.trim()で値の前後の空白を削除 
  var title = $.trim(textField.val());
});

しかし、上記のコードだと「いつ」のタイミングで検索窓の値を取得するのか無い状態です。このタイミングの付け方ですが、例えば以下のようにon()を使ってkeyupのイベント処理を結びつけたとします。

app/assets/javascripts/messages.js | keyupイベントの例-->
1
2
3
4
5
$(function () {
  $('.js-text_field').on('keyup', function () {
    console.log("キーボードを入力した時に発生");
  })
});

このようにすると、キーボードを入力するたびにconsole.log("キーボードを入力した時に発生")が実行されるのが、以下の動画からも分かりますね。

keyupイベントの例

検索窓の値を取得するタイミングも「キーボードを入力するたび」にしたいので、$(function(){}内を以下のように記述します。

app/assets/javascripts/messages.js | キーボードを入力するタイミングで値を取得する-->
1
2
3
4
5
6
7
8
9
$(function () {
  $('.js-text_field').on('keyup', function () { 
    //  キーボードを入力したタイミングで以下の処理を実行する
    var textField = $('.js-text_field');
    var title = $.trim(textField.val());

    console.log(title); // 検索窓の値が取れているか確認
  });
});

上記のコードで追加したconsole.log(title)によって、以下のように検索窓で文字を入力しキーボードを入力する度に「検索窓の値を取得出来ているか」をConsoleで確認が取れます。

Consoleで検索窓入力の度に値を取得出来ているか確認

以下のコードで検索窓の値の取得は終わりですが、もう少しシンプルに記述する事が出来るのでリファクタリングします。

app/assets/javascripts/messages.js | リファクタリング前のコード -->
1
2
3
4
5
6
$(function () {
  $('.js-text_field').on('keyup', function () { 
    var textField = $('.js-text_field');
    var title = $.trim(textField.val());
  });
});

上記のtextFieldには$('.js-text_field')が格納されていますが、$(this)を使う事が出来ます。$(this)とは、以下の画像のようにイベントが発生した要素の情報の事です。

$(this)を既存コードに当てはめたときの例

つまり、.js-text_fieldのタグとタグの内部の情報を指しているので、緑枠で囲う箇所は$(this)に変更する事が出来ます。そしてtextFieldを使わなくても、以下のように直接$(this)を記述する事でよりシンプルになります。

app/assets/javascripts/messages.js | リファクタリング後のコード -->
1
2
3
4
5
6
$(function () {
  $('.js-text_field').on('keyup', function () {
    var title = $.trim($(this).val());
    console.log(title); // 検索窓の値が取れているか確認
  });
});

リファクタリングは以上になりますが、コードが正常に動くかconsole.log(title)で以下のように確認します。

リファクタリングのコードが正常に動くか確認する

問題なく動いていますね。リファクタリングした事でかなりシンプルなコードになりました。最後にconsole.log(title)は、動作確認のコードなので消しておきましょう。

app/assets/javascripts/messages.js | このセクションの完成コード -->
1
2
3
4
5
$(function () {
  $('.js-text_field').on('keyup', function () {
    var title = $.trim($(this).val());
  });
});

thisについて詳細は「thisについて理解しよう(関数呼び出し編)」を参考にしてください。

$.ajax()でリクエストを送信

ajax通信までの準備はできたので、これから下記の動画の様に$.ajax()メソッドを使ってインクリメンタルサーチを実装していきます。

Ajax実装をしたインクリメンタルサーチの完成図

$.ajax()メソッドを使ってRails側にリクエストを送る流れは、下記になります。

  1. 検索窓から検索ワードを取得する。
  2. 取得した検索ワードを$.ajax()メソッドを使ってRails側にリクエスト

1は前章で実装したので、これから2の「$.ajax()メソッドを使ってRails側にリクエスト」を実装していきます。

まずは、リクエストの送信が出来るように$.ajax()を使います。

$.ajaxメソッドを使ってリクエストを送信する-->
1
2
3
$.ajax({
 // リクエストの設定項目
)}

$.ajax()で設定出来るリクエストの項目には、以下のようなものがあります。

設定項目 内容
type リクエストのタイプ type: 'POST'type: 'GET'
url リクエストを送信するURL url: '/messages/searches'
data サーバーに送信するデータ data: { title: title }
dataType 期待するサーバーから返却される型 dataType: 'json'

今回は、検索窓から取得した値(title)のデータを'/messages/searches'にGETでリクエストを送信したいので、以下のように設定します。

app/assets/javascripts/messages.js | リクエスト送信のための設定を追加する -->
1
2
3
4
5
6
7
8
9
10
11
12
13
$(function () {
  $('.js-text_field').on('keyup', function () {
    var title = $.trim($(this).val());

    // 追加コード
    $.ajax({
      type: 'GET', // リクエストのタイプ
      url: '/messages/searches', // リクエストを送信するURL
      data:  { title: title }, // サーバーに送信するデータ
      dataType: 'json' // サーバーから返却される型
    })
  });
});

最後のdataTypeには、サーバーから返される型にJSONを指定します。(詳細は後述します。)そして、サーバー側との通信を「非同期」で行います。

これでリクエスト送信の設定は完了しました。実際にリクエスト送信してデータを受け取れているかは、次のセクションのControllerを作成してから確認します。

4.データをControllerで受け取ろう

前のセクションで$.ajax()を使ってリクエスト送信までの設定が終わりましたが、今度はリクエストされたデータをControllerで受け取れるように、ルーティングの設定と検索処理するコントローラを作成します。

データをControllerで受け取るイメージ図

検索処理をするコントローラを作成

$.ajax()でリクエストされた検索窓のデータを受け取って処理をするコントローラは、以下のapp/controllers/messages/searches_controller.rbに記述していきます。

コントローラの位置を確認する

このmessagesディレクトリとsearches_controller.rbファイルはまだ作成していないので、以下のコマンドで作成します。

ターミナル | 検索窓のデータを受け取るコントローラを作成する-->
1
mkdir app/controllers/messages && touch app/controllers/messages/searches_controller.rb

上記で作成したsearches_controller.rbには、以下のコードを記述します。

app/controllers/messages/searches_controller.rb | 初期コードを設定する-->
1
2
3
4
class Messages::SearchesController < ApplicationController
  def index
  end
end

Railsではファイル構造とクラス名を合わせる必要があるので、上記のようにクラス名にMessages::を付ける事で、この「SearchesControllermessagesディレクトリ配下にあるController」だと認識されます。

ポイント

app/controllers/messages_controller.rbに新たにsearchアクションを作成することも出来ますが、上記のようにapp/controllers/messagesのディレクトリに分けてSearchesController#indexに処理を記述する事で、RESTを崩さずに済むのでコードの可読性も上がります。

ルーティングの設定

次にルーティングの設定を行います。ここまでで決まっていることは、以下の通りです。

  • $.ajax()でリクエストを送信するURLは、/messages/searchesです。
  • 上記で送信されるデータを受け取るのは、Messages::SearchesControllerindexアクションです。

上記より、「/messages/searches」にリクエストが送信された場合は、Messages::SearchesControllerindexアクションが動いて欲しいので、ルーティングの設定を以下のように記述します。

config/routes.rb | ルーティングの設定を追加する-->
1
2
3
4
5
6
7
8
Rails.application.routes.draw do
  # 以下のブロックを追加する
  namespace :messages do 
    resources :searches, only: :index, defaults: { format: :json }
  end

  resources :messages # このコードより上に追加する(※補足説明へ)
end

上記の設定で生成されるルーティングは、以下の通りです。(ターミナルでrails routesを実行してます。)

ルーティングを確認する

このようにnamespaceで定義すると、URLだけではなくコントローラのファイル構成も指定したパス通り(/messages/searches)になります。

データを受け取れているか確認

これまでの$.ajax()やルーティングなどの設定によって、以下の画像の様に検索窓に文字を入力した際にMessages::SearchesControllerのindexアクションで検索窓の値を受け取る事ができているのか確認します。

indexアクションで検索窓の値を受け取る流れ

ここまでに行った設定や流れの詳細は、以下の通りです。

  1. 検索窓に文字を入力
  2. キーボードを入力する度に検索窓の値を取得
  3. このデータを$.ajax()/messages/searchesにリクエストを送信
  4. ルーティングで上記のURLがきたらMessages::SearchesControllerindexアクションが動くように設定

それでは、データを受け取れているか確認しましょう。

まずは、以下のコードのように「binding.pry」追加して、indexアクションが動いたら処理を止める様にします。(このgemは、pry-railsを参考にインストールしてください。)

app/controllers/messages/searches_controller.rb | binding.pryを使ってデータを受け取れるか確認する-->
1
2
3
4
5
class Messages::SearchesController < ApplicationController
  def index
    binding.pry # 追加する
  end
end

キーボードを入力した時に検索窓の値が取得されて、その値を$.ajax()の設定によって/messages/searchesに送信され、Messages::SearchesControllerのindexアクションが動くので、binding.pryを記述した箇所で処理が止まるはずです。

以下の動画の様に検索窓に文字を入力すると、ターミナルでプログラムの実行が停止されてPryが起動します。

検索窓の値がコントローラで受け取れているか確認する

そして、paramsを実行すると送信されたパラメータが表示されて、検索窓の値を{"title" => "検索窓の値"}として受け取れている事が分かります。このtitleというのは、以下の画像の様に$.ajax()dataでtitleのキーに検索窓の値を設定したからです。

$.ajax()との繋がり

また、Pryにrequest.formatを実行すると、リクエストされる形式がjsonである事が分かります。これは、上記のdataTypeで'json'を指定したからです。

ここまでの設定で無事検索窓に入力した値を受け取る事が出来たので、その値を含むタイトルがあるかという検索処理を実装します。

ポイント
  1. indexアクションで検索窓の値を{"title" => "検索窓の値"}として受け取れるのは、$.ajax()でdata: { title: title }と設定したからです。
  2. リクエストされる形式がjsonなのは、$.ajax()でdataType: 'json'と設定したからです。

5.検索処理を記述してレスポンスを返そう

次にindexアクションに検索処理を記述して、その結果をレスポンスします。ここでの1番の実装ポイントは、検索結果のオブジェクト(@messages)をそのまま返すのではなく、JSON形式のデータに変換してから返すという点です。

検索処理からレスポンスを返すまでの流れ

それでは、実装していきます。

検索窓の値は、先ほどのPryでparamsの中のtitleに格納されていた事が分かったのでparams[:title]の記述で取得できますね。

これを踏まえてindexアクションには、以下の「検索処理のコード」と「検索結果のデータ(@messages)をレスポンスするコード」を記述します。

app/controllers/messages/searches_controller.rb | タイトルを検索する-->
1
2
3
4
5
6
7
8
9
10
11
12
class Messages::SearchesController < ApplicationController
  def index
    # ↓検索処理のコード
    @messages = Message.where('title LIKE(?)', "%#{params[:title]}%")

    respond_to do |format|
      format.html { redirect_to :root }
      # ↓検索結果のデータをレスポンスするコード
      format.json { render json: @messages }
    end
  end
end

上記の検索処理のコードですが、whereメソッドLIKE句のあいまい検索を使ってtitleカラムにparams[:title]の値を含む全てのレコードを取得します。

これは、実際にアプリケーションを動かしたほうが理解しやすいので、以下のようにbinding.pryを追加して確認します。

app/controllers/messages/searches_controller.rb | @messagesの中身を確認する-->
1
2
3
4
5
class Messages::SearchesController < ApplicationController
  def index
    @messages = Message.where('title LIKE(?)', "%#{params[:title]}%")
    binding.pry  # 追加
    # 以下省略

検索窓には、以下の動画のように「の」を入力してPryを起動させます。
そして、@messagesを確認するとタイトルに「の」という値を含むレコードを全て取得しています。

検索処理で何を取得できるか確認

また、以下の動画のように検索窓に「大切」と入力すると、@messagesには、メッセージのタイトルが「大切」という値を含むレコードが取得されます。

検索処理が成功してるか確認する

このように、@messages検索窓の値を含むタイトルのレコードが全て格納されます。

そして、以下のように「respond_toメソッド」を使ってリクエストの形式ごとに処理を切り分けます。(詳細は、「respond_toメソッドの使い方」を参考にしてください。)

app/controllers/messages/searches_controller.rb | タイトルを検索する-->
1
2
3
4
5
6
7
8
9
class Messages::SearchesController < ApplicationController
  def index
    @messages = Message.where('title LIKE(?)', "%#{params[:title]}%")
    respond_to do |format| # リクエスト形式によって処理を切り分ける
      format.html { redirect_to :root } # html形式の場合
      format.json { render json: @messages } # json形式の場合
    end
  end
end

json形式でリクエストが送信された場合(format.json{})は、@messagesをそのまま返すのではなく、JSONデータへ変換したデータをレスポンスしたいので、render json: @messagesを記述します。(このデータの中身は次のセクションで確認します。)

リクエストからレスポンスまでの一連の流れを整理

上記の画像のように、$.ajax()で送信されるリクエスト形式はrequest.formatで確認したようにjsonなので、render json: @messagesの方を実行し@messagesをJSON形式に変換した情報をレスポンスします。

6.レスポンスを受け取りjQueryでViewに反映させよう

最後に以下の画像の①と②のように、Controllerからレスポンスを送信されたデータを受け取って、JavaScript(jQuery)で受け取ったデータを元にタイトル一覧に反映させます。

レスポンスからViewに反映までの流れ

レスポンスを受け取る

$.ajax()でリクエストを送信して通信が成功すると、以下のようにdone()が実行されます。引数のdataには、レスポンスで送信されたデータが格納されます。

レスポンスを受け取る流れ

$.ajax()送信から通信成功の流れ-->
1
2
3
4
5
6
$.ajax({
 // リクエストの設定項目
)}
.done(function(data){ // dataにはレスポンスされたデータが入る
  //通信に成功した際の処理
})

それでは以下のコードを追加して、通信が成功した際にdone()が実行されて引数dataにレスポンスデータが入るのか確認しましょう。

app/assets/javascripts/messages.js | 通信成功時の処理を追加する-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$(function () {
  $('.js-text_field').on('keyup', function () {
    var title = $.trim($(this).val());
    $.ajax({
      type: 'GET',
      url: '/messages/searches',
      data: ("title=" + title),
      dataType: 'json'
    })
    // 以下を追加
    .done(function (data) {
      console.log(data); // dataを確認する
    })
  });
});

以下の動画のように検索窓に「大切」と入力してConsoleを開くと、dataの中身は「検索窓の値を含んだタイトル」を持つメッセージの情報だと分かります。

done()に渡されるdataの中身

そして、Controllerのrender json: @messagesで変換した通り、dataJSON形式のデータになっていますね。

このdataを上手く使えば、検索窓の値を含んだタイトル一覧の表示が出来そうですね。

jQueryでViewに反映させる

それでは、done()に処理を記述します。

通信が成功したら現在表示されているタイトル一覧は必要ないので、以下のコードを追加して削除します。

app/assets/javascripts/messages.js | 既存のタイトル一覧を削除する-->
1
2
3
 .done(function (data) {
    $('.js-messages li').remove(); // 追加する
 })

上記のコードを追加して検索窓に文字を入力すると、以下のようにjs-messagesのクラスを持つulの中身(li)が全て削除されます。

既存のタイトル一覧が削除される様子

次に、以下のコードを追加して「レスポンスされたデータが入るdata」を使って「削除したjs-messagesのクラスを持つul要素の中身(li要素)」を追加します。

app/assets/javascripts/messages.js | 検索結果に応じたタイトル一覧を追加する-->
1
2
3
4
5
6
7
8
9
10
 .done(function (data) {
    $('.js-messages li').remove();

    // 以下のコードを追加する
    $(data).each(function(i,message) {
      $('.js-messages').append(
        `<li class="message"><a href="/messages/${message.id}">${message.title}</a></li>`
      );
    });
 })

上記はeach()dataの中身を1つ1つ取り出し、js-messagesのクラスを持つul要素にappend()の引数に指定したli要素を次々に追加します。

この記述によってjs-messagesのクラスを持つul要素の中身は、以下のように検索窓に入力した内容に表示する事が出来ます。

検索窓に入力してjQueryで反映させている様子

これでAjaxを用いた「検索窓に文字を入力するたびにタイトルの候補を表示する」というインクリメンタルサーチをRailsとjQueryを使って実装する事が出来ました。

以下の画像を確認して、ここまで実装した流れを一回整理してみましょう。

Ajax実装の全体の流れ

手元に置いておきたい1冊

こちらの「プロを目指す人のためのRuby入門」は、Rubyの特徴から例外処理、デバック技法など実務で必要となる知識を一通り学ぶことができます。

Ruby on Railsは、Rubyで書かれたフレームワークです。Railsで使用される構文はRubyの構文が大半です。そのためRubyをしっかり学習することでRailsへの理解が深まります。

Rubyは触りだけでRailsの扱い方に学習時間を費やす、もしくはRailsから学習している方もいますが、いずれにしても並行してRubyを学ぶと良いでしょう。

豊富なサンプルコードと丁寧な解説でしっかり理解できるように書かれている良書です!

プロを目指す人のためのRuby入門
プロを目指す人のためのRuby入門

言語仕様からテスト駆動開発・デバッグ技法まで

補足

前のセクションまででAjaxを用いたインクリメンタルサーチの実装は大体終わりましたが、「通信が失敗した場合の処理」や「他のページから遷移した際のjQueryの挙動」などを確認していきます。

通信が失敗の場合の処理

Ajaxでリクエストを送信して成功した際の処理をdone()で記述しましたが、以下のようにfall()を使うと通信に失敗した際の処理を記述する事が出来ます。

$.ajaxメソッドを使用した通信の流れ-->
1
2
3
4
5
6
7
8
9
$.ajax({
 // リクエストの設定項目
)}
.done(function(data){
  //通信に成功した際の処理
})
.fail(function(){
  //通信に失敗した際の処理
})

もし、通信に成功しても失敗しても通信が完了した際に何か処理を実行したい場合は、以下のようにalways()を使って処理を記述する事が出来ます。

$.ajaxメソッドを使用した通信の流れ-->
1
2
3
4
5
6
7
8
9
10
11
12
$.ajax({
 // リクエストの設定項目
)}
.done(function(data){
  //通信に成功した際の処理
})
.fail(function(){
  //通信に失敗した際の処理
})
.always(function(){
 //通信が完了してdone()かfail()が実行された後に、always()が実行される 
})

ページ遷移した際のjQueryの挙動

現在は、タイトル一覧ページに初回でアクセスした場合やリロードした場合にインクリメンタルサーチが正常に動く状態ですが、以下の動画のように他のページからタイトル一覧ページに遷移した場合はjQueryが上手く動かずに機能が使えない状態です。

jQueryが動かない場合を再現

上記は、デフォルトでturbolinksというgemが入っているので$(function () {})内に記述するコードが読み込まれないのが原因です。

これを解決するために、以下のmessages.jsのように$(function () {})の外側にturbolinks:loadを設定します。

app/assets/javascripts/messages.js | turbolinks:loadを追加する-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$(document).on('turbolinks:load', function () { // このコードを追加する
  $(function () {
    $('.js-text_field').on('keyup', function () {
      var title = $.trim($(this).val());
      $.ajax({
        type: 'GET',
        url: '/messages/searches',
        data: { title: title }, //("title=" + title),
        dataType: 'json'
      })
      .done(function (data) {
        $('.js-messages li').remove();
        $(data).each(function (i, message) {
          $('.js-messages').append(`<li class="message"><a href="/messages/${message.id}">${message.title}</a></li>`);
        });
      })
    });
  });
}); // このコードを追加する

上記のように記述する事で、初回アクセスやリロードだけではなく他のページから遷移しても処理が読み込まれて、正常に動作するようになります。

他のページから遷移しても処理が読み込まれて、正常に動作する様子

上記は、他のページからタイトル一覧ページにアクセスしてもインクリメンタルサーチが正常に動作していますね。

この記事のまとめ

  • $.ajax()でリクエストを送信して通信が成功した場合は、done()に記述した処理が実行される
  • レスポンスされるデータは、done(function(data){})の引数dataで受け取る事が出来る
  • Rails + jQueryでのAjax実装は、1つ1つの処理の流れを把握すると理解しやすい!