はじめに
Rialsアプリに非同期通信のいいね機能を実装しました。
方針としては、部分テンプレートを作成してレスポンスをJS形式で返すことで画面の一部のみを更新するというものです。備忘録としてまとめたいと思います。
実装イメージ
- 非同期でいいねといいねの解除ができる
- いいねされたらカウントに反映される
開発環境
- macOS Catalina
- Ruby 2.6.5
- Ruby on Rails 5.2
前提
- userとpost(投稿)モデルを作成済み
- fontawesomeを導入済み
- jQueryを導入済み
目次
1.モデルとアソシエーション
2.ルーティング
3.コントローラー
4.view
1. モデルとアソシエーション
モデルとアソシエーションの構成は以下の通りです。
$ rails g model favorite user:references post:references
$ rails db:migrate
次にアソシエーションを組みます。
has_many :posts, dependent: :destroy
has_many :favorites, dependent: :destroy
# すでにいいねしているかを判定するメソッド
def already_favorited?(post)
self.favorites.exists?(post_id: post.id)
end
already_favorited?
メソッドを定義します。
これはもし投稿にいいねしていたら解除のリンクを表示させ、解除していたらいいねリンクを表示させる条件分岐のために記述しています。
belongs_to :user
has_many :favorites, dependent: :destroy
belongs_to :user
belongs_to :post
2. ルーティング
いいねは投稿ひとつひとつに紐付いているのでルーティングはネストさせて記述します。
ネストさせることでアソシエーション先のレコードのidをparamsに追加してコントローラーに渡せるようになります。
(今回の場合はfavoriteのidを取得できる)
resources :posts, only: [:index, :new, :create, :show, :destroy] do
resource :favorites, only: [:create, :destroy]
end
3. コントローラー
favorites_controller.rbではcreateとdestroyアクションを定義します。
class FavoritesController < ApplicationController
before_action :set_post
def create
@favorite = Favorite.create(user_id: current_user.id, post_id: @post.id)
@favorite.save
end
def destroy
@favorite = Favorite.find_by(user_id: current_user.id, post_id: @post.id)
@favorite.destroy
end
private
def set_post
@post = Post.find(params[:post_id])
end
end
set_post
・before_actionを設定することで、アクション実行前にどの投稿に対するものなのかを判断するためにidを取得します。
def show
@post= Post.find(params[:id])
end
4. view
非同期通信をするために_favorite.html.erb
という部分テンプレートを作成します。
更にcreateとdestroyアクション実行時にページの一部を更新するためにcreate.js.erb
とdestroy.js.erb
というファイルを作成します。
JS形式でレスポンスするためにjs.erb
という拡張子になっています。
ディレクトリ構成は以下の通りです。
views
|-posts
| |-show.html.erb
|-favorites
|-_favorite.html.erb
|-create.js.erb
|-destroy.js.erb
まずは、投稿詳細ページのviewから記述します。
<div id="favorite_area_<%= post.id %>", class="favoriteArea">
<%= render partial: "favorites/favorite", locals: { post: @post } %>
</div>
divタグにfavorite_area_<%= post.id %>
というidを付与しています。
これはどの投稿に対するものなのかを判別するために記述しています。
またrenderメソッドで_favorite.html.erb
ファイルを呼び出しています。
localsオプションではpostコントローラーで定義した@post
の変数を部分テンプレートのなかでpost
として使用できるように定義しています。
次に部分テンプレートです。
<% if current_user.already_favorited?(post) %>
<%= link_to post_favorites_path(post), method: :delete, class: "goodLink", remote: true do %>
<i class="fas fa-heart"></i>
<% end %>
<% else %>
<%= link_to post_favorites_path(post), method: :post, class: "goodLink", remote: true do %>
<i class="far fa-heart"></i>
<% end %>
<% end %>
<p class="favoriteCount"><%= post.favorites.count %></p>
user.rb
で定義したalready_favorited?
メソッドを使用しています。
link_toにはremote: ture
を記述することで非同期通信をしますという意味になります。これでHTML形式ではな先程作成したjs.erb
ファイルを返す挙動になります。
最後の行の<%= post.favorites.count %>
はcountメソッドを使用していいねされている数を表示しています。
最後にjs.erbファイルの編集です。
<!-- #favorite_area_<%= @post.id %>この部分のHTMLだけ、renderで部分的に更新するという処理 -->
$("#favorite_area_<%= @post.id %>").html("<%= j(render partial: 'favorites/favorite', locals: { post: @post }) %>");
<!-- #favorite_area_<%= @post.id %>この部分のHTMLだけ、renderで部分的に更新するという処理 -->
$("#favorite_area_<%= @post.id %>").html("<%= j(render partial: 'favorites/favorite', locals: { post: @post }) %>");
意味としてはfavorite_area_<%= @post.id %>
というidを付与したdivタグの中身を_favorite.html.erb
の内容で更新するという内容になります。
以上で非同期のいいね機能を実装できていると思います!