23 October 2014

The Big Difference Between Empty And None

Given an ActiveRecord class with a has_many association…

class User < ActiveRecord::Base
  has_many :records
  ...
end

…this is a mistake:

user = User.first
user.records.none?

Why, you ask?

Let’s step back for a second. At the database level, the correct way to answer the question, “Are there any records that belong to this user?” is to fire off a SQL statement with a SELECT count(*) clause, and see whether the result is greater than 0. That’s what we want to have happen behind the scenes.

However, that’s not what’s happening when we call none?, because none? is not defined on ActiveRecord::Relation (the fancy/magical Rails class that is responsible for abstracting away cross-table SQL queries), but rather, it is defined on the plain old Ruby module Enumerable.

So, what you might assume (as I did) is doing something smart and reasonable (i.e. performing a SQL count, and comparing the result with zero), is actually doing something VERY stupid (i.e. loading a ton of Ruby objects into memory and performing a count on them there). You can imagine that if users had thousands of records each, this line could constitute a serious drain on your application’s resources.

Solution

Know thy ActiveRecord::Relation methods.

The method you were probably reaching for was ActiveRecord::Relation#empty?, which performs a SQL count(). For reference, here’s a breakdown of some other count-related methods on Enumerable and their ActiveRecord::Relation counterparts.

Question Enumberable Method ActiveRecord::Relation Method
More than zero objects in collection? any? any?
Zero objects in collection? none? empty?
More than one object in collection? x many?
Exactly one object the collection? one? x