With Rails/ActiveRecord, you can count the records in an association using three methods .count
, .length
, .size
.
As a rails newbie, when you first discover this, it can be confusing… Why do we need three methods? Is there any advantage to using one method over the other? How are they different?
Here are 6 characteristics of this three methods that you should know. They will help you decide which method to use on different situations.
I will also share a why I think that most of the time you should start with .size
and the reference to the docs where you can find more information.
COUNT
queryEven if you call it twice.
post = Post.first
# Post Load ...
post.comments.count
# Comment Count (0.6ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 1]]
post.comments.count
# Comment Count (0.3ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 1]]
Even if the collection has already been loaded it will always execute a COUNT
post = Post.first
# Post Load ...
post.comments.load
# Comment Load (0.3ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 1]]
post.comments.count
# Comment Count (0.5ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 1]]
If you later need to use the records, they will be already loaded.
post = Post.first
# Post Load ...
post.comments.length
# Comment Load (0.2ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 1]]
post.comments.to_a
# (no query)
If the collection is already loaded, it won’t need a new database call.
post = Post.first
# Post Load ...
post.comments.to_a
# Comment Load (0.2ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 1]]
post.comments.length
# (no query)
Like with length
, if the collection is already loaded it will compute the length with ruby.
post = Post.first
# Post Load ...
post.comments.load
# Comment Load (0.5ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 1]]
post.comments.size
# (no query)
COUNT
queryLike count
, it will execute a COUNT
if the collection has not been loaded.
post = Post.first
# Post Load ...
post.comments.size
# Comment Count (0.4ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 1]]
post.comments.load
# Comment Load (0.2ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 1]]
And also like count
, if you use it twice it will make two queries.
post = Post.first
# Post Load ...
post.comments.size
# Comment Count (1.5ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 1]]
post.comments.size
# Comment Count (0.7ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 1]]
It will never need a database call.
post = Post.first
# Post Load ...
post.likes.size
# (no query)
post.likes.size
# (no query)
Most people recommend to use .size
unless you have a reason for not using it, because most of the time it will do what you want:
COUNT
if the records are not already loadedWhen you don’t have a counter cache and you need to use the association later in the view, one tip from the Nate Berkopec, is to use .load.size
.
Learn just enough fundamentals to be fluent preloading associations with ActiveRecord, and start helping your team to avoid n+1 queries on production.