17
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Rails】remote:true形式でAjax通信を行う(お気に入り登録機能のajax化)

Last updated at Posted at 2020-05-16

Ajaxとは

Ajaxとは、Webブラウザ上で非同期通信を行い、ページ全体の再読み込み無しにページを更新する方法のことです。

#####同期通信について

同期通信では、クライアントはwebページ全体の情報(HTMLとそれに紐づくcss,js,imageなどのアセット)をサーバーから受け取って、ページを一から作り直します。
例えばページの一部を変更するだけなのに、他の部分も組み立て直すってことはその分ページの表示に時間がかかっちゃいます。(サーバー側の処理を待つことになる)

しかも、このリクエスト〜レスポンスの処理を行っている間は、他の処理を行わずにサーバーからレスポンスが返ってくるのを待ち続ける必要があります(よくあるのが画面が真っ白になって何もできない状態)。

そこでAjaxのような非同期通信を使用すれば、ページ遷移無しに、高速で更新処理を行い、尚且つ、リクエスト〜レスポンスの処理を行っている間も他の処理が行えます

######非同期通信の方法は2種類

この便利なAjaxによる非同期通信を行う方法としては、
①remote:true形式
②ajax関数を使った形式

の2パターンが存在しますが、今回はremote:true形式について以下に記していきます。

仕組みだけ知りたいよって方は、コードの説明は読み飛ばしちゃっても大丈夫です。

コードの説明

今回作るもの

掲示板のお気に入り(いいね)ボタンを押した時に、お気に入りの登録、解除を行うという仕組みをajax化させていきます。

ルーティングの設定

お気に入りの登録、削除を行うために必要なルーティングの設定を行う。

config/routes.rb
resources :boards do
  resources :favorites, only: %i[create destroy], shallow: true
end

モデルの設定

モデルでUsersテーブル、Boardsテーブル、Favoritesテーブルの関連付を行う。

簡素なER図を書くとこんな感じ。
スクリーンショット 2020-05-16 14.13.29.png

app/models/user.rb
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
app/models/board.rb
class Board < ApplicationRecord
  belongs_to :user
  has_many :favorites, dependent: :destroy
end
app/models/favorite.rb
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のコードに変換した文字列が、レスポンスボディとしてブラウザに返される(詳細は後述)。

app/controllers/favorites_controller.rb
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を呼び出す。
    • お気に入りボタンは色付きの状態
    • お気に入りを削除する機能
app/views/favorites/_favorite_area.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オプションを付与。
app/views/favorites/_favorite.html.erb
<%= link_to board_favorites_path(board), id: "favorite-button-#{board.id}", method: :post, remote: true do %>
  <%= icon 'far', 'star' %>
<% end %>

お気に入りしている場合のボタンを実装

_unfavorite.html.erbファイルを作成

  • お気に入りを削除するので、HTTPメソッドはdeletedestroy.js.erbを呼び出す。
  • id属性を付与。
  • remote: trueオプションを付与。
app/views/favorites/_unfavorite.html.erb
<%= 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に置き換えるよう記述。

app/views/favorites/create.js.erb
$("#btn-favorite-<%= @board.id %>").html("<%= j(render('boards/unfavorite', board: @board)) %>");

destroy.js.erbファイルを作成】
create.js.erbと逆の内容を記述する。

app/views/favorites/destroy.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で処理するんだな」と判断している。
スクリーンショット 2020-05-03 22.11.16.png
  • レスポンスボディにjs形式のコードが入っている。
スクリーンショット 2020-05-03 22.10.46.png
  • レスポンスボディの詳細
$("#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通信を行う方法の説明を終えます。
なにか説明部分について誤りがございましたら、ご指摘頂きたく思います。

17
12
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
17
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?