目的
自作アプリで投稿にコメントができる機能を作成。
その際出てくるエラーの解決に時間がかかったので、解決までの考え方を備忘録として記事にしました。
環境
windows10 home
Ubuntu 20.04.1 on WSL2
rails 6.0.3
ruby 2.6.6
起きた現象
空のコメントを送信するとInvalid dateエラーが発生。
コメントモデルのカラムは以下に記載
- id
- content(コメントの中身)
- user_id(外部キー)
- micropost_id(外部キー)
- created_at
- updated_at
①ビューを確認
<% @comments.each do |c| %>
<div class="d-flex">
<div class="d-flex">
<%= link_to user_path(c.user) do %>
<%= image_tag c.user.portrait.to_s, size: "40x40" %>
<% end %>
</div>
<div class="d-flex flex-column">
<small class="text-muted"><%= c.user.name %></small>
<div>
<small><%= simple_format(h(c.content)) %></small>
</div>
</div>
</div>
<div class="d-flex flex-row mb-3 border-bottom border-dark">
<div class="d-flex align-items-center">
<small><%= Date.parse(c.created_at.to_s).strftime("%m月%d日") %></small> <%# ここでエラー発生 %>
</div>
<% if c.user.id == current_user.id %>
<%= link_to "削除", micropost_comment_path(@micropost, c), method: :delete, data: {confirm: "コメントを削除しますか?"}, class: "btn btn-sm btn-light ml-2" %>
<% end %>
</div>
<% end %>
日付を「〇〇月〇〇日」の形で表示させるところでエラーが発生している。
このメソッドは他のページでも使用しており、その際エラーがでていないことから
他の部分に不具合があり、それがここで引っかかっているのではないかと考えました。
②バリデーションを確認
まずはコメントモデルのバリデーションがちゃんと記述できているか確かめる。
class Comment < ApplicationRecord
belongs_to :user
belongs_to :micropost
validates :content, presence: true, length: {maximum: 200}
validates :user_id, {presence: true}
validates :micropost_id, {presence: true}
end
モデルのバリデーションに問題はなさそう。
とすると、データベースのNotNull制約に何か問題があるのかも。
参考:【Rails】「テーブルのカラムに定義するNot Null制約」と「モデルに定義するバリデーション(presence: true)」の挙動の違い。
データベースの中身を調べてみる。
mysql> desc comments;
+--------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-------------+------+-----+---------+----------------+
| id | bigint | NO | PRI | NULL | auto_increment |
| content | text | NO | | NULL | |
| user_id | bigint | NO | MUL | NULL | |
| micropost_id | bigint | NO | MUL | NULL | |
| created_at | datetime(6) | NO | | NULL | |
| updated_at | datetime(6) | NO | | NULL | |
+--------------+-------------+------+-----+---------+----------------+
6 rows in set (0.03 sec)
ちゃんと:contentカラムにNotNull制約がついているのを確認。
どうやらバリデーションに問題はないみたい。
③コンソールで確認
次に空投稿が送信されたときの動きをコンソールで調べてみる。
pry(main)> c = Comment.new(user_id:33, micropost_id:76, content:"")
=> #<Comment:0x0000563201d95c18
id: nil,
content: "",
user_id: 33,
micropost_id: 76,
created_at: nil,
updated_at: nil>
pry(main)> c.save!
ROLLBACK
ActiveRecord::RecordInvalid: バリデーションに失敗しました: Contentを入力してください
from /home/XXXXX/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/activerecord-6.0.3.5/lib/active_record/validations.rb:80:in `raise_validation_error'
contentが空のコメントはちゃんとバリデーションで引っかかっている。
...ん?じゃあなんでInvalid dateエラーになるんだ?
空コメントのインスタンスが何らかの原因でcreateされてるってこと?
④コントローラーを確認
投稿詳細ページにコメントを表示させるので、
コメントとマイクロポスト2つのコントローラーを確認する。
class CommentsController < ApplicationController
before_action :authenticate_user!
before_action :set_micropost
def create
@comments = @micropost.comments
@comment = @micropost.comments.new(comment_params)
@comment.user = current_user
if @comment.save
flash[:primary] = "コメントしました"
redirect_to @micropost
else
flash.now[:danger] = "コメントを入力してください"
render("microposts/show")
end
end
・・・
(略)
・・・
private
def set_micropost
@micropost = Micropost.find_by(id: params[:micropost_id])
end
def comment_params
params.required(:comment).permit(:content).merge(user_id: current_user.id, micropost_id: params[:micropost_id])
end
end
def show
@micropost = Micropost.find_by(id: params[:id])
@comments = @micropost.comments
@comment = Comment.new # コメントフォームのインスタンスはmicropost/showで作成
end
マイクロポストコントローラーに問題はなさそう。
そうなるとコメントコントローラーのcreateアクションに原因がありそう。
⑤原因判明
createアクションを見ていて最初に定義した@commentsと次に定義した@commentが
@micropost.commentsに紐付いているということに気づいた。
createされた@commentはすぐに@micropost.commentsとして組み込まれる。
組み込まれた@commentをビューが表示しようとするが、
created_atがnilなのでInvalid dateエラーになる。
つまり@commentを定義するとき@micropostと紐付けるのをやめればいいのでは?
【変更前】
・・・
def create
@comments = @micropost.comments
@comment = @micropost.comments.new(comment_params)
@comment.user = current_user
if @comment.save
flash[:primary] = "コメントしました"
redirect_to @micropost
else
flash.now[:danger] = "コメントを入力してください"
render("microposts/show")
end
end
・・・
【変更後】
・・・
def create
@comments = @micropost.comments
@comment = Comment.new(comment_params) # 変更
@comment.user = current_user
if @comment.save
flash[:primary] = "コメントしました"
redirect_to @micropost
else
flash.now[:danger] = "コメントを入力してください"
render("microposts/show")
end
end
・・・