2021/01/07 掲示板のbookmarkの追加
やること
掲示板をブックマーク/解除出来る機能の追加
ブックマークの一覧表示の機能を追加
考えること
一つの掲示板に対し、ブックマークは複数
一つのユーザーに対し、ブックマークは複数
一つのブックマークに対し、掲示板はひとつ、ユーザーはひとつ
bookmarkテーブルには掲示板とユーザーIDのカラムを持たせ、bookmarkのcreateアクションでそれを
保存し、掲示板の中でbookmarkテーブルにその掲示板のidがあるかどうかでだしわけできるようにすれば良い?
bookmarkのカラムは以下の通り作成。
class CreateBookmarks < ActiveRecord::Migration[5.2] def change create_table :bookmarks do |t| t.references :board_id, foreign_key: true t.references :user_id, foreign_key: true t.timestamps end add_index :bookmarks, :board_id add_index :bookmarks, :user_id add_index :bookmarks, [:board_id, :user_id], unique: true end end
board/userモデルにhas_many、bookmarkモデルにbelongs_toをそれぞれ記述。
bookmarksコントローラを作成し、パーシャルをbookmark_area.html.erb(showテンプレートに記述する用)、 bookmark.html.erb(ブックマーク済ボタンを表示する用)、unbookmark.html.erb(未ブックマークを表示する用)で3つ作成。
やることは、
- 現在のユーザーがそのboardに紐づくbookmarkを持っているか判断するメソッドを作り、ボタンのだしわけができるようにする
- フォームからboard_id、user_idを取得し、bookmarkのcreateアクションでbookmarkモデルに保存(このフォームはbookmark.html.erbに記述)
- 削除用のdestroyアクションも作る(フォームはunbookmark.html.erbに作成)
- ブックマークボタンを表示したい掲示板一覧に、bookmark_area用のパーシャルを追加、この中で、bookmark有無によって表示をだし分けできるようにメソッドを作っていく
- 現在のユーザーのブックマーク一覧を取得し、表示するページを作る
やったこと
ルーティングの追加
resources :boards, only: %i[index new create show edit destroy update] do resources :comments, shallow: true resources :bookmarks, shallow: true collection do get :bookmarks end end
アソシエーションの追加
#user.rb has_many :bookmarks, dependent: :destroy #board.rb has_many :bookmarks, dependent: :destroy #bookmark.rb belongs_to :board belongs_to :user
切り分け用のメソッド
#board.rb def bookmarked?(user) bookmarks.where(user_id: user.id).exists? end
ブックマークボタン作成
<% unless current_user.own?(board) %> #掲示板が現在のユーザーのものでなければブックマークボタンを表示 <% if board.bookmarked?(current_user) %> #現在のユーザーがブックマークしている掲示板かを判定 <%= link_to board_bookmarks_path(board),method: :post, local: true, id: "js-bookmark-button-for-board-#{board.id}" do %> #ネストしている <i class="far fa-star"></i> <% end %> <% else %> <%= link_to bookmark_path(board),method: :delete, local: true, id: "js-bookmark-button-for-board-#{board.id}" do %> #shallowオプション <i class="fas fa-star"></i> <% end %> <% end %> <% end %>
create、destoryアクションを作成
class BookmarksController < ApplicationController def create board = Board.find(params[:board_id]) board.bookmarks.create!(user_id: current_user.id) redirect_to boards_path, success: t('.success') end def destroy bookmark = Bookmark.find_by(board_id: params[:id], user_id: current_user.id) bookmark.destroy! redirect_to boards_path, success: t('.success') end end
これで一通り動くようになった!回答例見るとあまりいい出来ではないようなので、解説見ながらリファクタリング
改修
boardとuserを関連付ける。
#user.rb has_many :bookmarks, dependent: :destroy has_many :bookmark_boards, through: :bookmarks, source: :board
railsチュートリアルで出てきたけど全然理解できていなかったhas_manyのthroughオプションを使う
userモデル、boardモデルそれぞれに関連づけられているbookmarkモデルを通じてuserとboardを関連づける
この関連付けを行うことでbookmarkにあるboardの情報を、bookmarkを介してuserから取得できるようになる
具体的には
user_object.bookmark_boards.all
のように、第一引数をメソッド名として、userがブックマークしている掲示板の情報を取得するというようなメソッドが使えるようになる
続く引数でなんの情報が欲しいかを指定しているため、第一引数のメソッド名はなんでもいいが他と名前が被らないように指定する必要がある
これを使って、userがブックマークしている掲示板一覧を出すメソッドを書き換える
#board.rb def bookmarked?(board) bookmark_boards.include?(board) #メソッドの引数に指定したboardがレシーバーのuserがブックマークしている中に含まれているか? end
userオブジェクトのインスタンスメソッドになるため、user.rbに変更
これに応じてviewも書き換え
<% if current_user.bookmarked?(board) %>
また、create、editアクションもthroughオプションで使えるようになったメソッドを使って書き換える
コントローラの可読性をよくするために、メソッドはuser.rbに記載
#user.rb def bookmark(board) bookmark_boards << board end def unbookmark(board) bookmark_boards.delete(board) end
collection<<メソッドは、1つ以上のオブジェクトをコレクションに追加します。このとき、追加されるオブジェクトの外部キーは、呼び出し側モデルの主キーに設定されます。
user_object.bookmark(board) で、user_idを外部キーとしてboardがbookmarkに追加される
# 中間テーブルを削除する bookmarks.destroy(bookmarks) # お気に入りの掲示板の集合から対象の掲示板を削除する bookmark_boards.destroy(board)
destroyアクションは中間テーブル(bookmark)を削除するか、userがブックマークしている掲示板をbookmarkから削除するか、どちらでも挙動は同じ
#bookmarks_controller.rb class BookmarksController < ApplicationController def create board = Board.find(params[:board_id]) current_user.bookmark(board) redirect_back fallback_location: root_path, success: t('.success') end def destroy board = current_user.bookmarks.find(params[:id]).board current_user.unbookmark(board) redirect_back fallback_location: root_path, success: t('.success') end end
redirect_back fallback_location:で、元のページにリダイレクト、例外ならrootへという指定ができる(アクションが例外を出した時用?)