お気に入り登録を実装(非同期通信)


現在RubyとJavaScriptを用いて記事投稿サイトを作成しています。
今回は非同期通信のお気に入り登録を実装します。
お気に入り記事一覧ページの実装までを書いていきたいと思います。

■仕様等
・お気に入りアイコンをクリックで登録/解除
・jQueryを使用しない

■完成後の動作

■お気に入り機能

①Userモデルにお気に入り操作に関するメソッドを記述

bookmarkモデル、コントローラーを作成した後、
お気に入り操作に関するメソッドをuserモデルに追記していきます。
※bookmarksテーブルのカラムはuser_idとarticle_idです。

user.rb
         has_many :bookmarks_articles, through: :bookmarks, source: :article
         def bookmark(article)
           bookmarks_articles << article
         end

         def unbookmark(article)
           bookmarks_articles.delete(article)
         end

         def bookmark?(article)
           bookmarks_articles.include?(article)
         end

1行目
has_many :bookmarks_articles, through: :bookmarks, source: :article
ユーザーがお気に入り登録した記事を全て取得します。
2行目〜4行目
bookmark(article)
引数に渡された記事情報を1行目のbookmarks_articlesに追加します。
6行目〜8行目
unbookmark(article)
引数に渡された記事情報を1行目のbookmarks_articlesから削除します。
10行目〜12行目
bookmark?(article)
引数に渡された記事情報が1行目のbookmarks_articlesに存在するかを判定します。

②ルーティングの設定

routes.rb
Rails.application.routes.draw do
  devise_for :users
  root to: "articles#index"
  resources :bookmarks, only: [:create, :destroy]
end

3行目
resources :bookmarks, only: [:create, :destroy]
お気に入りの登録と解除を実装するので、createとdestroyを指定します。

③コントローラーの設定

bookmarks_controller.rb
class BookmarksController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    current_user.bookmark(@article)
  end

  def destroy
    @article = current_user.bookmarks.find_by(id: params[:id]).article
    current_user.unbookmark(@article)
  end
end

4行目/9行目
current_user.bookmark(@article)
current_user.unbookmark(@article)
userモデルで設定した各メソッドの引数に、対象の記事情報を渡しています。

④ビューの記述

index.html
    <% @articles.each do |article| %>
      <div class="article-content" id="article_<%= article.id %>">
        <%= render 'shared/articles', article: article %>
      </div>
    <% end %>
_articles.html
<% if user_signed_in? %>
  <% if current_user.bookmark?(article) %>
    <%= render 'bookmarks/unbookmark', article: article %>
  <% else %>
    <%= render 'bookmarks/bookmark', article: article %>
  <% end %>
<% end %>

■非同期処理の設定

_bookmark.html
<%= link_to bookmarks_path(article_id: article.id), class: "bookmark", id: "js-bookmark-button-for-article-#{article.id}", method: :post, remote: true do %>
<% end %> 
_unbookmark.html
<%= link_to bookmark_path(current_user.bookmarks.find_by(article_id: article.id)), class: "bookmark", id: "js-bookmark-button-for-article-#{article.id}", method: :delete, remote: true do %>
<% end %> 

各1行目
<%= link_to bookmarks_path(article_id: article.id), class: "bookmark", id: "js-bookmark-button-for-article-#{article.id}", method: :post, remote: true do %>
<%= link_to bookmark_path(current_user.bookmarks.find_by(article_id: article.id)), class: "bookmark", id: "js-bookmark-button-for-article-#{article.id}", method: :delete, remote: true do %>
各末尾のオプションに注目してください。
remote:trueを設定することでJavaScript形式のリクエストが送信されます。
送信後、コントローラーのcreate/destroyアクションが実行されると、
create.js.erb/destroy.js.erbに遷移します。

create.js.erb
document.getElementById('article_<%= @article.id %>').innerHTML = '<%= escape_javascript( render 'shared/articles', article: @article ) %>'
destroy.js.erb
document.getElementById('article_<%= @article.id %>').innerHTML = '<%= escape_javascript( render 'shared/articles', article: @article ) %>'

対象の記事を取得して、index.htmlにもあるrenderの部分を挿入しています。
escape_javascriptは改行のエスケープ処理を行います。

以上でお気に入り登録を実装することができました。

■お気に入り一覧表示ページの実装

①ルーティングの設定

routes.rb
Rails.application.routes.draw do
  devise_for :users
  root to: "articles#index"
  resources :articles do
    collection do
      get :bookmarks
    end
  end
  resources :bookmarks, only: [:create, :destroy]
end

4行目〜8行目
:articlesにネストする形でcollection do :bookmarks endを記述します。

②コントローラーの設定

articles_controller.rb
  def bookmarks
    @bookmarks_articles =
    current_user.bookmarks_articles.includes(:user).order('created_at DESC')
  end

current_user.bookmarks_articles
現在のユーザーの全てのお気に入り記事を取得しています。(冒頭のUserモデルにて設定)

③ビューを記述

bookmark.html.erb
    <% @bookmarks_articles.each do |article| %>
      <div class="article-content" id="article_<%= article.id %>">
        <%= render 'shared/articles', article: article %>
      </div>
    <% end %>

インスタンス変数を受け取って一覧を表示しています。

以上でお気に入り記事一覧表示ページの完成です。

参考記事

https://miiina01220.hatenablog.com/entry/2020/11/20/172739
https://techtechmedia.com/favorite-function-rails/
https://qiita.com/naberina/items/c6b5c8d7756cb882fb20