Benito Serna Tips and tools for Ruby on Rails developers

Difference between count, length and size in an association with ActiveRecord

April 18, 2022

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.

1. “count” always performs an SQL COUNT query

Even 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]]

2. “length” loads the records and calculates the length with ruby

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)

3. “length” if loaded, only calculates the length with ruby

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)

4. “size” if loaded, calculates the length with ruby

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)

5. “size” if not loaded, performs an SQL COUNT query

Like 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]]

6. “size” if there is a counter cache, uses the cached count

It will never need a database call.

post = Post.first
# Post Load ...
post.likes.size
# (no query)
post.likes.size
# (no query)

Conclusion: Start with “size”

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:

When 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.

Reference

Related articles

Download a free ebook to learn the basics of n+1 queries on Rails basics

Learn just enough fundamentals to be fluent preloading associations with ActiveRecord, and start helping your team to avoid n+1 queries on production.