Kayaのブログ

プログラミング学習用です

ブックマーク機能の追加

ブックマーク機能の追加 今回の要件

掲示板のボタンを押すと、その掲示板をブックマーク、または解除できる機能の作成

・ブックマークの一覧を表示する機能を作成

 

できたは良いけれど不明点が多かったため抜き出してまとめる。

 

★ has_many :through

user.rb

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

 has_many :through関連付けは、多対多で別のモデルと関連している

従属している第3のモデル(結合モデル)を介して、対象のモデルと多対多の関連付けになっている

Active Record の関連付け - Railsガイド

関連付けを行うことで、このように

boards_controller.rb

 
def bookmarks
@bookmark_boards = current_user.bookmark_boards.includes(:user)
                                   .order(created_at: :desc)
end
 ブックマークした掲示板情報を取得できる
 
ちなみにsource: :boardは、boardモデルからデータを参照する、ということ

 

user.rb

# お気に入り追加
def bookmark(board)
bookmark_boards << board
end

#  お気に入りを外す
def unbookmark(board)
bookmark_boards.destroy(board)
end

#  お気に入り登録しているか判定する
def bookmark?(board)
bookmark_boards.include?(board)
end
controllerに記載すると、アクション内部の記述が複雑になり、可読性が落ちるため、ブックマークする処理はmodelに寄せる。
・上記メソッドをmodelに作成しない場合(bookmarks_controller.rb)
def create
board = Board.find(params[:board_id])
Bookmark.create(user: current_user, board: board)
・[今回]上記メソッドをmodelに作成する場合(bookmarks_controller.rb)
ログインユーザーが対象の掲示板をブックマークする、という内容を簡潔に記載できる
def create
board = Board.find(params[:board_id])
current_user.bookmark(board)
 
bookmark_boards << board
この演算子
bookmarks.create!(board_id: board.id)
と同様の処理が行われている、らしい
<<で引数で渡したboardの情報がbookmark_boardsに入ってる、だと少しわかる気がする
 

user.rb

has_many :bookmark_boards, through: :bookmarks, source: :board
としたため、Userクラスのインスタンスメソッド、bookmark_boardsが定義できる
 
 
★ unique制約
bookmark.rb
validates :user_id, uniqueness: { scope: :board_id }
ブックマーク処理を行うと、"どのユーザーがどの掲示板をブックマークしているか"という関係性を表したレコードを作成する
ブックマークを外す処理では、ユーザーと掲示板のブックマークの関係性がなくなるように該当レコードを削除する
もしuserとboardの組み合わせのレコードが重複すると、ブックマークを外しても関係性のレコードが残り、ブックマークが削除できなくなる
そのため、ユーザーと掲示板の組み合わせのレコードを一意にするためのunique制約が必要となる
 
 
★ コレクションルーティング
routes.rb
resources :boards do
resources :comments, only: %i[create update destroy], shallow: true
collection do
get :bookmarks
end
end
 今回はbookmarks_boards_path(=> boards/bookmarks)としたいので、idを伴わない。
(boards/:id/bookmarksのようにidが入らない、ということ)
ので、collectionルーティングを使用する。
 
注: idを伴う場合は、memberを使用する
 
★ fallback_location
bookmarks_controller.rb
redirect_back fallback_location: root_path, success: t('.success')
redirect_backを使うと、ユーザを直前のページに戻すことができます。戻る場所はHTTP_REFERERヘッダを利用していますが、これはブラウザが必ず設定しているとは限りません。そのため、fallback_locationは必ず設定しなければなりません。
 
もともとredirect_to :back、だったらしい。そっちの方が簡潔でわかりやすかったなあ
 
 
 
★ partial(一番の課題点!これが原因で一覧表示ができなかった!)
bookmarks.html.erb
<% if @bookmark_boards.present? %>
<%= render partial: 'board', collection: @bookmark_boards %>
<% else %>
<p><%= t('.no_result') %></p>
<% end %>
 @bookmark_boardsは、bookmarks_controller.rbで定義した変数
 
collectionオプションを使用するのは、
インスタンス変数をパーシャル内で使用しないため
・eachを使用せずにcollectionで指定した要素を1つずつ出力してくれる
 (指定した要素の数だけ部分テンプレートが繰り返される)
 
注: 
<%= render partial: 'board', collection: @bookmark_boards %>
は、
<%= render @bookmark_boards %>
と省略できる。
ただ、
<%= render 'board', collection: @bookmark_boards %>
や、
<%= render partial: 'board', @bookmark_boards %>
 のようにすることはできない!!
partial: とcollection: はセット!!!
 
 
★ お気に入り登録、解除
解答を確認して理解できないわけではなかったけれど、初見でどう実装すれば良いのかわからなかったため書き出す
 
_bookmark.html.erb
<%= link_to bookmarks_path(board_id: board.id), class: 'mr10 float-right',
id: "js-bookmark-button-for-board-#{board.id}", method: :post do %>
<%= icon 'far', 'star' %>
<% end %>
お気に入り登録
bookmarks POST /bookmarks(.:format) bookmarks#create
より、bookmarks_path = create
 
_unbookmark.html.erb
<%= link_to bookmark_path(current_user.bookmarks.find_by(board_id: board.id)),
class: 'mr10 float-right', id: "js-bookmark-button-for-board-#{board.id}",
method: :delete do %>
<%= icon 'fas', 'star' %>
<% end %>
 お気に入り解除
bookmark DELETE /bookmarks/:id(.:format) bookmarks#destroy
より、bookmark_path = delete
current_userのbookmarksを、board_idを使って取得する
 
routeで困ったらとにかくまずはrails routesで確認してみること