どうもどハマりエンジニアです。
ブックマーク機能の実装を行っていきます。既にコメント機能まで完成しています。
①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)マイグレーション
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>
以上でブックマーク機能を追加できました!
多対多になると中間テーブルがでてきて複雑になりがちなので難しい。
最後までご視聴ありがとうございます。