Skip to main content

[ActiveRecord::RecordInvalid - Validation failed: Blog must exist]

Rails • Asked by Linards Berzins

Hi,

trying to sort out this error for a while now, no luck.

I get this error above when adding a new comment for a blogpost. The blog is clearly there. Any insight would be much appreciated.

_comment.html.erb

<div class="comment-card">
    <div class="card">
        <div class="card-block">
            <div class="row">
                <div class="col-md-1">
                </div>
                <div class="col-md-11">
                    <%= comment.content %>
                </div>
            </div>
        </div>
    </div>
</div>

_comment_form.html.erb

<% unless current_user.is_a? GuestUser %>
  <%= form_for @comment, url: '#' do |f| %>
    <div class="form-group">
      <%= f.label :content %>
      <%= f.text_area :content, class: 'form-control' %>
    </div>
    <%= f.submit 'Post Comment', class: 'btn btn-primary' %>
  <% end %>
<% end %>

comment.rb

class Comment < ApplicationRecord
  belongs_to :user
  belongs_to :blog
  validates :content, presence: true, length: { minimum: 5, maximimum: 1000 }
  after_create_commit { CommentBroadcastJob.perform_later(self) }
end

comment_controller.rb

class CommentsController < ApplicationController
  def create
    @comment = current_user.comments.build(comment_params)
  end
  private
  def comment_params
    params.require(:comment).permit(:content)
  end
end

blogs_controller.rb

  def show
    @blog = Blog.includes(:comments).friendly.find(params[:id])
    @comment = Comment.new
    @page_title = @blog.title
    @seo_keywords = @blog.body
  end

Terminal output:
Could not execute command from ({"command"=>"message", "identifier"=>"{\"channel\":\"BlogsChannel\",\"blog_id\":\"\"}", "data"=>"{\"comment\":\"sdgergre\",\"blog_id\":\"\",\"action\":\"send_comment\"}"}) [ActiveRecord::RecordInvalid - Validation failed: Blog must exist]:


You are scoping comments to the User, but not to the blog. So you need to add the blog_id too in the create action.
You can see in your log that the blog_id is empty too.


Thanks Jack,

blog_id is being added actually

blogs_channel.rb

class BlogsChannel < ApplicationCable::Channel
  def subscribed
    stream_from "blogs_#{params['blog_id']}_channel"
  end
  def unsubscribed
  end
  def send_comment(data)
    current_user.comments.create!(content: data['comment'], blog_id: data['blog_id'])
  end
end

blogs_controller.rb

def show
   @blog = Blog.includes(:comments).friendly.find(params[:id])
   @comment = Comment.new
end

show.html.erb

<div class="col-sm-8 blog-main">
    <h2> <%= @blog.title %></h2>
    <%= link_to 'Edit', edit_blog_path(@blog) if logged_in?(:site_admin) %>
    <p><%= @blog.body %></p>

    <%= render 'comments/comment_form' %>

    <div id="comments" data-blog-id="<%= @blog_id %>">
        <%= render @blog.comments %>
    </div>
</div>

I can create the comment in terminal:

[2] pry(main)> Comment.create!(user_id: User.last.id, blog_id: Blog.last.id, content: "1234567")
  User Load (0.4ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1  [["LIMIT", 1]]
  Blog Load (0.2ms)  SELECT  "blogs".* FROM "blogs" ORDER BY "blogs"."id" DESC LIMIT $1  [["LIMIT", 1]]
   (0.1ms)  BEGIN
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  Blog Load (0.1ms)  SELECT  "blogs".* FROM "blogs" WHERE "blogs"."id" = $1 LIMIT $2  [["id", 20], ["LIMIT", 1]]
  SQL (54.8ms)  INSERT INTO "comments" ("content", "user_id", "blog_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["content", "1234567"], ["user_id", 1], ["blog_id", 20], ["created_at", "2018-02-02 05:45:37.058157"], ["updated_at", "2018-02-02 05:45:37.058157"]]
   (9.1ms)  COMMIT
Enqueued CommentBroadcastJob (Job ID: 22604b93-a864-4213-a348-0e0da6c69ee0) to Async(default) with arguments: #<GlobalID:0x007f93f1e3cfb0 @uri=#<URI::GID gid://web-portfolio/Comment/7>>
=> #<Comment:0x007f93f52996f0
 id: 7,
 content: "1234567",
 user_id: 1,
 blog_id: 20,
 created_at: Fri, 02 Feb 2018 05:45:37 UTC +00:00,
 updated_at: Fri, 02 Feb 2018 05:45:37 UTC +00:00>
[3] pry(main)>   Comment Load (0.4ms)  SELECT  "comments".* FROM "comments" WHERE "comments"."id" = $1 LIMIT $2  [["id", 7], ["LIMIT", 1]]
Performing CommentBroadcastJob (Job ID: 22604b93-a864-4213-a348-0e0da6c69ee0) from Async(default) with arguments: #<GlobalID:0x007f93f0fd68b8 @uri=#<URI::GID gid://web-portfolio/Comment/7>>
  Blog Load (0.3ms)  SELECT  "blogs".* FROM "blogs" WHERE "blogs"."id" = $1 LIMIT $2  [["id", 20], ["LIMIT", 1]]
  Rendered comments/_comment.html.erb (1.7ms)
[ActionCable] Broadcasting to blogs_20_channel: {:comment=>"\t<div class=\"comment-card\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"card-block\">\n\t\t\t\t<div class=\"row\">\n\t\t\t\t\t<div class=\"col-md-1\">\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div class=\"col-md-11\">\n\t\t\t\t\t\t1234567\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</div>"}
Performed CommentBroadcastJob (Job ID: 22604b93-a864-4213-a348-0e0da6c69ee0) from Async(default) in 424.54ms

Hi Linards,

I think what Jack is referring to is your comments controller create action:

class CommentsController < ApplicationController
  def create
    @comment = current_user.comments.build(comment_params)
  end
  private
  def comment_params
    params.require(:comment).permit(:content) #you're only allowing :content
  end
end

Your comment params are only permitting :content and in your new comment form you don't pass the blog ID either:

<% unless current_user.is_a? GuestUser %>
  <%= form_for @comment, url: '#' do |f| %>
    <div class="form-group">
      <%= f.label :content %>
      <%= f.text_area :content, class: 'form-control' %>
    </div>
    <%= f.submit 'Post Comment', class: 'btn btn-primary' %>
  <% end %>
<% end %>

And in your show action you're just creating a new comment without associating the @blog.id:

def show
   @blog = Blog.includes(:comments).friendly.find(params[:id])
   @comment = Comment.new # you should be building the comment from the blog
end

So what you should be able to do is something like:

def show
   @blog = Blog.includes(:comments).friendly.find(params[:id])
   @comment = @blog.comments.build
end

Which should now properly pass the :blog_id. You may have to play with this some, I haven't tested and I've only had one cup of coffee so my brains not firing on all cylinders yet but this should get you going in the right direction.

You could also do something like this (but the above is the more railsy way)

<% unless current_user.is_a? GuestUser %>
  <%= form_for @comment, url: '#' do |f| %>
    <div class="form-group">
      <%= f.label :content %>
      <%= f.text_area :content, class: 'form-control' %>
      <%= f.hidden_field :blog_id, @blog.id %>
    </div>
    <%= f.submit 'Post Comment', class: 'btn btn-primary' %>
  <% end %>
<% end %>

And then just add to your permitted params:

class CommentsController < ApplicationController
  def create
    @comment = current_user.comments.build(comment_params)
  end
  private
  def comment_params
    params.require(:comment).permit(:content, :blog_id)
  end
end

Thank you Jacob,

its been sorted now.

Much appreciated!


Login or Create An Account to join the conversation.

Subscribe to the newsletter

Join 24,647+ developers who get early access to new screencasts, articles, guides, updates, and more.

    By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

    More of a social being? We're also on Twitter and YouTube.