🌸
【Rails7】Turboを使って非同期でいいね機能を実装した話
自己紹介
もなかと申します。
プログラミング学習2年目の初心者です。
Ruby on Railsをメインに勉強しています。
実務は未経験です。
内容につきまして、間違いや不備がありましたらコメントで教えてください。
参考文献
いいね機能の実装
環境
Rails 7.0.8
ruby 3.2.2
Gemfile
+ gem 'turbo-rails'
modelの作成
rails g model Favorite user_id:integer post_id:integer
rails db:migrate
モデルは下記の内容を追記します。
favorite.rb
+class Favorite < ApplicationRecord
+ belongs_to :user
+ belongs_to :post
+end
user.rb
class User < ApplicationRecord
+ has_many :favorites, dependent: :destroy
end
post.rb
class Post < ApplicationRecord
+ has_many :favorites, dependent: :destroy
:
+ def favorited_by?(user)
+ favorites.exists?(user_id: user.id)
+ end
end
ルーティングの設定
いいねを作成したり、削除できるように、createとdestroyで設定します。
URLにidが表示されないようにするため、resourceを単数形で実装します。
resources :posts do
+ resource :favorites, only: %i[create destroy]
end
controllerの作成
rails g controller favorites
controllers/favorites_controller.rb
class FavoritesController < ApplicationController
before_action :require_login
def create
post = Post.find(params[:post_id])
favorite = current_user.favorites.new(post_id: post.id)
respond_to do |format|
if favorite.save
format.turbo_stream do
render turbo_stream: turbo_stream.update("post_#{post.id}_favorite", partial: 'posts/favorite', locals: { post: post })
end
else
format.html { redirect_to post, alert: 'Failed to favorite.' }
end
end
end
def destroy
favorite = current_user.favorites.find_by(post_id: params[:post_id])
post = Post.find(params[:post_id])
respond_to do |format|
if favorite.destroy
format.turbo_stream do
render turbo_stream: turbo_stream.update("post_#{post.id}_favorite", partial: 'posts/favorite', locals: { post: post })
end
else
format.html { redirect_to post, alert: 'Failed to unfavorite.' }
end
end
end
end
viewの作成
posts/_favorite.html.erb
<% if logged_in? %>
<% if post.favorited_by?(current_user) %>
<%= link_to post_favorites_path(post), data: { turbo_method: :delete } do %>
<i class="fas fa-heart text-lg" aria-hidden="true" style="color: red;"></i>
<%= post.favorites.count %>
<% end %>
<% else %>
<%= link_to post_favorites_path(post), data: { turbo_method: :post } do %>
<i class="fas fa-heart text-lg" aria-hidden="true"></i>
<%= post.favorites.count %>
<% end %>
<% end %>
<% else %>
<% end %>
views/posts/_post.html.erb
<div id="post_<%= post.id %>_favorite">
<%= render "posts/favorite", post: post %>
</div>
起きた問題
1. いいねができない。
初めに_post.html.erb
に下記のコードのみを加えたのですがうまく反映できませんでした。
views/posts/_post.html.erb
<%= render "posts/favorite", post: post %>
下記のようにdivタグで囲むことで実装ができました。
views/posts/_post.html.erb
+ <div id="post_<%= post.id %>_favorite">
<%= render "posts/favorite", post: post %>
+ </div>
2. 非同期にならない。
初めはコントローラーを下記のように書いていました。
_favorites_controller.rb
class FavoritesController < ApplicationController
before_action :require_login
def create
post = Post.find(params[:post_id])
favorite = current_user.favorites.new(post_id: post.id)
respond_to do |format|
if favorite.save
format.turbo_stream do
+ render turbo_stream: turbo_stream.prepend("post_#{post.id}_favorite", partial: 'posts/favorite', locals: { post: post })
end
else
format.html { redirect_to post, alert: 'Failed to favorite.' }
end
end
end
def destroy
favorite = current_user.favorites.find_by(post_id: params[:post_id])
post = Post.find(params[:post_id])
respond_to do |format|
if favorite.destroy
format.turbo_stream do
+ render turbo_stream: turbo_stream.replace("post_#{post.id}_favorite", partial: 'posts/favorite', locals: { post: post })
end
else
format.html { redirect_to post, alert: 'Failed to unfavorite.' }
end
end
end
end
prepend
は指定したTurboFrameの中の先頭に要素を追加します。
replace
は指定したTurboFrameを上書きします。
その結果、いいねボタンが増殖したり、ボタンを押して初めの1.2回は非同期で行えてもその後は画面上に反映されないトラブルが起きました。
そこで下記の記事のTurbo 7つのアクションを参考に修正することで思うように反映させることができました。
favorites_controller.rb
# いいね機能についてのコントローラー
class FavoritesController < ApplicationController
before_action :require_login
def create
post = Post.find(params[:post_id])
favorite = current_user.favorites.new(post_id: post.id)
respond_to do |format|
if favorite.save
format.turbo_stream do
+ render turbo_stream: turbo_stream.update("post_#{post.id}_favorite", partial: 'posts/favorite', locals: { post: post })
end
else
format.html { redirect_to post, alert: 'Failed to favorite.' }
end
end
end
def destroy
favorite = current_user.favorites.find_by(post_id: params[:post_id])
post = Post.find(params[:post_id])
respond_to do |format|
if favorite.destroy
format.turbo_stream do
+ render turbo_stream: turbo_stream.update("post_#{post.id}_favorite", partial: 'posts/favorite', locals: { post: post })
end
else
format.html { redirect_to post, alert: 'Failed to unfavorite.' }
end
end
end
end
作成したアプリ
最後に
最後まで記事を見ていただきありがとうございます。
分かりにくいコードであったり、説明不足の部分もあったかと思います。
これからも勉学に励んでいきたいと思います。
誤り等あればコメントをお願いいたします。
Discussion