Programming Journal

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

掲示板にコメント機能を実装する②

ルーティングの設定

ルーティングをネスト(入れ子にする)して、boardsとcommentsとの親子関係を表していきます。

今回必要なのはcreateアクションだけなので、とりあえずこのようにネストすると

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

f:id:Study-Diary:20200811143326p:plain
rails routes

/boards/:board_id/comments(.:format) comments#create

こうなる。ルーティングをすっきりさせるために、:shallowオプションを使用しています。以下Railsガイドから。

コメントのidがあるアクションについては、boardのidは必要ないので、このオプションを使用します。

コレクション (index/new/createのような、idを持たないアクション) だけを親のスコープの下で生成するという手法があります。このとき、メンバー (show/edit/update/destroyのような、idを必要とするアクション) をネストに含めないのがポイントです。これによりコレクションだけが階層化のメリットを受けられます。つまり、以下のように最小限の情報でリソースを一意に指定できるルーティングを作成するということです。親リソースで:shallowオプションを指定すると、すべてのネストしたリソースが浅くなります。

Rails のルーティング - Railsガイド

掲示板詳細画面の作成

レイアウト部分などかなり省略していますが、大切な部分だけ抜粋

<% content_for(:title, @board.title) %>
〜
<!-- 掲示板内容 -->
 〜
<%= image_tag @board.board_image.url, class: 'card-img-top img-fluid', size: '300x200' %>

<%= @board.title %>
<%= render 'crud_menus', board: @board %>  #crud_menuとは、削除と編集部分のアイコンをパーシャル化したもの
<%= @board.user.decorate.full_name %>   #decoratorで以前作った'full_name'メソッドを呼んでいる
<%= l @board.created_at, format: :long %>        #I18nで翻訳したもの。
<%= simple_format(@board.body) %>

<!-- コメントフォーム -->
<%= render 'comments/form', { board: @board, comment: @comment } %>

<!-- コメントエリア -->
<%= render 'comments/comments', { comments: @comments } %>
  • 掲示板の内容を表示
  • コメントフォームはパーシャルを呼び出す
  • コメントエリア(今までのコメントが表示される)もパーシャルで呼び出す
わかりにくかったコードを読み解く

<% content_for(:title, @board.title) %>

content_forでタイトル(ブラウザの上部タブに表示されるもの)を指定している

ちなみに、以下の設定をしているので、content_forで呼び出せている。

指定がないときは、デフォルトタイトルになっている。

 <title><%= content_for?(:title)? yield(:title) :'デフォルトタイトル' %></title>

Action View の概要 - Railsガイド


<%= simple_format(@board.body) %>

simple_formatRailsのヘルパーメソッドで、テキストを改行とかつけてトランスフォームしてくれる。

Ruby on Rails API


<!-- コメントフォーム -->
<%= render 'comments/form', { board: @board, comment: @comment } %>

コメントを直接書き込めるフォーム部分。

{ board: @board, comment: @comment } ローカルオプションが省略されている形。パーシャルに、@board と @commentを引数として渡している。

コメントフォームと一覧部分を作成する

パーシャル化した部分を作成していく。

パーシャルにはインスタンス変数を使用しないこと。

(コントローラーと関連付けてしまうと、再利用性が低くなってしまうから。)

まずは、コメントを書き込めるフォーム部分。

エラーメッセージも呼び込めるように実装。

<!-- コメントフォーム -->
<div class="row mb-3">
  <div class="col-lg-8 offset-lg-2"> 
    <%= form_with model: comment, url: [board, comment], local: true do |f| %>
      <%= render 'shared/error_messages', object: f.object %>
      <%= f.label :body %>
      <%= f.text_area :body, class: 'form-control mb-3', row: 4,  placeholder: Comment.human_attribute_name(:body) %>
      <%= f.submit t('defaults.post'), class: 'btn btn-primary' %>
    <% end %>
  </div>
</div>

<div class="col-lg-8 offset-lg-2"> 

Bootstrapのグリッドシステムを使っている

Bootstrapのグリッドシステムについてまとめてみた - Qiita

Grid system · Bootstrap


<%= form_with model: comment, url: [board, comment], local: true do |f| %>

  • model: comment  Commentモデルを基にフォームが作られる。

  • local: true :localオプションは、送信時にAjaxを使わなくする。

  • url: [board, comment] :urlオプションは、データの送信先を指定している。

ルーティングを親子関係でネスト(boads/:board_id/comment)しているので、データの送信先も両者を指定しなくてはいけない。

ActionView::Helpers::FormHelper

【Rails入門説明書】form_withについて解説 | WEBCAMP NAVI


placeholder:

入力欄に、文書を表示させている。

::placeholder - CSS: カスケーディングスタイルシート | MDN


コメント一覧部分。ファイル名を、commentsと複数形にしている。

ここはさらに、_comment.html.erb を呼びだしている。

<div class="row">
  <div class="col-lg-8 offset-lg-2">
    <table id="js-table-comment" class="table">
      <%= render comments %>
    </table>
  </div>
</div>

各コメント部分

長くなるので、レイアウト部分は無視して重要な部分だけ抜粋

<!-- コメントエリア -->
<%= comment.user.decorate.full_name %>
<%= simple_format(comment.body) %>
〜
  

コメント作成者だけ、編集と削除ボタンを表示する。

View側で設定するのではなく、Model側に記述していく。

変更があったときなど、メンテナンスするときにModelの設定を変更するだけでいいから。

「リソースの判定はUserモデルに記載する」などとルールを決めておくと、各リソースの判定について参照すべきところがまとまっていて◎

comment.user == current_user としてしまうのはNG。commentモデルにあるuser_idを使って、Usersテーブルから対象を探してきてしまい、無駄にSQLを発行させてしまうから。

commet.user_idはcommentモデルにあるため、SQLの発行がない。

def my_comment?(comment)
  comment.user_id == id #self.idの省略形
end

#こちらのほうが汎用的なので採用◎
def own?(object)
    id == object.user_id
  end
<%if current_user.own?(comment) %> #先程定義したもの
    <td class="action">
      <ul class="list-inline justify-content-center" style="float: right;">
        <li class="list-inline-item">
          <a href="#" class='js-edit-comment-button' data-comment-id="<%= comment.id %>">
            <%= icon 'fa', 'pen' %>
          </a>  
        </li>
        <li class="list-inline-item">
          <a href="#" class='js-delete-comment-button' data-comment-id="<%= comment.id %>">
            <%= icon 'fas', 'trash' %>
          </a>
        </li>
      </ul>
   <% end %>