掲示板にコメント機能を実装する②
ルーティングの設定
ルーティングをネスト(入れ子にする)して、boardsとcommentsとの親子関係を表していきます。
今回必要なのはcreateアクションだけなので、とりあえずこのようにネストすると
resources :boards, only: %i[index new create show] do resources :comments, only: %i[create], shallow: true end
/boards/:board_id/comments(.:format)
comments#create
こうなる。ルーティングをすっきりさせるために、:shallow
オプションを使用しています。以下Railsガイドから。
コメントのidがあるアクションについては、boardのidは必要ないので、このオプションを使用します。
コレクション (index/new/createのような、idを持たないアクション) だけを親のスコープの下で生成するという手法があります。このとき、メンバー (show/edit/update/destroyのような、idを必要とするアクション) をネストに含めないのがポイントです。これによりコレクションだけが階層化のメリットを受けられます。つまり、以下のように最小限の情報でリソースを一意に指定できるルーティングを作成するということです。親リソースで
:shallow
オプションを指定すると、すべてのネストしたリソースが浅くなります。
掲示板詳細画面の作成
レイアウト部分などかなり省略していますが、大切な部分だけ抜粋
<% 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>
<%= simple_format(@board.body) %>
simple_format
はRailsのヘルパーメソッドで、テキストを改行とかつけてトランスフォームしてくれる。
<!-- コメントフォーム --> <%= 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
<%= 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 %>