Search code examples
rubyvalidationcommentsruby-on-rails-7

Rails 7 "Getting Started with Rails" what is the prefered way to validate comments?


I'm new to rails. Appreciate any help.
I'm trying to extend functionality of the Blog, described in the official Rails 7 guide with comments validation. Here is my github repo link for the project code. What is the right way to add validation to the comments and show error messages on the frontend after submitting invalid form?

Setup
ubuntu - 22.04
rvm - 1.29.12
ruby - 3.1.4p223
rails - 7.0.4.3

Currently if I add these validations on the Comment model validation doesn't work. When I submit an empty comment form it just redirects me to the post page.

/app/models/comment.rb

class Comment < ApplicationRecord
  belongs_to :post
  validates :author, presence: true
  validates :body, presence: true
end

Also tried to handle comment saving in the comments_controller. But it saves the comment with an empty :author and :body fields.

/app/controllers/comments_controller.rb

def create
  @post = Post.find(params[:post_id])
  @comment = @post.comments.build(comment_params)
  if @comment.save
    redirect_to @post
  else
    render @post, status: :unprocessable_entity
  end
end

/app/views/comments/_form.html.erb

<%= form_with model: [@post, @post.comments.build] do |form| %>
  <div class="mb-3">
    <%= form.label :author, class: 'form-label' %><br>
    <%= form.text_field :author, class: 'form-control', placeholder: 'John Doe' %>
  </div>
  <div class="mb-3">
    <%= form.label :body, class: 'form-label' %><br>
    <%= form.text_area :body, class: 'form-control', rows: 3 %>
  </div>
  <div class="mb-3">
    <%= form.submit 'Add comment', class: 'btn btn-outline-primary' %>
  </div>
<% end %>

Here are some visualization that i want to achieve(I've just add some html through dev tools). First screenshot - post page before comment form submission, just clean page. Second screenshot(what I want to ahive) - the same page after i submit comment form with empty Author and Body fields.

First screenshot

Second screenshot(this is what I want comments form to be like)


Solution

  • Make these two changes to your CommentsController class create method when the save fails:

    1. Re-fetch the post: @post = Post.find(params[:post_id])
    2. Change the render line to: render '/posts/show', status: :unprocessable_entity

    The reason we need to re-fetch the post is that when the post's show view is re-rendered (in /posts/show.html.erb), the <%= render @post.comments %> line in the show view will list out all the comments for the post - which will include the new "failing" comment unless we do the re-fetch before rendering on fail.

    So that's the first part of the solution. The next is displaying the error message above the comment form and re-populating the comment form's author and body with their original values. To make that happen, we need to do the following in /posts/show.html.erb

    First, to render the comment form (in your <h4>Add a comment</h4> section), do the following:

    <%= render partial: 'comments/form', locals: { comment: @comment } %>

    This passes the comment down to the comment partial view (/comments/_form.html.erb) for use. Here's a sample of that partial view that will accomplish the goals:

    <p style="color: red"><%= comment ? comment.errors.full_messages.to_sentence : nil %></p>
    
    <%= form_with model: [ @post, @post.comments.build ] do |form| %>
      <p>
        <%= form.label :author %><br>
        <%= text_field_tag 'comment[author]', comment&.author %>
      </p>
    
      <p>
        <%= form.label :body %><br>
        <%= text_area_tag 'comment[body]', comment&.body %>
      </p>
    
      <p>
        <%= form.submit %>
      </p>
    <% end %>
    

    The Safe Navigation operator (&.) checks to see if the object is nil. So, if there is a valid comment for the form (which will only be the case on failed re-render) then display the original data, otherwise, the fields will be empty. You could similarly use the &. syntax on the error message, but you'd need to add it to each property - so I left the "long" version instead.

    Now, when any of your comment model validations fail, an error message will be displayed and the original contents of your failing comment will be re-populated in their respective fields.

    comment error example

    As a final "clean-up", you could consider passing a local post to the /comments/_form.html.erb rather than accessing @post directly in the partial.