Programming Journal

学習したことの整理用です。

掲示板にお気に入り機能を実装する②

前回は、モデルのアソシエーションの設定を終えました。

今回は、ルーティングとコントローラー、ビューの設定です。難しかった。間違っているかも

また、各判定の処理はモデルに書いていきます。

ルーティングの設定

  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]

f:id:Study-Diary:20200817124345p:plain
ネストしない場合(/bookmarks/:id)

resources :boards, shallow: true do
    resources :comments, only: %i[create]
    resources :bookmarks, only: %i[create destroy]
    collection do
      get :bookmarks
    end  
  end 
end

f:id:Study-Diary:20200817124111p:plain
bookmarksをネストする場合( /boards/:board_id/bookmarksとなる)

コントローラー、もろもろの設定の流れ

  • bookmarks_controllerを生成する
  • bookmarkする処理はモデルに定義する
  • 自分が作った掲示板かを判定するメソッドをモデルに定義する(定義済)
  • bookmarkしてるかを判定するメソッドをモデルに定義する
  • bookmarks_controllerのcreateアクション、destroyアクションを定義する
  • bookmarksアクションを定義する

 bookmarks_controllerの生成

createdestroyだけで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.

ActionController::Redirecting


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)として、関連するユーザー情報も取得しています。

それでは、ビューの設定へ。

もう少しで終わり!

ビューの設定流れ

  • 掲示板一覧の中で、お気に入りボタンを配置するエリアを用意する
  • お気に入りボタン・お気に入り解除ボタンをパーシャルで用意する
  • 自分が作成した掲示板には、お気に入りボタンを配置しない(画面左)
  • お気に入りした掲示板の一覧を表示する

f:id:Study-Diary:20200817102532p:plain
掲示板画面

お気に入りボタンを用意する

画像のとおり、各掲示板画面の右側に☆ボタンを配置したいです。

まず、掲示板画面の中に、ボタンを配置するエリアを用意して、☆と★を条件によってどちらかを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がこのこの掲示板をお気に入りしてたら、unbookmarkrenderする

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_tobookmark_pathへのリクエストにパラメーターを渡すことができる

current_user(現ユーザー)に紐付いているbookmarksをboard_idを使って取得している。

ルーティングはさっきのroutes.rbのとおり

Crome検証ツールでパラメーターが渡せているか確認する。

f:id:Study-Diary:20200817130145p:plain
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 %>

f:id:Study-Diary:20200817130340p:plain
Crome検証ツール

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なのかは、公式でチェック

Font Awesome