See all articles

How Active Record overwrites standard Ruby methods for counting array elements

Paweł Dąbrowski-Chief Technology Officer

It may appear that counting records in a database with ActiveRecord is pretty simple. Well, it is true, but only if you understand the differences between the `size`, `count`, and `length` methods and when is the right time to use each of them.

We recently ran an online contest for Ruby developers called Mortal Coding, and we asked them the following question:

Which of the methods mentioned below does not have its implementation in the Active Record library?

1. length
2. count
3. size
4. all mentioned methods have

The correct answer is a. Unfortunately, only 19% of the developers answered correctly. We decided to follow up on this topic. In today’s article, we don’t only provide the correct answer, but also zoom in on different methods for counting things in Ruby. This knowledge should come in handy in daily development for any RoR engineer.

Curious why Active Record doesn’t have its implementation of the length method? Let’s see.

Counting records with Active Record

Counting records in the database seems to be a straightforward task. However, choosing the wrong method can slow down your application. How much?

In Ruby, we have three methods for counting things at our disposal: `size`, `count`, and `length`. Each time we want to count elements, we must choose which method we want to use. Our choice is essential when it comes to Rails.

The count method

In pure Ruby, there are three ways to use the `count` method. We can simply count the elements in the array:

1 2 names = ["Tim", "Tina", "Mike", "Tina"] names.count # => 4

We can also pass an argument, either as a normal argument or a block:

1 2 3 names = ["Tim", "Tina", "Mike", "Tina"] names.count("Tina") # => 2 names.count { |name| name.start_with?("T") } # => 3

Then, it counts the number of occurrences of the passed value or the number of occurrences that evaluates to true in the block.

In Ruby on Rails, similar to pure Ruby’s implementation, we can call `count` on a model class without arguments, pass one argument or pass a block. In two first cases, Rails will generate SQL COUNT query:

1 2 3 User.count # => 4 User.count(:name) # => 1 # SELECT COUNT(*) FROM "users"

When calling `User.count(:name)`, we get the count of records in the users table where the column name has value. This call is equivalent to ``. However, when we would pass a block to count method, Rails will generate SQL SELECT query and return the count of records that evaluates to true:

1 2 User.count { |user| } # => 1 # SELECT "users".* FROM "users"

This last invocation of the `count` method can consume a lot of memory depending on your database size.

In a Ruby on Rails application, use `count` to get the number of records whenever you are sure that the records are not loaded into memory, and they won’t be.

The size method

In pure Ruby, the `size` method just counts the elements in the array.

In Ruby on Rails, the `size` method is more complex and works differently depending on the context.

When your records are not yet loaded into memory, calling size method will produce the SQL COUNT query:

1 2 User.where(name: nil).size # => 1 # SELECT COUNT(*) FROM "users" WHERE "users"."name" IS NULL

However, when your records are already loaded (for example, you want to count them in the view), size won’t produce SQL query but count the elements from memory:

1 2 users = User.where(name: nil) # load into memory users.size # => 4

If you used the count method in the same situation, Rails would call the database for a second time:

1 2 3 4 users = User.where(name: nil) # load into memory # SELECT "users".* FROM "users" WHERE "users"."name" IS NULL users.size # => 4 # SELECT COUNT(*) FROM "users" WHERE "users"."name" IS NULL

In the Ruby on Rails application, use `size` to count elements in places where you know that elements are already in memory (like views) or you are not sure if they are in memory.

The length method

In pure Ruby, the `length` method works the same as the size. It counts the number of elements in the array. However, `size` is not an alias for `length` (in documentation, size does not have its section) – under the hood, they work the same way, but Ruby creators give us some space to implement our versions of those methods (and Rails does it).

In Ruby on Rails, the `length` method does not have a different implementation than pure Ruby. The method is mentioned in the documentation, but that’s it. Rails’ creators wanted developers to know how it would work with Active Record. For example, if you would call length on a vast collection, all records will be loaded into memory and then counted:

1 2 User.where(name: nil).length # SELECT "users".* FROM "users" WHERE "users"."name" IS NULL

We advise not to use `length` with Active Record and only choose between the size and count.

# The right answer

Among the `count`, `length,` and `size` methods, only the length does not have its implementation in the Active Record library. This knowledge is precious and will help you avoid performance issues in the future.

Soon we will publish more articles regarding the hardest questions from the Mortal Coding challenge. Stay tuned!

In case you’re planning to start a Ruby on Rails project, and are looking for an experienced development team, let’s chat about how we can help you build the product you’re proud of.

Bulletproof your development with remote team augmentation

Build your product and prevent possible issues with development process.