Ryota400’s blog

エンジニアを目指して書いてます。

ブックマーク機能(お気に入り登録)の追加

モデルの作成

rails g model Bookmark user:references board:references

referencesを使用することで、モデル間の関連付けであるbelongs_toを自動で追加してくれます。今回はUserモデルとBoardモデルにBookmarkモデルがbelongs_toで紐づいているという状況だ。

マイグレーションファイル

class CreateBookmarks < ActiveRecord::Migration[5.2]
  def change
    create_table :bookmarks do |t|
      t.references :user, foreign_key: true
      t.references :board, foreign_key: true

      t.timestamps
    end
    # bookmarksテーブルに置いてuser_idとboard_idの組み合わせを一意性のあるものしている。
    add_index  :bookmarks, [:user_id, :board_id], unique: true
  end
end

bookmark.rb

class Bookmark < ApplicationRecord
  # belongs_toは対象カラムに対するpresence: trueは自動で設定されている。
  belongs_to :user # 外部キー user_id
  belongs_to :board  #  外部キー board_id

  # user_id と board_idの組み合わせを一意性のあるものにしている
  validates :user_id, uniqueness: { scope: :board_id }
end

belongs_to オプションを設定した場合に、対象カラムに対する presence: true は自動で設定されるので不要となる。 アソシエーションもしくはscope/joinを使ってブックマークした掲示板の一覧を取得していること

user.rb

class User < ApplicationRecord

  # ここからが他のモデルとの関係性
  has_many :boards, dependent: :destroy
  has_many :comments, dependent: :destroy
  has_many :bookmarks, dependent: :destroy
  # userのidを入れて、bookmarksメソッドを入れて、それぞれのboardを出す
  # 下記の記述はuser.bookmarks.map(&:board)これをしているのと一緒
  has_many :bookmark_boards, through: :bookmarks, source: :board

  # 引数に渡されたものが、userのものであるか?
  def own?(object)
    id == object.user_id
  end

  # 引数に渡されたboardがブックマークされているか?
  def bookmark?(board)
    bookmark_boards.include?(board)
  end

 # board_idを入れてブックマークしてください
  def bookmark(board)
    # current_userがブックマークしているboardの配列にboardを入れる
    bookmark_boards << board
  end

  # 引数のboardのidをもつ、レコードを削除してください
  def unbookmark(board)
    bookmark_boards.destroy(board)
  end

end

board.rb

class Board < ApplicationRecord

  belongs_to :user
  has_many :bookmarks, dependent: :destroy

end

ルーティング設定

resources :boards do
    resources :comments, only: %i[create]
   # /boards/bookmarksのURLを作っている。このURLのブックマークの一覧を表示する。
    collection do
      get :bookmarks
    end
end
# ブックマークのcreateアクションとdestroyアクション
 resources :bookmarks, only: %i[create destroy]

コントローラーの作成

class BookmarksController < ApplicationController
  def create
    board = Board.find(params[:board_id])
    current_user.bookmark(board)
    redirect_back fallback_location: root_path, success: t('defaults.message.bookmark')
  end

  def destroy
    board = current_user.bookmarks.find(params[:id]).board
    current_user.unbookmark(board)
    redirect_back fallback_location: root_path, success: t('defaults.message.unbookmark')
  end
end
def bookmarks
    @bookmark_boards = current_user.bookmark_boards.includes(:user).order(created_at: :desc)
end

無駄にSQLを発行させるN + 1 問題を防ぐために、includes(:user)として、関連するユーザー情報も取得する。

Viewの作成

<% if current_user.bookmark?(board)  %>
  <%# bookmarkしていれば、ブックマークしているボタンのとこへ %>
  <%= render 'unbookmark', board: board %>
<% else  %>
  <%# bookmarkしていなければ、ブックマークしていないボタンのとこへ %>
  <%= render 'bookmark', board: board %>
<% end %>

ブックマークしていない場合

<%= link_to bookmarks_path(board_id: board.id), id: "js-bookmark-button-for-board-#{board.id}", class:"float-right", method: :post do %>
  <%= icon 'far', 'star' %>
<% end %>

ブックマークされていないので、表示は☆ボタンを表示しています。link_toで囲んでbookmarksコントローラーのcreateアクションに遷移するpathが記述されています。つまり、☆ボタンを押すとbookmarksコントローラのcreateアクション動きます。

ブックマークしている場合

<%= link_to bookmark_path(current_user.bookmarks.find_by(board_id: board.id)), id: "js-bookmark-button-for-board-#{board.id}", class:"float-right", method: :delete do %>
  <%= icon 'fas', 'star' %>
<% end %>

ブックマークしていない場合と逆になり、色あり星ボタンを表示し、destroyコントローラが動くpathが記載されている。

<% if current_user.bookmark?(board) %>
  <%= render 'unbookmark', { board: board } %>
<% else %>
  <%= render 'bookmark', { board: board } %>
<% end %>

参考文献

qiita.com

qiita.com

qiita.com

study-diary.hatenadiary.jp