Programming Journal

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

掲示板の編集と削除機能を実装する

実装したいこと

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

  • 掲示板一覧画面と詳細画面にそれぞれ、編集と削除ボタンを表示する。

  • 編集・削除ボタンは、掲示板作成者本人にしか表示されない。

  • 削除ボタンをクリックすると、「本当に削除しますか?」と確認アラートが表示される。

  • 直接URLを打ち込み、他人の作成した掲示板の編集ページにアクセスしようとしても404エラーが出るようにする。

実装の大まかな流れ

  • ビュー側の設定

  • ルーティングの設定

  • コントローラーの設定

それでは、順番に実装していきます

ビューの設定流れ

  • 編集・削除ボタンの実装 (パーシャル化する。)
  • ボタンは、作成者本人だけにしか見えないようにする。
  • 掲示板編集画面と新規作成画面は共有にしておく。

編集・削除ボタンの実装

<ul class='crud-menu-btn list-inline float-right'>
  <li class="list-inline-item">
    <%= link_to edit_board_path(board), id: "button-edit-#{board.id}" do %>
      <%= icon 'fa', 'pen' %>
    <% end %>
  </li>
  <li class="list-inline-item">
    <%= link_to board_path(board), id: "button-delete-#{board.id}", method: :delete, data: {confirm: t('defaults.message.delete_confirm')} do %>
      <%= icon 'fas', 'trash' %>
    <% end %>
  </li>
</ul>

ボタンに一意のCSSのidを割り振っています。これは将来的にJavaScriptを使って各ボタンを操作するときに役立ちます。(慣習)

掲示板の詳細画面と一覧画面に、作成したボタンを表示させます。

このとき、掲示板作成者以外にはボタンを表示させないようにします。

パーシャルを呼び出すときのオプション(partial,locals)は、コード量削減のために省略しています。

<%= render 'crud_menus', board: @board if current_user.own?(@board) %>

英語のように読み解くと、

crud_menusパーシャルを呼び出し

パーシャル内のboard@boardを代入する

もし、current_userid@boarduser_idと一致していたら

ここif current_user.own?(@board)は前回作った、これを使用しています。

current_user掲示板作った人が一致しているか確認しています。

def own?(object)
    id == object.user_id
  end

ルーティングの設定

前回までなかったアクションを追加するので、結局こうなる

resources :boards do
    resources :comments, only: %i[create], shallow: true
  end

boards_controllerの設定

class BoardsController < ApplicationController
 before_action :set_board, only: %i[edit update destroy]
〜
def edit; end

  def update
    if @board.update(board_params)
      redirect_to @board, success: t('defaults.message.updated', item: Board.model_name.human)
    else
      flash.now[:danger] = t('defaults.message.not_updated', item: Board.model_name.human)
      render :edit
    end
  end

  def destroy
    @board.destroy!
    redirect_to boards_path, success: t('defaults.message.deleted', item: Board.model_name.human)
  end

  private

  def set_board
    @board = current_user.boards.find(params[:id])
  end

  def board_params
    params.require(:board).permit(:title, :body, :board_image, :bord_image_cache)
  end
〜

難しいところを一つずつみていきます

before_action :set_board, only: %i[edit update destroy]

edit update destroy アクションでは、それぞれ、掲示板を取得するという処理が共通するので、コールバックのbefore_actionでまとめて記載します。

  def set_board
    @board = current_user.boards.find(params[:id])
  end

これが、掲示板を取得する 処理です。

show アクションでは、@board = Board.find(params[:id]) で掲示板を取得してきました。

しかし、この処理だと「指定したidの掲示板をDBから取得する」こととなり、params[:id]とはURLのパラメーターから情報を取得しているため、/boards/〇〇(適当な数字)/editをURLに入力すれば、違うユーザーが作成した掲示板編集ページに飛べてしまい、危険です。

@board = current_user.boards.find(params[:id]) とすることで、ログインしているユーザーに紐付いた掲示板データを取得できるようになります。


 redirect_to @board, success: t('defaults.message.updated', item: Board.model_name.human) #メッセージはja.ymlに書いてあるもの

redirect_to @board で掲示板詳細ページに遷移します。

これは、redirect_to board_path(@board)の省略形


 def destroy
    @board.destroy!

!を使っている理由は、削除処理は「必ず成功するもの」だからです。

save save! は、処理が失敗したときの挙動が違います。 前者はfalse を返し、後者は例外を返します。

例えば、掲示板作成でタイトルを入力漏れし、falseが帰ってきたら、エラー表示箇所を訂正してまた作成を試みます。掲示板作成は「失敗する可能性がある」処理です。

一方、削除の処理は失敗する余地がない処理です。この処理が失敗したときは、意図的に処理を止めてデバッグが必要になります。

以前、Fakerを使うときの、create! アクションでもこのことについて言及しました。 study-diary.hatenadiary.jp