How Active Record overwrites standard Ruby methods for counting array elements

Paweł Dąbrowski - Chief Technology Officer
rails, ruby, software, web development

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:

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

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

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:

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 User.select(:name).count. 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:

User.count { |user| user.name.present? } # => 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:

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:

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:

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:

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.


Let’s get in touch
Author's Bio
Paweł Dąbrowski

Chief Technology Officer

Open source fan and growth seeker with over a decade of experience in writing for both human beings and computers. I connect the dots to create high-quality software, valuable relations with people and businesses.

Similar articles
Comments

Bulletproof your development with remote team augmentation

Read how
This page is best viewed in portrait mode
Our websites and web services use cookies. We use cookies and collected data to enhance your experience, provide additional communication channels, improve marketing materials and enhance our offer. IRONIN SP. Z O.O. SP. K. is committed to protecting all the data that we collect or process in any way, especially data of personal nature. By accepting these terms you agree to our usage of cookies and processing your data, according to our Privacy Policy, and you declare that your browser settings reflect your preferences. Read more You have the right to revoke this agreement at any time, based on the terms of our Privacy Policy. You can change cookies settings in your browser. If you do not agree with us using cookies and processing your data, please change your cookies settings in your web browser and reject these terms. You can find more information about cookies, your data privacy This site uses cookies. By continuing to browse the site, you are agreeing to our use of cookies. data processing, and your rights in our Privacy Policy.