掲示板にお気に入り機能を実装する②
前回は、モデルのアソシエーションの設定を終えました。
今回は、ルーティングとコントローラー、ビューの設定です。難しかった。間違っているかも
また、各判定の処理はモデルに書いていきます。
ルーティングの設定
resources :users, only: %i[new create] resources :boards do resources :comments, only: %i[create update destroy], shallow: true collection do get :bookmarks end end resources :bookmarks, only: %i[create destroy]
resources :boards, shallow: true do resources :comments, only: %i[create] resources :bookmarks, only: %i[create destroy] collection do get :bookmarks end end end
コントローラー、もろもろの設定の流れ
- bookmarks_controllerを生成する
- bookmarkする処理はモデルに定義する
- 自分が作った掲示板かを判定するメソッドをモデルに定義する(定義済)
- bookmarkしてるかを判定するメソッドをモデルに定義する
- bookmarks_controllerのcreateアクション、destroyアクションを定義する
- bookmarksアクションを定義する
bookmarks_controllerの生成
create
とdestroy
だけでOK
作ってから気づいたけど、viewもいらないし、手作業でcontrollerだけ作ればよかったのでは…?
$ rails g controller bookmarks create destroy
bookmarkする処理とbookmarkしたか判定する処理をモデルに書いていく
コントローラーに記載すると、アクション内部の記述が複雑になってしまい、可読性が落ちるので、モデルに記載していく。
(どのくらいまでならコントローラーに書いていいのか、塩梅が分からない)
def bookmark(board) bookmark_boards << board end def unbookmark(board) bookmark_boards.destroy(board) end def bookmark?(board) bookmark_boards.include?(board) end
前回、has_many :bookmark_boards, through: :bookmarks, source: :board
で
has many through
オプションをつけたので、Userクラスのインスタンスメソッドbookmark_boards
が定義されています。
def bookmark(board) bookmark_boards << board end
お気に入り追加
<<
によって、引数で渡した掲示板レコードが、中間テーブルに自動的に保存されている。save
メソッドは必要ない
def bookmark?(board) bookmark_boards.include?(board) end
bookmarkしているか判定している。「このboardはbookmark_boardsに含まれている?」
include?
メソッドは、引数の掲示板レコードが含まれていたらtrue,そうでなければfalseを返す
def create bookmark = current_user.bookmarks.build(board_id: params[:board_id]) bookmark.save! redirect_to boards_path, success: t('defaults.message.bookmark') end def destroy current_user.bookmarks.find_by(board_id: params[:board_id]).destroy! redirect_to boards_path, success: t('defaults.message.unbookmark') end end
class BookmarksController < ApplicationController def create board = Board.find(params[:board_id]) current_user.bookmark(board) redirect_back fallback_location: root_path, success: t('defaults.message.bookmark') end def destroy board = current_user.bookmarks.find(params[:id]).board current_user.unbookmark(board) redirect_back fallback_location: root_path, success: t('defaults.message.unbookmark') end end
知らなかったところを説明
redirect_back fallback_location: root_path
redirect_back
を使うと、ユーザーを直前のページに戻すことができる。
戻せない場合のfallback_locationは必須。
※fallback---An alternative plan that may be used in an emergency.
board = current_user.bookmarks.find(params[:id]).board
destroyアクションのここが難しかった。なんでparams[:id]
なの??と思ったけど、ルーティングを思い出すと、
bookmarks POST /bookmarks(.:format) bookmarks#create bookmark DELETE /bookmarks/:id(.:format) bookmarks#destroy
current_userに紐付いたお気に入りを今のパラメーター(bookmarks/:id)から持ってきて、その掲示板レコードを取得している
後ろの.board
は掲示板レコードをとってきてる
コードが分からなかったので、rails console
で試してみた。疑問があったら試す癖をつけたい。
params
は頭が混乱するので、設定したルーティングとurlを意識する!
ルーティング、今回没にしたやつのほうが実装はシンプルになるので、良さそう。ただ、コードを読み解く勉強になった
お気に入りした掲示板の一覧表示するためのアクション追加
def bookmarks @bookmark_boards = current_user.bookmark_boards.includes(:user).order(created_at: :desc) end
無駄にSQLを発行させるN + 1 問題を防ぐために、includes(:user)
として、関連するユーザー情報も取得しています。
それでは、ビューの設定へ。
もう少しで終わり!
ビューの設定流れ
- 掲示板一覧の中で、お気に入りボタンを配置するエリアを用意する
- お気に入りボタン・お気に入り解除ボタンをパーシャルで用意する
- 自分が作成した掲示板には、お気に入りボタンを配置しない(画面左)
- お気に入りした掲示板の一覧を表示する
お気に入りボタンを用意する
画像のとおり、各掲示板画面の右側に☆ボタンを配置したいです。
まず、掲示板画面の中に、ボタンを配置するエリアを用意して、☆と★を条件によってどちらかをrender
していきます。
長くなってしまうので、必要箇所だけ抜粋して解説。
掲示板一覧画面は、各掲示板をrender
して読み込んでいます。
前回は掲示板画面に、自分の作成した掲示板だった場合、編集・削除ページを表示しました。
そこのコードを少しいじっていきます。
<% if current_user.own?(board) %> <%= render 'crud_menus', board: board %> <% else %> <%= render 'bookmark_button', board:board %> <% end %>
以前作った、こちらの判定メソッドを使用しています。 自分自身(self.idが省略されてる)が掲示板のuser_idと一致しているか??
一致してなかったら、bookmark_button
をrenderします。(これから作成する)
def own?(object) id == object.user_id end
bookmark_button
を作成していきます。
<% if current_user.bookmark?(board) %> <%= render 'unbookmark', { board: board } %> <% else %> <%= render 'bookmark', { board: board } %> <% end %>
今回の前の方で作った、bookmarkしてるかの判定メソッドを使っています。
これ↓↓
def bookmark?(board) bookmark_boards.include?(board) end
current_user
がこのこの掲示板をお気に入りしてたら、unbookmark
をrender
する
localオプションで、パーシャルのboard
には引数のboard
を渡す
これ、最初、逆にして実装してしまいました。
ちゃんと整理すると、今はお気に入り済みなので★マークが表示されている。★マークをクリックすると、お気に入りが解除されるので、unbookmark
になる。
では、それぞれのボタンを設定していく。
お気に入り解除のボタン
<%= 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 %>
link_to
でbookmark_path
へのリクエストにパラメーターを渡すことができる
current_user(現ユーザー)に紐付いているbookmarksをboard_idを使って取得している。
ルーティングはさっきのroutes.rb
のとおり
Crome検証ツールでパラメーターが渡せているか確認する。
エンドポイントが、(/bookmarks/〇〇(ID))になっていることを確認◎
こちらは、お気に入りするボタン
<%= 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 %>
link_to
で、リクエストにパラメータを付与することができることを確認( /bookmarks?board_id=10 )、
お気に入り一覧画面の作成
必要な部分だけ抜粋
さっきboards_controller.rb
内に作ったインスタンス変数@bookmark_boards
をrenderしていく。
<!-- 掲示板一覧 --> <% if @bookmark_boards.present? %> <%= render @bookmark_boards %> <% else %> <p><%= t('bookmarks.bookmarks.no_result') %></p> <% end %>
おまけ
N + 1問題解消のためにコード修正
掲示板一覧を取得するアクションindex
が今はこのようになっているが、お気に入りについてのキャッシュも取得してほしい
# 現在はこれ。userだけキャッシュしてる def index @boards = Board.all.includes(:user).order(created_at: :desc) end # こちらへ修正。bookmarkも取得してくれるようになる! @boards = Board.all.includes([:user, :bookmarks]).order(created_at: desc)
fontawesomeについて
gem 'font-awesome-sass', '~> 5.13.0'
font-awesome-sass
を追加しているので、view内では、
<%= icon 'fas', 'star' %>
このように使えます。fasかfarなのかは、公式でチェック