empty?, blank?, any?, exists? methods of Ruby on Rails ActiveRecord

April 17, 2016

Ruby on Rails ActiveRecord provides several methods for checking if a relation (a query) returns zero or more records: empty?, blank?, any?, and exists?.

  • empty? and blank? return true if a relation returns no records.

  • any? and exists? return true if a relation returns one or more records.

Let's take a closer look at these methods to understand the difference and find out when to use them.

empty? and blank?

The documentation says blank? returns true for blank relations; and empty? returns true for relations with no records. That's not very informative, so let's have a look at the code of the methods.

def blank?
  to_a.blank?
end

and

def empty?
  return @records.empty? if loaded?

  if limit_value == 0
    true
  else
    c = count(:all)
    c.respond_to?(:zero?) ? c.zero? : c.empty?
  end
end

blank? checks if the array representation of the relation is blank. That means that if a relation hasn't been preloaded yet then it loads all its records.

As for empty?, its behaviour varies depending on if the relation has been loaded and may generate a database query to count relation records and return its result.

Clearly, these two methods generate different database queries and thus must have different performance. Let's have a look at the generated SQL queries for a preloaded and not preloaded relation:

User.where(admin: true).empty?
 (0.6ms)  SELECT COUNT(*) FROM `users` WHERE `users`.`admin` = 1
=> false
User.where(admin: true).blank?
  User Load (0.9ms)  SELECT `users`.* FROM `users` WHERE `users`.`admin` = 1
=> false

We can see that in case of a not preloaded relation empty? fetches only the number of records that meet the query condition. blank? fetches all such records. That means that for no preloaded relations empty? is faster than blank?.

Now let's take a look at an example for a relation has already been preloaded.

admins = User.where(admin: true).load
User Load (1.0ms)  SELECT `users`.* FROM `users` WHERE `users`.`admin` = 1
admin.blank?
=> false
admin.empty?
=> false

Both methods in this example check preloaded records in the relation and don't generate database queries. In this case they have very similar performance.

These examples show that from performance perspective it is safer to use empty?. The reason is: in the worst case scenario (for a relation that hasn't been preloaded yet) it will run a more efficient SQL query than blank? would. Note that the more rows a table contains the bigger the difference will be.

any? and exists?

any? accept a block as a parameter. With a block it fetches the records in the relation (unless they were preloaded), represents them as an array and then calls Enumerable#any? on it. Without a block this method is equivalent to !empty? and either retrieves the count of the records in the relation from the database or relies on the preloaded records.

exists? always queries the database and never relies on preloaded records:

User.where(admin: true).exists?
`User Exists (1.9ms)  SELECT  1 AS one FROM `users` WHERE `users`.`admin` = 1 LIMIT 1`

Only one record is retrieved and that makes this method fast compared to any? without a block. exists? also accepts various parameters and uses them to build and SQL query.

If the passed to any? can be represented as a condition for exists? and the relation is not preloaded then exists? would show better performance. For example,

User.any? { |u| u.admin? }
User Load (2.7ms)  SELECT `users`.* FROM `users`
User.exists?(admin: true)
User Exists (0.4ms)  SELECT  1 AS one FROM `users` WHERE `users`.`admin` = 1 LIMIT 1

Summary

To summarise what we've just learnt let's see what would be the most performant way to check if there are users with admin column set to true in users table using Ruby on Rails ActiveRecord.

  • User.where(admin: true).blank? would retrieve all records that have admin equal to true and count them.

  • User.where(admin: true).empty? would retrieve the count of records that have admin equal to true.

  • User.where(admin: true).any? would be exactly the same as the previous option.

  • User.any? { |u| u.admin? } would retrieve all the records from users table and then check if at least one of them has admin? equal to true.

  • User.exists?(admin: true) or User.where(admin: true).exists? would retrieve a record (that can be a random record in the table that satisfies the condition) with admin set to true. That makes this approach fastest and most scalable among those five.

Copyright © 2014-2019 Andrei Gridnev