In the world of Ruby on Rails, we’ve got three cool methods for figuring out how big our collections are: “length,” “count,” and “size.” They each have their quirks, and knowing the scoop to keep your code running smoothly is good.
- “length”: it loads all the records into memory first and then does a headcount there. If you plan to use those records later, using “length” to count active records can save you a query.
- “count”: The rebel of the group! It always runs a COUNT query, no matter when you ask. So, you get the real-deal count, but be ready for some extra queries.
- “size”: This one’s a bit of a chameleon. If records are still chillin’ in the database, “size” will run a COUNT query. But if the gang’s already in memory, it does a quick in-memory headcount. It’s like having the best of both worlds, depending on your collection’s vibe.
Oh, and when your collection’s already loaded, “size” and “length” are like twins — same results, different names.
So, let’s dive into the quirks of “length,” “count,” and “size” in Ruby on Rails.
The method .count
in Ruby on Rails
.count
will ALWAYS run a COUNT query to the database:
users = User.all
users.count
# User Count (0.5ms) SELECT COUNT(*) FROM "users"
# => 10
Yep, always! Even if you run .count
twice, it will run two queries:
users = User.all; users.count; users.count
# User Count (0.5ms) SELECT COUNT(*) FROM "users"
# User Count (0.5ms) SELECT COUNT(*) FROM "users"
# => 10
.count
doesn’t load records in memory, which means that the following will run another query:
users = User.all
users.count
# (...)
users.first
# User Load (0.4ms) SELECT "users".* FROM "users" ORDER BY "users". "id" ASC LIMIT $1 [["LIMIT", 1]]
# => <User id:...
And it will run a COUNT query even if the records are already loaded:
users = User.all.load
# User Load (0.5ms) SELECT "users". * FROM "users"
# => [#<User id: 1...
users.count
# User Count (0.5ms) SELECT COUNT(*) FROM "users"
# => 10
The method .length
in Ruby on Rails
On the other hand, .length
will load the records in memory (if needed):
users = User.all
# User Load (0.6ms) SELECT "users".* FROM "users" LIMIT $1 [["LIMIT", 11]]
users.length
# User Load (0.3ms) SELECT "users".* FROM "users"
# => 10
So if you run .length
twice, it will run only one query:
users = User.all; users.length; users.length
# User Load (0.3ms) SELECT "users".* FROM "users"
# => 10
This also means that this won’t run another query:
users = User.all; users.length
# User Load (0.3ms) SELECT "users".* FROM "users"
# => 10
users.first
# => <User id:...
The method .size
in Ruby on Rails
Last, .size
runs a COUNT query if the records are not loaded:
users = User.all
# User Load (2.2ms) SELECT "users".* FROM "users"
users.size
# User Count (0.7ms) SELECT COUNT(*) FROM "users"
# => 10
So even if you run .size
twice, it will run two queries:
users = User.all; users.size; users.size
# User Count (1.2ms) SELECT COUNT(*) FROM "users"
# User Count (0.4ms) SELECT COUNT(*) FROM "users"
# => 10
But .size
will be smart enough not to re-run a query if the collection is already loaded:
users = User.all.load; users.size; users.size
# User Load (0.5ms) SELECT "users".* FROM "users"
# => 10