【Rails】お気に入り機能を非同期(Ajax)にする

Ruby on Railsアイキャッチ画像

目標物がこちら

お気に入り機能と言いつつ、このアプリ内では、「プロジェクト登録機能」になっていますので、favoriteという名前にはなっていない点にご注意ください。

非同期でないお気に入り機能を比較対象に、変更前のコードとして記載しています。

routes

変更前

Rails.application.routes.draw do
  resources :products, except: [:index]
  resources :user_products, only: %i[create destroy]
end

変更後

productsにネストして、ルーティングはshallowにしています。

Rails.application.routes.draw do
  resources :products, except: [:index] do
    resources :user_products, only: %i[create destroy], shallow: true
  end
end

view

変更前

<% if @user_product.present? %>
  <%= link_to user_product_path(id: @user_product.id), method: :delete do %>
  <div class="project_cancel">
    <div class="project_cancel_icon">
      <i class="fas fa-star fa-lg"></i>
    </div>
    <% end %>
    <div class="project_cancel_text">
      プロジェクトから解除する
    </div>
  </div>
<% else %>
  <%= link_to user_products_path(product_id: @product.id), method: :post do %>
  <div class="project_registration">
    <div class="project_registration_icon">
      <i class="far fa-star fa-lg"></i>
    </div>
    <% end %>
    <div class="project_registration_text">
      プロジェクトに登録する
    </div>
  </div>
<% end %>

変更後

user_products/registerファイルにレンダリングさせ、id=”registered-btn-<%= @product.id %>”の部分を書き換える。

<div id="registered-btn-<%= @product.id %>">
  <%= render partial: 'user_products/register', locals: { product: @product } %>
</div>

変更前にこの位置に記述していたコードはレンダリングファイルに移動する。

<% if current_user.registered?(product) %>
  <%= link_to user_product_path(product), method: :delete, remote: true do %>
  <div class="project_cancel">
    <div class="project_cancel_icon">
      <i class="fas fa-star fa-lg"></i>
    </div>
  <% end %>
    <div class="project_cancel_text">
      プロジェクトから解除する
    </div>
  </div>
<% else %>
  <%= link_to product_user_products_path(product), method: :post, remote: true do %>
  <div class="project_registration">
    <div class="project_registration_icon">
      <i class="far fa-star fa-lg"></i>
    </div>
  <% end %>
    <div class="project_registration_text">
      プロジェクトに登録する
    </div>
  </div>
<% end %>

ifの条件式以外はもともと、products/show.html.erbに記述していたコード

ceateとdestroyそれぞれのjsファイルを作成

$('#registered-btn-<%= @product.id %>').html("<%= j(render partial: 'user_products/register', locals: {product: @product}) %>");

$('#registered-btn-<%= @product.id %>').html("<%= j(render partial: 'user_products/register', locals: {product: @product}) %>");

model

registered?(product)はmodelで定義

class User < ApplicationRecord
  has_many :products, dependent: :destroy
  has_many :user_products, dependent: :destroy
  has_many :registered_products, through: :user_products, source: :product

  def registered(product)
    registered_products << product
  end

  def unregistered(product)
    registered_products.destroy(product)
  end

  def registered?(product)
    user_products.where(product_id: product.id).exists?
  end
end

これで、「登録プロジェクト」に登録されているかどうか判定する。

1productに対し、userは1回しか登録ができないようにする制約を設ける。

正直、今回の場合は不要。

いいね!機能一人一回しかいいね!ができないようにするときなどに必要となってくる。

class UserProduct < ApplicationRecord
  belongs_to :user
  belongs_to :product

  validates :user_id, uniqueness: { scope: :product_id }
end

controller

変更前

class ProductsController < ApplicationController
  def show
    @user_product = current_user.user_products.find_by(product_id: @product.id)
  end
end

非同期処理により上記の記述が不要となる。

class UserProductsController < ApplicationController
  def create
    user_product = current_user.user_products.create(product_id: params[:product_id])
    redirect_to registered_projects_path, notice: "#{user_product.product.name}を「★登録プロジェクト」に登録しました"
  end

  def destroy
    user_product = current_user.user_products.find_by(id: params[:id]).destroy
    redirect_to homes_path, notice: "#{user_product.product.name}を「★登録プロジェクト」から解除しました"
  end
end

変更後

class UserProductsController < ApplicationController
  def create
    @product = Product.find(params[:product_id])
    current_user.registered(@product)
  end

  def destroy
    @product = current_user.registered_products.find(params[:id])
    current_user.unregistered(@product)
  end
end

参考にした記事がこちら

【Rails】remote:true形式でAjax通信を行う(お気に入り登録機能のajax化) - Qiita
AjaxとはAjaxとは、Webブラウザ上で非同期通信を行い、ページ全体の再読み込み無しにページを更新する方法のことです。#####同期通信について同期通信では、クライアントはwebページ全体…

コメント