🐻

【Ruby on Rails】【実装途中】複数のいいね機能の非同期処理に関して

2024/07/08に公開

はじめに

現在Ruby on Railsで5つのいいね機能の実装及び、その非同期処理化に取り組んでいます。
下記は複数のいいね機能の同期処理になっており、これをTurbo Streamsで非同期化したいと考えています。

Image from Gyazo

・ 複数のいいね機能の同期処理
・ 1つのいいね機能での非同期処理
までは実装でき挙動も確認できていますが、表題の「複数のいいね機能×そのいいね機能の非同期処理化」が、現時点で実装途中になります。

実装途中ではありますが自分の中で情報を整理するため、また同じような悩みを抱えていらっしゃる方がいればその方の助けになればと思い、この記事を書いております。
表記が解決したら、改めて記事にまとめたいと思っています。
間違い等ございましたら、優しくご指摘いただけますと幸いです。

環境

Rails 7.1.3.4
Bootstrapアイコン
turbo-rails2.0.5

実装過程

1,複数のいいね機能の実装(同期処理)

最初は、同期処理での複数のいいね機能の実装しました。
同期処理での複数のいいね機能実装では、「1つのボタンを押すと他ボタンもいいねが押され、数も全部同じカウントになってしまう」という現象に悩まされました。
ここでは検証ツールで状況確認し、いいねボタンに同じIDが振られてるからだ気がつきました。
上記を受け、いいねテーブルにfavorite_typeカラムを追加し、同じIDが割り振られないようにすることで、
同期処理での複数のいいね機能の実装はできました。

変更前:
Image from Gyazo

変更後:
Image from Gyazo

同期処理での複数のいいね機能の実装コード
記事詳細画面にいいねボタンを表示させるため、shared配下にパーシャルファイルを作成し、ログイン中のユーザーがいいねボタンを押した時の条件分岐の処理を_favorite.html.erbに記載しています。
(本来であれば、shared配下は不適切かと思いますが、このコード作成時はshared配下に作ってしまっていました...)

  • app/controllers/favorites_controller.rb
class FavoritesController < ApplicationController
  def create
    @favorite = current_user.favorites.new(article_id: params[:article_id], favorite_type: params[:type])
    if @favorite.save
      redirect_to article_path(params[:article_id])
    end
  end

  def destroy
    @favorite = current_user.favorites.find_by(article_id: params[:article_id], favorite_type: params[:type])
    if @favorite.destroy!
      redirect_to article_path(params[:article_id]), status: :see_other
    end
end
end
  • app/views/articles/show.html.erb
<%= render 'shared/favorite' %>
  • app/views/shared/_favorite.html.erb
<!-------------1つ目のいいねボタン------------->
<div class="tooltip" data-tip="参考になった!">
<% if logged_in? %>
<% if @article.favorited?(current_user, :like_first) %>
      <%= link_to article_favorite_path(@article.id, type: :like_first), data: { turbo_method: :delete }, class: "unlike-button-1" do %>
        <i class="bi bi-arrow-through-heart-fill"></i>
        <%= @article.favorites.where(favorite_type: 'like_first').count %>
     <% end %>
  <% else %>
      <%= link_to article_favorites_path(@article.id, type: :like_first), data: { turbo_method: :post }, class: "like-button-1" do %>
        <i class="bi bi-arrow-through-heart"></i>
        <%= @article.favorites.where(favorite_type: 'like_first').count %>
      <% end %>
  <% end %>
  <% else %>
    <%= link_to login_path, class: "like-button-1" do %>
      <i class="bi bi-arrow-through-heart"></i>
      <%= @article.favorites.where(favorite_type: 'like_first').count %>
    <% end %>
  <% end %>
</div>
<!-------------1つ目のいいねボタン------------->
<!-------------2つ目のいいねボタン------------->
<div class="tooltip" data-tip="学びが深まった">
<% if logged_in? %>
  <% if @article.favorited?(current_user, :like_second) %>
     <%= link_to article_favorite_path(@article.id, type: :like_second), data: { turbo_method: :delete }, class: "unlike-button-2" do %>
       <i class="bi bi-award-fill"></i>
       <%= @article.favorites.where(favorite_type: 'like_second').count %>
     <% end %>
  <% else %>
     <%= link_to article_favorites_path(@article.id, type: :like_second), data: { turbo_method: :post }, class: "like-button-2" do %>
       <i class="bi bi-award"></i>
       <%= @article.favorites.where(favorite_type: 'like_second').count %>
      <% end %>
  <% end %>
<% else %>
    <%= link_to login_path, class: "like-button-1" do %>
      <i class="bi bi-award"></i>
      <%= @article.favorites.where(favorite_type: 'like_second').count %>
    <% end %>
  <% end %>
  </div>
<!-------------2つ目のいいねボタン------------->

- 以下、3つ目以降のいいねボタンは上記と同じコードのため、省略 -

  • app/models/article.rb
def favorited?(user, type)
    favorites.where(user_id: user.id, favorite_type: type).exists?
  end
2,1つのいいね機能の実装(非同期処理)

複数のいいね処理が実装できた後、そのまま複数のいいね機能の非同期処理に取り掛かりましたが、再度1つのいいねボタンを押すと、他のいいねボタンも押されてしまう現象に悩まされました...。
原因究明のため、一旦1つのいいねボタンで非同期処理ができるかを確認しました。
下記がそのコードになります。余談ですが、確認の意味でもコミットはこまめに行うべきだなと感じました。

非同期処理での1つのいいね機能の実装コード
こちらの記事を参考にしながら、実装を進めました。
turbo_streamのreplaceを使用して、いいねボタンの <i class="bi bi-arrow-through-heart"></i> <i class="bi bi-arrow-through-heart-fill"></i>の入れ替えを行っています。
favoritesコントローラーのrender :createrender :destroyは記載しなくてもビューからturbo_streamのリクエストを送れていれば、アクション名と対応するaction名.turbo_stream.erbファイル(今回は、create.turbo_stream.erbとdestroy.turbo_stream.erb)を探してくれるとのことですが、今回は一旦記載しております。

非同期化するにあたり、同期処理での複数のいいね機能の実装コードでの、app/views/shared/_favorite.html.erbの中身、いいねボタンを押された状態/押されていない状態の条件分岐はやめ、app/views/articles/_first_favorite.html.erbと、app/views/articles/_first_unfavorite.html.erbという形でパーシャルファイルを分けました。
(create.turbo_stream.erbとdestroy.turbo_stream.erbファイル内で、renderでそれぞれのパーシャルファイルを呼び出すため)

  • app/controllers/favorites_controller.rb
class FavoritesController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @favorite = current_user.favorites.new(article_id: params[:article_id], favorite_type: params[:type])
    if @favorite.save
    render :create
    end
  end

  def destroy
    @article = Article.find(params[:article_id])
    @favorite = current_user.favorites.find_by(article_id: params[:article_id], favorite_type: params[:type])
    if @favorite.destroy!
      render :destroy
    end
  end
end
  • app/views/articles/show.html.erb
<%= render 'articles/favorite', article: @article, favorite: @favorite %>
  • app/views/articles/_favorite.html.erb
<% if logged_in? %>
  <% if @article.favorited?(current_user, :like_first) %>
    <%= render 'articles/first_unfavorite', article: @article %>
  <% else %>
    <%= render 'articles/first_favorite', article: @article %>
  <% end %>
  <% end %>
</div>
  • app/views/articles/_first_favorite.html.erb
<%= link_to article_favorites_path(@article.id, type: :like_first), data: { turbo_method: :post, turbo_stream: true }, id: "first-favorite-#{@article.id}",class: "like-button-1" do %>
  <i class="bi bi-arrow-through-heart"></i>
  <%= @article.favorites.where(favorite_type: 'like_first').count %>
<% end %>
  • app/views/articles/_first_unfavorite.html.erb
<div class="tooltip" data-tip="参考になった!">
  <%= link_to article_favorite_path(article, @article.id, type: :like_first), data: { turbo_method: :delete }, id: "first-unfavorite-#{@article.id}", class: "unlike-button-1" do %>
    <i class="bi bi-arrow-through-heart-fill"></i>
    <%= @article.favorites.where(favorite_type: 'like_first').count %>
  <% end %>
</div>
  • app/views/favorites/create.turbo_stream.erb
<%= turbo_stream.replace "first-favorite-#{@article.id}", class: "like-button-1" do %>
  <%= render 'articles/first_unfavorite', article: @article %>
<% end %>
  • app/views/favorites/destroy.turbo_stream.erb
<%= turbo_stream.replace "first-unfavorite-#{@article.id}", class: "like-unbutton-1" do %>
  <%= render 'articles/first_favorite', article: @article %>
<% end %>
3,複数のいいね機能の実装(非同期処理) 、現時点で実装途中となります

ここからが大変でした...。
下記試したことになります。

  • app/views/articles/_first_favorite.html、app/views/articles/_first_unfavorite.html.erbと同様、他4ついいねボタン用のパーシャルファイルの作成、app/views/favorites/create.turbo_stream.erb、app/views/favorites/destroy.turbo_stream.erbファイルへ、同じようにturbo_streamでの処理を記載。
    -> 1つのいいねボタンを押すと、他の4つのいいねボタンの色も変化してしまう。

  • app/controllers/favorites_controller.rbのcreate, destroyアクションそれぞれで、favorite_typeによって色を変化させるよう条件分岐させる。
    -> こちらも1つのいいねボタンを押すと、他の4つのいいねボタンの色も変化してしまう。

現状をどう打開したか
  • 1つのいいね機能の非同期処理と、複数のいいね機能の非同期処理の時のサーバーログの比較
    -> ここでは特に収穫なし...

  • 「turbo rails multiple」のような形で検索をかけたところ、こちらの記事を発見。
    -> そもそも、turbo_streamのアクションが異なるかも?と思い先ほどの記事の、他のアクションを試してみました。
    しかし、他のアクションでも処理はうまくいかず、、、。
    検証ツールも改めて確認。今度は、「turbo rails fetch」のような検索をかけました。すると、こちらの記事に、

基本のメソッドはターゲット指定にid属性を使うため1つしか対象に取れない。一方all系のメソッドではターゲット指定にCSSクエリセレクタを使うため、複数の要素を対象に取れる。

との記載を発見!
早速、replaceをreplace_allへ、またtargets属性を指定してみました。

非同期処理での複数のいいね機能の実装コード
上記より変更したファイルのみ記載しています。

  • app/views/favorites/create.turbo_stream.erb
<%= turbo_stream.replace_all "like-button-1", targets: "first-favorite-#{@article.id}" do %>
  <%= render 'articles/first_unfavorite', article: @article %>
<% end %>

<%= turbo_stream.replace_all "like-button-2", targets: "second-favorite-#{@article.id}" do %>
  <%= render 'articles/second_unfavorite', article: @article %>
<% end %>
  • app/views/favorites/destroy.turbo_stream.erb
<%= turbo_stream.replace_all "unlike-button-1", targets: "first-unfavorite-#{@article.id}" do %>
  <%= render 'articles/first_favorite', article: @article %>
<% end %>

<%= turbo_stream.replace_all "unlike-button-2", targets: "second-unfavorite-#{@article.id}" do %>
  <%= render 'articles/second_favorite', article: @article %>
<% end %>
  • app/views/articles/_first_favorite.html.erb
<%= link_to article_favorites_path(@article.id, type: :like_first), data: { turbo_method: :post, turbo_stream: true }, targets: "first-favorite-#{@article.id}", class: "like-button-1" do %>
  <i class="bi bi-arrow-through-heart"></i>
  <%= @article.favorites.where(favorite_type: 'like_first').count %>
<% end %>
  • app/views/articles/_unfirst_favorite.html.erb
<div class="tooltip" data-tip="参考になった!">
  <%= link_to article_favorite_path(article, @article.id, type: :like_first), data: { turbo_method: :delete }, targets: "first-unfavorite-#{@article.id}", class: "unlike-button-1" do %>
    <i class="bi bi-arrow-through-heart-fill"></i>
    <%= @article.favorites.where(favorite_type: 'like_first').count %>
  <% end %>
</div>
結果どうなったか

CSSクエリセレクタを加えたことで、其々のいいねボタンの色が変更するようになりました!
しかし、今度はいいねボタンを押した後、一回リロードしないと色が変わらない事態が発生...。
まだここの原因究明はできていないので、ここから確認したいと思います。

  • ボタンを押してもその場で色が変わらないため、毎回リロードしています
    Image from Gyazo

  • 一回いいねボタンを押した後、リロードした時のネットワークタブの状況
    いいねを押した段階ではturboで処理が動いているようですが、リロードした際に通常のHTTPリクエストが行われているようです...
    Image from Gyazo

実装途中であり至らない点が多々あると思いますが、自分の備忘録のためにも記載しました。
ありがとうございました。

参考

Discussion