1

Background

I was learning from rails guide when I encountered this queer behaviour cause by build

So in the guide I was making a blog where posts had comments section. In the guide they made the comments posted appear before the comments form for adding new comments. Somehow I wanted to tried the other way around (comments form first). However when I did that, additional empty tags <h4></h4><p></p> were rendered.

Initially I thought it was rendering an empty comment from the model but after running

<%= @article.comments.count %> # => 2 gives expected comments count

Now comes the queer part. When I inverted the order as per the guide, comments form first then comments, the empty tags disappeared and everything works fine.

Question

  1. How do I fix it? (solved)
  2. Why does changing the order of the form and the comments cause the 'bug' to disappear?

View

#This works
#comments
<h3>Comments</h3>
<%= render @article.comments %>

#comments form
<h3>Add a comment!</h3>
<%= render 'comments/form' %>

#But not this
#comments form
<h3>Add a comment!</h3>
<%= render 'comments/form' %>

#comments
<h3>Comments</h3>
<%= render @article.comments %>

Partials

comment partial

<h4>
  <%= comment.commenter %>
</h4>
<p>
  <%= comment.body %>
</p>

comment form partial

<%= form_for([@article, @article.comments.build]) do |f| %>
  <p class="commenter">
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p class="text">
    <%= f.label :body %><br>
    <%= f.text_area :body %>      
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>

Summary of the answers/findings

Solution

Comment.new is used instead of @article.comments.build as .build is making an extra instance (is this a bug?)

new vs build

According to Kris

.new method has changed since Rails > 3.2.13

new and build are the same. build is merely an alias

Comparison of size vs count

I found this SO post about count vs size and the recommended reading in that post. In case someone passes by and wants to know more about the subtlety.

In essence (from the SO post) and @Jiří Pospíšil answer count sends a query to the db to retrieve the number of elements. In this context @article.comments.count returns the number of comments in the DB

length give the number of comments loaded into memory that said, memory and db data might not be the same. Some elements in the memory might be new.

size as @Jiří Pospíšil will give the number of elements in the collection if it has been loaded (like length) else works like count and sends a SQL COUNT query

#when .build was used
<%= @article.comments.length %> # => 2
<%= @article.comments.count %> # => 1
<%= @article.comments.size %> # => 2

and when the proposed solution Comment.new was used, all methods return 1 which is consistent with what this guy said

EDIT

Stated questions more explicitly
added summary of answers/discussion

Community
  • 1
  • 1
Gerald
  • 567
  • 1
  • 10
  • 17

2 Answers2

2
<%= form_for([@article, @article.comments.build]) do |f| %>

The @article.comments.build part will create a new Comment and add it to the @article.comments collection. Later on, you iterate over the collection and that's why there's one more than should be. You can actually see it happening if you do something like this.

<%= form_for([@article, @article.comments.build(commenter: "Hello!")]) do |f| %>

To get around the issue, you need to create a Comment but not associate it with the collection. Using Comment.new instead of @article.comments.build should be enough as the record itself is not important.

Note that the reason you're seeing the right number of comments (@article.comments.count) is because the comment created via build hasn't been saved to the database and #count always perform a COUNT query regardless of whether the collection has already been loaded. You could see the extra comment by using #size instead (@article.comments.size).

Jiří Pospíšil
  • 14,296
  • 2
  • 41
  • 52
  • The extra tags still appear. – Gerald Jan 03 '15 at 16:57
  • @madsonic Can you try using just `Comment.new`? – Jiří Pospíšil Jan 03 '15 at 17:07
  • `<%= form_for([@article, Comments.new]) do |f| %>` like this? – Gerald Jan 04 '15 at 03:24
  • @madsonic `form_for([@article, Comment.new])` - note the singular form of Comment, it's the name of the model. Also expanded the answer a bit to explain the "incorrect" number of records displayed. – Jiří Pospíšil Jan 04 '15 at 13:40
  • It works! About `count` vs `size`, am I right to say that `count` counts all instances regardless of whether they are saved or not whereas `size` only counts those in the database? – Gerald Jan 04 '15 at 14:20
  • @madsonic The other way around. `count` will always perform an SQL query so only saved records (in the database) will be counted. `size` tries to be smart and looks first whether the records for the collection have been loaded. If they have, it doesn't do any DB work and asks the collection itself for the number. Otherwise it will query the DB. If you use `build/new`, the record is created in memory (*not* yet saved to the DB - will not be counted with `count`) *and* added to the collection (will be counted with `size`). – Jiří Pospíšil Jan 04 '15 at 14:38
  • ok. Do you have any idea why the original code doesn't display the extra instance when the comment form was on top? – Gerald Jan 05 '15 at 04:35
0

The problem is that your comment form partial calls @article.comments.build, which besides constructing a new Comment instance also adds this instance to the collection. So that's why your seeing an extra comment. Try using @article.comments.new instead.

See also: Build vs new in Rails 3

Edit

I just found out that the .new method has changed since Rails > 3.2.13. And since you've tagged your post with Rails-4, the solution I proposed doesn't work. Instead, try using @article.comments.scoped.new as proposed here.

Community
  • 1
  • 1
Kris
  • 2,108
  • 18
  • 19
  • I believe `.scoped` is deprecated in rails 4 [see this post](http://stackoverflow.com/questions/18198963/with-rails-4-model-scoped-is-deprecated-but-model-all-cant-replace-it') – Gerald Jan 04 '15 at 03:23