ブックマーク機能の実装について

どうもどハマりエンジニアです。

 

ブックマーク機能の実装を行っていきます。既にコメント機能まで完成しています。

 

 

 

①bookmarkモデルの作成

 

②bookmarksコントローラの作成。

 

③各モデルに関連付けを行う

 

(bookmark.rb)

class Bookmark < ApplicationRecord
belongs_to :board
belongs_to :user
validates :user_id, uniqueness: { scope: :board_id }
end
validates :user_id, uniqueness: { scope: :board_id }を行うことでuser_idとboard_idの一意性が保たれるため、1人のユーザーは1つの掲示板につき一回しかブックマークが出来なくなる。
 
(board.rb)
has_many :bookmarks, dependent: :destroy
has_many :users, through: :bookmarks
 
def bookmark_by?(user)
bookmarks.where(user_id: user.id).exists?
end

bookmard_by?(user)メソッドはviewのところ、ブックマークをすでに行っているかいないかを確認をするときに使う。

 

(user.rb)

has_many :bookmarks, dependent: :destroy
has_many :bookmark_boards, through: :bookmarks, source: :board

二つ目のhas_manyは既に掲示板とユーザー情報をつなげた時にboardsという名前を使ってしまっているため、適当な名前を新たにつけて上げる必要がある。新たに名前をつけたので、source: :boardをつけてあげることでこれはboardとつながっているよと示すことができる。

 

(bookmarks.rb)マイグレーション

class CreateBookmarks < ActiveRecord::Migration[5.2]
def change
create_table :bookmarks do |t|
t.references :board, foreign_key: true
t.references :user, foreign_key: true
t.timestamps
t.index [:user_id, :board_id], unique: true
end
end
end

t.index [:user_id, :board_id], unique: true

上記は先ほどbookmarkモデルでしたようにデータベースでも一意性を保つために行う。

 

④ルーティングの設定

<routes.rb>

resources :boards, shallow: true do
resources :comments, only: %i[create]
resource :bookmarks, only: %i[create destroy]
get :bookmarks, on: :collection

get :bookmarks, on: :collection

ブックマーク一覧ページをboards/bookmarksにルーティンングを設定したいのでcollectionオプションを使用する。
ネスト(入れ子)すると外部キーのboard_idを取得するのに容易になります。また単数形のメソッドであるresourceとしたのはbookmarkのidが必要ないため。

 

⑤各コントローラーの設定

(bookmarks_controller.rb)

class BookmarksController < ApplicationController
def create
bookmark = current_user.bookmarks.build(board_id: params[:board_id])
bookmark.save!
redirect_to boards_path, success: 'ブックマークしました'
end

def destroy
current_user.bookmarks.find_by(board_id: params[:board_id]).destroy!
redirect_to boards_path, success: 'ブックマークを外しました'
end
end

createアクションでは先ほど、ルーティングでboardにネストをしたおかげでparamsからboard_idをもってくることができ、簡単にコードをかける。

destroyアクションも同様にparamsでboard_idを引っ張ってきて、削除するだけ。

 

(boards_controller.rb)

def bookmarks
@boards = current_user.bookmark_boards.includes(:user).order(created_at: :desc)
end

先ほどルーティングにて、collectionでbookmarksを追加したので、bookmarksアクションを定義していく。

これはブックマークの一覧に繋がっている。そのためログインユーザーがブックマークした情報だけを持ってきたいので、current_user(ログインしているユーザー).bookmark_boards(モデルでhas_manyと書いたおかげでブックマークしている情報を引っ張れる)

includes(:user)はもしかしたらuserだけではなくboardの情報を持ってくる必要があるかも。(n+1問題を解決するために)

 

⑥各viewの設定

(_board.html.erb)

<% if current_user.own?(board) %>
<div class='mr10 float-right'>
<%= link_to icon('fa', 'pen'), edit_board_path(board),
id: "button-edit-#{board.id}" %>
<%= link_to icon('fas', 'trash'), "/boards/#{board.id}",
id: "button-delete-#{board.id}", method: :delete,
data: {confirm: '本当に削除しますか?'} %>
</div>
<% else %>
<div class= "display: inline float: right">
<%= render 'boards/bookmark_area', board: board %>
</div>
<% end %>

前回作成したcurrent_user.own?(board)のelse以下にrenderでブックマークの状況に応じたテンプレートをもってくる。current_user.own?(board)はログインしているユーザーであれば掲示板に編集や削除ボタンがでてくるというもの。今回は逆にログインしているユーザーは自分の掲示板をブックマークすることが出来ないようにするためこのようにelse以下に書いている。

 

(_bookmard_area.html.erb)

<% if board.bookmark_by?(current_user) %>
<%= render 'boards/bookmark', board: board %>
<% else %>
<%= render 'boards/unbookmark', board: board %>
<% end %>

bookmark_by?(current_user)はboardモデルで定義したもので、既にブックマークに登録しているかいないかを判断してくれる。

ックマークに登録していれば'boards/bookmark'、していなければ、'boards/unbookmark'といった感じ。

 

(_bookmark.html.erb)

<%= link_to icon('fas', 'star'), board_bookmarks_path(board.id),
method: :delete, id: :"js-bookmark-button-for-board-#{board.id}" %>

すでにブックマークしてある場合は削除したいのでdelete。

(_bookmard.html.erb)

<%= link_to icon('far', 'star'), board_bookmarks_path(board.id),
method: :post, id: :"js-bookmark-button-for-board-#{board.id}" %>

まだブックマークしてないので、post。

 

(bookmarks.html.erb)ブックマーク一覧

<div class="row">
<div class="col-12">
<div class="row">
<% if @boards.present? %>
<%= render @boards %>
<% else %>
<p><%= 'ブックマーク中の掲示板がありません' %></p>
<% end %>
</div>
</div>
</div>

 

以上でブックマーク機能を追加できました!

多対多になると中間テーブルがでてきて複雑になりがちなので難しい。

最後までご視聴ありがとうございます。