ブックマーク機能の追加
アソシエーション
ユーザーは複数の掲示板をブックマークでき、掲示板は複数のユーザーにブックマークされるというようにモデル間で多対多の関係がある時、中間テーブル(今回の場合はブックマーク)を作成してモデル間の情報をやり取りする このテーブルによって、今回ならどのユーザーがどの掲示板と関連づいているのかという情報を管理することで、ユーザーが掲示板をブックマークといった機能を実装できる UserモデルとBoardモデルとBookmarkモデルの関係は以下のようになる User : Bookmark = 1 : 多 Board : Bookmark = 1 : 多
bundle exec rails g model Bookmark user:references board:references
を実行、bookmarkのモデルファイルとマイグレーションファイルを作成
#bookmark.rb class Bookmark < ApplicationRecord belongs_to :user belongs_to :board validates :user_id, uniqueness: { scope: :board_id } end
uniqunessヘルパーのscopeオプションにboard_idを指定することで、各掲示板(board_id)別にユーザー(user_id)との関係性を一意にすることができる
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 #user_idとboard_idカラムにuniqueインデックスを作成して、dbレベルで一意性を保つ t.index [:user_id, :board_id], unique: true t.timestamps end end end
これにより一人のユーザーが同じ投稿を複数回bookmarkできないようにしている
bundle exec rails db:migrate
を実行して中間テーブルは作成完了
#user.rb class User < ApplicationRecord #dependent: :destroyでユーザーが削除された時、そのユーザーに関連する情報を削除するようにしている has_many :bookmarks, dependent: :destroy #throughオプションでbookmarkテーブルを経由してuserモデルとアソシエーションを作成して、bookmark_boardsは仮のテーブル名なので、sourceオプションで参照するテーブルを指定している has_many :bookmark_boards, through: :bookmarks, source: :board end
ブックマークの処理
ブックマークの処理はモデルに記載すること 理由はコントローラーに記載すると可読性が落ちるため
#user.rb class User < ApplicationRecord #<<演算子でbookmarks.create!(board_id: board.id)と同様の処理を行う def bookmark(board) bookmark_boards << board end def unbookmark(board) bookmark_boards.destroy(board) end #ログインしているユーザーに紐づいたブックマークのレコードに引数で渡された掲示板モデルと紐づいたレコードが存在するか。存在すればユーザーはその掲示板をブックマークしていると判定、存在しなければブックマークされていないと判定できる def bookmark?(board) bookmark_boards.include?(board) end end
ルーティングの設定
今回ブックマーク一覧ページをboards/bookmarksにルーティングを設定したい urlから何を表示しているか推測しやすくするため また、bookmarksコントローラのcreateとdestroyアクションだけルーティングを設定したいので
#routes.rb resources :boards do collection do get :bookmarks end resources :bookmarks, only; %i[create destroy]
とcollectionオプションを使うことでboardsリソースの中にbookmarksアクションを追加できる、この時bookmarks_boards_urlやbookmarks_boards_pathといったルーティングヘルパーも使えるようになる。
Bookmarkコントローラ作成
bundle exec rails g controller bookmarks create destroy
を実行してbookmarks_controller.rbを作成して、以下を記述
#bookmarks_controller.rb class BookmarksController < ApplicationController #ブックマークボタンをクリックされた掲示板を取得して、Userモデルのインスタンスメソッドとして作成したbookmarkメソッドの引数に渡してブックマークする def create @board = Board.find(params[:board_id]) current_user.bookmark(@board) redirect_back fallback_location: root_path, success: t('.success') end #ログインしているユーザーに関連づいてクリックしたbookmarksモデルに関連づいたboardモデルを取得してunbookmarkメソッドの引数に渡してブックマーク解除をしている 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は必ず設定する必要がある。HTTP_REFERER(リンク元のURLが入っているためどこのURLからアクセスしてきたかを知ることができる)がブラウザが必ずしも設定しているとは限らないため
boards_controllerでbookmarksアクションの実装
#boards_controller.rb class BoardsController < ApplicationController def bookmarks @bookmark_boards = current_user.bookmark_boards.includes(:user).order(created_at: :desc) end end
ビュー実装
#boards/_board.html.erb 中略 <% if current_user.own?(board) %> #ログインしているユーザーが掲示板作成者であるならcrud_menusのパーシャルを表示する、そうでなければ、bookmark_buttonのパーシャルを表示する <%= render 'crud_menus', board: board %> <% else %> <%= render 'bookmark_button', board: board %> <% end %> #boards/_bookmark.html.erb user.rbにて作成したbookmark?メソッドによってログインしているユーザーが掲示板をブックマークしているか判定、ブックマークしていたら、unbookmarkパーシャルを表示、そうでなければbookmarkパーシャルを表示 <% if current_user.bookmark?(board) %> <%= render 'unbookmark', { board: board } %> <% else %> <%= render 'bookmark', { board: board } %> <% end %> #boards/_bookmark.html.erb <%= 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 %> #boards/_unbookmark.html.erb <%= 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 %> #boards/bookmarks.html.erb 中略 #ブックマークされた掲示板があればbookmark_boardパーシャルを表示、なければ、ブックマーク中の掲示板がありませんと表示 <% if @bookmark_boards.present? %> <%= render @bookmark_boards %> <% else %> <p><%= t('.no_result') %></p> <% end %>
参考文献
Active Record の関連付け - Railsガイド
https://qiita.com/niwa1903/items/8a0374155cd18adbd7cf