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
*.haml files) reveals some stats:
- 34 matches for
- 142 matches for
- 179 matches for
- 185 matches for
- 394 matches for
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.
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,
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…
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
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.
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
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
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.