BridgeU Engineering

empty? any? present? blank? wat?

We’ve been working with Ruby on Rails for a few years now and yet there are certain subtleties on everyday methods that still catch us off-guard and surprise us. Coming from a front-end background, I’m still learning something every week about the vast list of classes, methods, gems and whatnot.

Checking if a collection is empty or not is one of them. In Rails, there are multiple ways to check this, all more or less interchangeable. A quick search through our code (mainly on *.rb and *.haml files) reveals some stats:

  • 34 matches for .exists?
  • 142 matches for .empty?
  • 179 matches for .blank?
  • 185 matches for .any?
  • 394 matches for .present?

Seeing such numbers makes me believe we’re either very good at knowing when to use each one or that we could probably be more mindful of their inner workings. But what are the differences here? Where do these methods come from? Don’t they all do the same?

Well… yes and no. If you’re only concerned about getting back a boolean that tells you if the collection is empty or not, then yes, they lead to the same. However, they get there through different ways and trigger different side effects to get you that answer. This post will mainly focus on the differences of these methods when called on ActiveRecord collections, since that was the original context that sparked these questions.

blank? and present?

First of all, these two are a Rails extension to the Ruby language, i.e., they won’t be found at Ruby’s standard library. Generally speaking, blank? will tell you if an object is falsy (if you’re familiar with JavaScript, this definition might be clearer to you) and present? will tell you the opposite (object is truthy).

# File activesupport/lib/active_support/core_ext/object/blank.rb, line 19
def blank?
  respond_to?(:empty?) ? !!empty? : !self
end

# File activesupport/lib/active_support/core_ext/object/blank.rb, line 26
def present?
  !blank?
end

These methods can be redefined on a few classes, though. Even though every object in Ruby inherits from the Object class, some tweaks might be made to specific types of objects. blank? is a good example it gets redefined in a few classes:

We don’t want to lose focus in here, so let’s look particularly to the way ActiveRecord collections are loaded when we use these methods to fetch a sample list of users, for example:

pry(main)> users = User.all;
pry(main)> users.class
=> User::ActiveRecord_Relation

pry(main)> users.loaded?
=> false
pry(main)> users.blank?
  User Load (5.9ms)  SELECT "users".* FROM "users"
=> false
pry(main)> users.loaded?
=> true
pry(main)> users = User.all;
pry(main)> users.loaded?
=> false
pry(main)> users.present?
  User Load (5.9ms)  SELECT "users".* FROM "users"
=> true
pry(main)> users.loaded?
=> true

These two methods load the whole collection, we can see that clearly by the User Load query that we see Rails returning back, SELECTing all the records from the users table. This means these methods are good if you are going to use the collection after — for example, showing an empty message if there are no items or iterating through the resulting list to render each one of them. However, if your goal is to only check if a collection is empty or not, there might be a better way…

any?, empty? and exists?

any? is originally defined on the Enumerable class, while empty? comes from Array. In any case, we’re interested in the behaviour they trigger on ActiveRecord collections:

pry(main)> User.all.any?
  User Exists (1.6ms)  SELECT  1 AS one FROM "users" LIMIT $1  [["LIMIT", 1]]
=> true
pry(main)> User.all.empty?
  User Exists (0.6ms)  SELECT  1 AS one FROM "users" LIMIT $1  [["LIMIT", 1]]
=> false

Both of them trigger the same SELECT query, picking a single record from the table. The same can be achieved with the ActiveRecord::FinderMethods#exists? actually:

pry(main)> User.all.exists?
  User Exists (6.8ms)  SELECT  1 AS one FROM "users" LIMIT $1  [["LIMIT", 1]]
=> true

Since the query they trigger is simply an EXISTS, it is not surprising that the whole collection is not loaded after running them:

pry(main)> users = User.all;
pry(main)> users.loaded?
=> false

pry(main)> users.any?
  User Exists (1.0ms)  SELECT  1 AS one FROM "users" LIMIT $1  [["LIMIT", 1]]
=> true
pry(main)> users.loaded?
=> false

pry(main)> users.empty?
  User Exists (1.6ms)  SELECT  1 AS one FROM "users" LIMIT $1  [["LIMIT", 1]]
=> false
pry(main)> users.loaded?
=> false

pry(main)> users.exists?
  User Exists (1.3ms)  SELECT  1 AS one FROM "users" LIMIT $1  [["LIMIT", 1]]
=> true
pry(main)> users.loaded?
=> false

This distinction is important: in situations where we only need to see if a collection is empty or not to decide what to render, not having to load any data besides running a quick EXISTS could saves us precious seconds.

Summary

If you’re looking for a quick way to see if a collection is empty or not, maybe because you just want to see if you need to display a link to another page, then any?, empty? or exists? are great options for you. You’ll be able to perform a blazing fast SQL query without fetching anything else.

However, if you’re looking at a situation where you either want to show an empty screen or render your whole list, feel free to go for present? or blank?. These methods will load the collection straight away and save you one EXISTS query. You’re going to load the data so you can render it, so might as well get it as soon as you need it.

Of course, the performance impact of these changes will be heavily dependent on the queries you’re running and the dataset you could potentially load. However, we believe that being mindful of these consequences and picking the best method for the right situation, over and over again as you build a product, will allow you to build on top of quality.

Useful resources