Reputation: 1201
I have a basic "blog" application with posts and comments. I'd like to allow users to comment on a post from post#show
and I'm not exactly sure how to present comment validation errors on the post.
The code below nests comments under posts and will successfully create comments. The issue when when a validation fails (such as a blank comment) the visitor is redirected to the post show page but the comment error messages are lost.
Right now CommentsController#create
is redirecting the user to the post
he was viewing. Alternatively I had tried render 'posts/show'
instead of a redirect but it ends up rendering the page at posts/1/comments
instead of posts/1
.
Any help getting errors for comment validations displayed on the posts/show template would be greatly appreciated.
# config/routes.rb
Rails.application.routes.draw do
resources :posts do
resources :comments, only: [:create]
end
end
class Comment < ApplicationRecord
belongs_to :post
validates :content, presence: true, length: { in: 6..20 }
end
class Post < ApplicationRecord
has_many :comments
end
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
end
end
# app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
@post = Post.find(params[:post_id])
@comment = @post.comments.build(comment_params)
if @comment.save
# SAVE works fine
redirect_to @post, notice: 'Comment was successfully created.'
else
# ERROR displays nothing on the post show page
redirect_to @post
end
end
private
def comment_params
params.require(:comment).permit(:content, :post_id)
end
end
# app/views/posts/show.html.erb
<p id="notice"><%= notice %></p>
<p>
<strong>Title:</strong>
<%= @post.title %>
</p>
<h2>Comments</h2>
<table>
<thead>
<tr>
<th>Content</th>
</tr>
</thead>
<tbody>
<% @post.comments.each do |comment| %>
<tr>
<td><%= comment.content %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<h3>New Comment</h3>
<%= render 'comments/form', post: @post, comment: @post.comments.build %>
# app/views/comments/_form.html.erb
<%= form_with(model: [post, comment], local: true) do |form| %>
<% if comment.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(comment.errors.count, "error") %> prohibited this comment from being saved:</h2>
<ul>
<% comment.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :content %>
<%= form.text_field :content %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
Note: I am trying to avoid accepts_nested_attributes_for
if possible as for more complex use cases I find it to be extremely confusing.
Upvotes: 0
Views: 855
Reputation: 1407
You don't see the error message because on a failed save attempt, you redirect to a different controller/action; unsaved @comment
object is not only gone, but also overwritten in view layer when comment form is rendered. Try rendering the posts/show
view in CommentsController#create
action.
if @comment.save
# SAVE works fine
redirect_to @post, notice: 'Comment was successfully created.'
else
render 'posts/show'
end
For this to work, you also need to move instantianting of a new comment from view to PostsController#show
action.
# app/controllers/comments_controller.rb
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
@comments = @post.comments
@comment = Comment.new(post: @post)
end
end
Iterate on @comments
loaded in controller. New commment shouldn't be included among them. When rendering the form, use @comment
variable. Remember to associate new comment with post before saving it.
<h2>Comments</h2>
<table>
<thead>
<tr>
<th>Content</th>
</tr>
</thead>
<tbody>
<% @comments.each do |comment| %>
<tr>
<td><%= comment.content %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<h3>New Comment</h3>
<%= render 'comments/form', post: @post, comment: @comment %>
This way posts/show
view should work with both controller actions; state of @comment
object, along with it's errors, should be preserved after save attempt.
Upvotes: 2