・参考URL
https://sadah.github.io/rails-training/ja/004_comments.html
今回紹介するコードは、プログラミングスクールで学んだTwitterクローンを基にしたRailsのアプリ(Micropost)への追加機能であることを前提にするとコードが読みやすくなると思います。
#モデルの作成
rails g model Comment content:string user:references micropost:references
コメントはUserとMicropostの多:多の関係性を表すため、中間テーブルが必要になります。
Userがコメントする投稿が複数あって、MicropostにはコメントしたUserが複数いるためです。
###マイグレーションファイル
class CreateComments < ActiveRecord::Migration[5.2]
def change
create_table :comments do |t|
t.string :content
t.references :user, foreign_key: true
t.references :micropost, foreign_key: true
t.timestamps
end
end
end
マイグレーションを忘れずに。
rails db:migrate
自動作成されたコメントモデルにバリデーションを付けておきましょう。
###コメントモデル
class Comment < ApplicationRecord
belongs_to :user
belongs_to :micropost
validates :content, presence: true, length: { maximum: 255 }
end
コメントを文字なしで投稿できないように設定します。
#関連モデル
###Userモデル
class User < ApplicationRecord
has_many :microposts
has_many :comments
end
一つのUserに対して多数のコメントがあるためこの関係になります。
###Micropostモデル
class Micropost < ApplicationRecord
belongs_to :user
has_many :comments, dependent: :destroy
end
一つのMicropostに対して多数のコメントがあるためこの関係になります。
Micropostモデルにdependent: :destroy
を追記したことで、投稿が削除されるとそれに紐づいたコメントが削除されるようになります。
dependent: :destroy
がないと、コメントの付いた投稿を削除するときにエラーが起きます。
has_many :commenters, through: :comments, source: :user
Micropost側から見ると、コメントしてくるユーザが多数いるので、正確にモデル作成するなら以下のコードの追記が必要かもしれません。(今回使うことはありませんでしたが、間違っていたらごめんなさい)
#ルーティング
Rails.application.routes.draw do
resources :microposts, only: [:create, :destroy, :show] do
resources :comments, only: [:create, :destroy]
end
end
どのMicropostに対してコメントなのかを明らかにするためにルーティングのネスト(入れ子構造)をします。
#コントローラー作成
$ rails g controller comments create destroy
コメントで必要な機能は作成機能(のちに削除機能)の2つだけなのでターミナルへの入力はこのようになります。
class CommentsController < ApplicationController
before_action :require_user_logged_in
def create
@micropost = Micropost.find(params[:micropost_id])
@comment = @micropost.comments.build(comment_params)
@comment.user_id = current_user.id
if @comment.save
flash[:success] = '投稿にコメントしました。'
redirect_back(fallback_location: root_path)
else
@micropost = Micropost.find(params[:micropost_id])
@comments = @micropost.comments.includes(:user)
flash.now[:danger] = '投稿へのコメントに失敗しました。'
render 'microposts/show'
end
end
private
def comment_params
params.require(:comment).permit(:content)
end
end
コメントはmicropostと紐づいているため@micropost
を頭につけます。
コメントとuserを紐づけるために@comment.user_id = current_user.id
が必要です。
これがないとコメントからuserデータを得られません。
else文が実行されるときrenderするファイルに@micropost
と@comments
の
2つのインスタンス変数を渡すのは、micropostのshowファイルを表示するために必要だからです。
片方でも欠けるとエラーになります。
###関連コントローラー
コメント作成を投稿の詳細ページ(show)で実行できるようにします。
class MicropostsController < ApplicationController
before_action :require_user_logged_in
def show
@micropost = Micropost.find(params[:id])
@comments = @micropost.comments.includes(:user)
@comment = @micropost.comments.build(user_id: current_user.id) if current_user # form_with 用
end
end
showページの@micropost = Micropost.find(params[:id])
で特定の一つのmicropostを表示できるように設定します。
@comments = @micropost.comments.includes(:user)
により、対応する投稿へのコメントを一覧表示することができます。
includes(:user)
はコメントしたuserを表示するためにuserカラムを取得するメソッドです。これがないとgravatar_url
などを表示する時にuser
がnilになりエラーが起こります。
@comment
には、コメント入力フォームの表示エラーを避けるために空のコメントを入れておきます。
class CommentsController < ApplicationController
before_action :require_user_logged_in
def create
@micropost = Micropost.find(params[:micropost_id])
@comment = @micropost.comments.build(comment_params)
@comment.user_id = current_user.id
if @comment.save
flash[:success] = '投稿にコメントしました。'
redirect_back(fallback_location: root_path)
else
@micropost = Micropost.find(params[:micropost_id])
@comments = @micropost.comments.includes(:user)
flash.now[:danger] = '投稿へのコメントに失敗しました。'
render 'microposts/show'
end
end
private
def comment_params
params.require(:comment).permit(:content)
end
end
基本的にはmicroposts_controller
のcreateと同じようにします。(microposts_controllerに関してはRails Tutorialなどを参考にしてください)
コメントはmicropost
に紐づくように作成したいので、対応するmicropost
の
インスタンスを記入しておきます。
@comment.user_id = current_user.id
がないとコメントにuser情報が入らないため必須です。
@micropost
と@comennts
両方のインスタンスを代入しないとrender先のshowファイルで
コメント失敗メッセージを表示できずにエラーが起こります。
#投稿詳細ページのルーティングの確認
投稿詳細ページに飛ぶためのルーティングをターミナルを使って確認します。
$ rails routes
microposts#show
ページに飛ぶために必要なPrefixを確認します
micropost GET /microposts/:id(.:format) microposts#show
投稿一覧のパーシャルに以下の1行を追加して、投稿詳細ページに移動できるようにしましょう。
<% microposts.each do |micropost| %>
〜
<%= link_to 'Comments', micropost_path(micropost), class: 'btn btn-link btn-sm' %>
〜
<% end %>
microposts#showのPrefixがmicropost
なのでmicropost_path
と記入します。
micropost一覧から特定の|micropost|
のページに飛びたいので、
micropost_path
の()内には||内のmicropost
を代入します。
#Viewファイル
###投稿詳細ページ
コメントしたい特定のmicropostを表示するファイルです。
micropostを表示させるためには、controllerのdef show
で定義した
@micropost
のインスタンスをshowファイルに渡せるように注意しましょう。
<ul class="list-unstyled">
<li class="media mb-3">
<img class="mr-2 rounded" src="<%= gravatar_url(@micropost.user, { size: 50 }) %>" alt="">
<div class="media-body">
<div>
<%= link_to @micropost.user.name, user_path(@micropost.user) %> <span class="text-muted">posted at <%= @micropost.created_at %></span>
</div>
<div>
<p><%= @micropost.content %></p>
</div>
<div class="btn-group">
<% if current_user == @micropost.user %>
<%#=詳細ページで削除するとid見つからないエラー発生 link_to "Delete", @micropost, method: :delete, data: { confirm: "You sure?" }, class: 'btn btn-danger btn-sm' %>
<% end %>
<%= render 'favorites/favorite_button', micropost: @micropost %>
</div>
</div>
</li>
</ul>
<%# コメント入力フォームのパーシャル %>
<%= render 'comments/form', micropost: @micropost %>
<%# コメント一覧のパーシャル %>
<%= render 'comments/comments', micropost: @micropost %>
投稿詳細ページでmicropostを削除すると、micropostの削除自体はできても
ルーティングエラーが起きてしまうため、現在は削除ボタンを使えないようにしています。
###コメント入力フォーム
form_with
のmodelに@micropost
と@comment
が入るようにしないとエラーが起こります。
<div class="col-sm-8">
<%= form_with(model: [@micropost, @comment], local: true) do |f| %>
<div class="form-group">
<%= f.text_area :content, class: 'form-control', rows: 3 %>
</div>
<%= f.submit 'Comment', class: 'btn btn-primary btn-block' %>
<% end %>
</div>
###コメント一覧
<ul class="list-unstyled">
<% @comments.each do |comment| %>
<li class="media mb-3">
<img class="mr-2 rounded" src="<%= gravatar_url(comment.user, { size: 50 }) %>" alt="">
<div class="media-body">
<div>
<%= link_to comment.user.name, user_path(comment.user) %> <span class="text-muted">posted at <%= comment.created_at %></span>
</div>
<div>
<p><%= comment.content %></p>
</div>
<div class="btn-group">
<% if current_user == comment.user %>
<%#= link_to "Delete", micropost_comment_path(comment), method: :delete, data: { confirm: "You sure?" }, class: 'btn btn-danger btn-sm' %>
<% end %>
</div>
</div>
</li>
<% end %>
<%#= paginate comments %>
</ul>
コメント削除機能は正しいルーティングができていないので、まだ未実装です。