Ajaxとは
Ajaxとは、Webブラウザ上で非同期通信
を行い、ページ全体の再読み込み無しにページを更新する方法のことです。
#####同期通信について
同期通信
では、クライアントはwebページ全体の情報(HTMLとそれに紐づくcss,js,imageなどのアセット)をサーバーから受け取って、ページを一から作り直します。
例えばページの一部を変更するだけなのに、他の部分も組み立て直すってことはその分ページの表示に時間がかかっちゃいます。(サーバー側の処理を待つことになる)
しかも、このリクエスト〜レスポンスの処理を行っている間は、他の処理を行わずにサーバーからレスポンスが返ってくるのを待ち続ける必要があります(よくあるのが画面が真っ白になって何もできない状態)。
そこでAjaxのような非同期通信を使用すれば、ページ遷移無しに、高速で更新処理を行い、尚且つ、リクエスト〜レスポンスの処理を行っている間も他の処理が行えます。
######非同期通信の方法は2種類
この便利なAjaxによる非同期通信を行う方法としては、
①remote:true形式
②ajax関数を使った形式
の2パターンが存在しますが、今回はremote:true形式について以下に記していきます。
仕組みだけ知りたいよって方は、コードの説明は読み飛ばしちゃっても大丈夫です。
コードの説明
今回作るもの
掲示板のお気に入り(いいね)ボタンを押した時に、お気に入りの登録、解除を行うという仕組みをajax化させていきます。
ルーティングの設定
お気に入りの登録、削除を行うために必要なルーティングの設定を行う。
resources :boards do
resources :favorites, only: %i[create destroy], shallow: true
end
モデルの設定
モデルでUsersテーブル、Boardsテーブル、Favoritesテーブルの関連付を行う。
class User < ApplicationRecord
has_many :boards, dependent: :destroy
has_many :favorites, dependent: :destroy
has_many :favorite_boards, through: :favorites, source: :board
# お気に入り関連のインスタンスメソッド
# お気に入りをする
def favorite(board)
favorites_boards << board
end
# お気に入りを解除する
def unfavorite(board)
favorites_boards.destroy(board)
end
# お気に入りしているかどうかを判定する
def favorites?(board)
favoritess.where(board_id: board.id).exists?
end
end
class Board < ApplicationRecord
belongs_to :user
has_many :favorites, dependent: :destroy
end
class Favorite < ApplicationRecord
belongs_to :user
belongs_to :board
validates :user_id, uniqueness: { scope: :board_id }
end
コントローラの設定
AjaxによるHTTP通信を行うには、formにremote:true
オプションを設定する必要がある。
-
form_withメソッドでAjax通信を利用しない場合(
local: true
オプション)
favoritesコントローラのcreateアクション実行の際に、favorites/create.html.erb
というファイルをレンダリングしようとするため、別のページへリダイレクトさせていた。 -
Ajax通信を利用する場合(
remote: true
オプション)
remote: true
の記述によって、AjaxでHTTPリクエストを送信するように設定される。
更に、html.erb
ファイルではなくjs.erb
ファイルをレンダリングしてくれる。そして、このjs.erbファイルをjsのコードに変換した文字列が、レスポンスボディとしてブラウザに返される(詳細は後述)。
class FavoritesController < ApplicationController
# js.erbファイルで変数を使用するため、インスタンス変数を設定
def create
@board = Board.find(params[:board_id])
current_user.favorite(@board)
end
def destroy
@board = current_user.favorite_boards.find(params[:id])
current_user.unfavorite(@board)
end
end
お気に入りボタンを切り替えるためのビュー
favorites/_favorite_area.html.erb
ファイルで、ログイン中のユーザーが掲示板をお気に入りしているかどうかによって呼び出すテンプレートを分ける。
- お気に入りしていない場合は
_favorite.html.erb
を呼び出す。- お気に入りボタンは色無しの状態
- お気に入りする機能
- お気に入りしている場合は
_unfavorite.html.erb
を呼び出す。- お気に入りボタンは色付きの状態
- お気に入りを削除する機能
<% if current_user.favorite?(board) %>
<%= render 'favorites/unfavorite', { board: board } %>
<% else %>
<%= render 'favorites/favorite', { board: board } %>
<% end %>
お気に入りしていない場合のボタンを実装
_favorite.html.erb
ファイルを作成
- お気に入りするので、HTTPメソッドは
post
。対応するコントローラがcreate.js.erb
を呼び出す。 - id属性を付与(どのボタンをクリックしたか判別するため、各レコードのidを使用し、一意性を保つ)
-
remote: true
オプションを付与。
<%= link_to board_favorites_path(board), id: "favorite-button-#{board.id}", method: :post, remote: true do %>
<%= icon 'far', 'star' %>
<% end %>
お気に入りしている場合のボタンを実装
_unfavorite.html.erb
ファイルを作成
- お気に入りを削除するので、HTTPメソッドは
delete
。destroy.js.erb
を呼び出す。 - id属性を付与。
-
remote: true
オプションを付与。
<%= link_to favorite_path(board), id: "favorite-button#{board.id}", method: :delete, remote: true do %>
<%= icon 'fas', 'star' %>
<% end %>
js.erbファイルを作成
js.erbファイルは以下2つの記述が可能。
1. jsの処理
2. rubyの記述(erbファイルだから)
以下のjs.erbファイルによって、画面上に表示するお気に入りボタンをAjax通信で切り替えられるようにします。
【create.js.erb
ファイルを作成】
create.js.erbでhtml()メソッドを用い、指定したセレクタのhtml部分(指定したid属性を持つ部分)を置き換える。_unfavorite.html.erb
に置き換えるよう記述。
$("#btn-favorite-<%= @board.id %>").html("<%= j(render('boards/unfavorite', board: @board)) %>");
【destroy.js.erb
ファイルを作成】
create.js.erbと逆の内容を記述する。
$("#btn-favorite-<%= @board.id %>").html("<%= j(render('boards/favorite', board: @board)) %>");
ここまでがコードの細かい話!!
お気に入りボタンを押した時のHTTPレスポンスについて
上記の実装によって、なぜお気に入りボタンをAjax通信で切り替えられるのか、その仕組みについて以下で説明します。
先に結論を述べると、それはサーバーからレスポンスボディとしてJavaScriptのコードを返し、そのコードに対する処理をクライアント側が実行してくれているからです。
HTTPレスポンスの中身とクライアントの処理は?
- お気に入りボタンを押した時のHTTPレスポンスの中身はどうなっているのか?
- それに対してクライアント(ブラウザ)側はどのような処理を行うのか?
の2点を押さえれば、お気に入りボタンをAjax通信で切り替えられる仕組みを理解できるはずです。
お気に入りボタンを押した時のHTTPレスポンスの中身は?
erbファイルをJS形式のコード(この段階ではただの文字列!)に変換したものが、レスポンスボディとしてクライアントに返されます。
つまり、erbファイルをそのままクライアントに返すのではなく、サーバー側でjs.erbファイルのrubyの記述(j render
とか@board
とか)を事前に実行し、HTMLのコードとして展開した結果を、クライアント側に返しているのです。
一言で表すなら、クライアント側が読める内容に変換してから返している、ということです。
レスポンスに対するクライアント側の処理
これに対し、クライアントはサーバーから返ってきたレスポンスボディを見て、「これはjs形式のものだな」と判断し、そこでようやくレスポンスボディの文字列に対してJavaScriptを実行してくれる、といった感じです。
検証ツールのネットワークタブを確認
- HTTPレスポンスの**Content-Type(どういうコンテンツの種類か)**が text/javascriptになっている。
→ajax通信に設定しているから、RailsがJS形式でレスポンスを送ってくれている。
→クライアント側はContent-typeを見て「JSで処理するんだな」と判断している。
- レスポンスボディにjs形式のコードが入っている。
- レスポンスボディの詳細
$("#js-favorite-button-152").replaceWith("<a id=\"js-favorite-button-152\" data-remote=\"true\" rel=\"nofollow\" data-method=\"delete\" href=\"/favorites/152\">\n <i class=\"fas fa-star\"><\/i>\n<\/a>");
おわりに
以上でremote:true形式でAjax通信を行う方法の説明を終えます。
なにか説明部分について誤りがございましたら、ご指摘頂きたく思います。