December 24, 2018 · activesupport rails ruby

ActiveSupport #blank? vs #empty?

The #blank? and #empty? methods are used to determine whether something is there or not. What determines “there-ness” varies by object type, but the idea is the same: does it contain something? In Ruby, only nil and false are falsey with everything else being truthy. But implicit truthiness doesn’t tell the full story.

If a user submits a form and leaves a field blank, the field will often come through as an empty string. Ruby gladly tells you “yes,” there is a string. But, for practical purposes an empty String is often considered nothing since the user typed nothing. Likewise, an empty array has the essence of nothing, yet Ruby will tell you “yes,” there is an array. That’s where #blank? and #empty? come in; they aim to provide intuitive definitions for “there-ness”.

#blank? is the opposite of #present?

ActiveSupport is a gem that grew out of Ruby on Rails and continues to live in the Rails repo. It is a stand-alone library that extends core Ruby classes with useful methods, but its inclusion in Rails can sometimes make it difficult tell where Ruby ends ActiveSupport begins. #present? and #blank? are a couple of good examples of this; they are frequently used and magically part of all objects. But where do they come from?

#present? is defined in the source code of ActiveSupport as follows:

# lib/active_support/core_ext/object/blank.rb
class Object
  def present?
    !blank?
  end
end

It’s a monkey patched method on Object which makes it available as an instance method on every object. And it’s defined as the exact opposite of #blank?. So to understand #present?, we need to first understand its opposite.

#blank? is basically an alias for #empty?

#blank? is implemented ten times in the ActiveSupport library, but the most import is Object#blank?. By extending Object with both #blank? and #present?, we can safely call either method on any object in the system… even nil.

Here’s the definition of Object#blank?:

# lib/active_support/core_ext/object/blank.rb
class Object
  def blank?
    respond_to?(:empty?) ? !!empty? : !self
  end
end

We see that #blank?‘s default behavior is as an alias to #empty? coerced to a boolean. But why #empty?? Well it turns out this takes advantage of 33 definitions of #empty? defined in Ruby 2.5 core. So it makes sense to build on top of existing behavior and redefine the bits that are different.

And for the other case where an object does not implement #empty?, then the method returns the opposite of that object’s implicit truthiness (!self).

A closer look at what’s #empty?

So to better understand #blank?, let’s look at a few of the Ruby definitions for #empty?.

Array#empty?

An array is empty when it does not have any items. This matches up perfectly with the Ruby implementation for Array#empty? which returns whether array.length == 0:

// ruby/array.c
static VALUE
rb_ary_empty_p(VALUE ary)
{
  if (RARRAY_LEN(ary) == 0)
    return Qtrue;
  return Qfalse;
}

Hash#empty?

Likewise, Hash checks whether hash.size == 0:

// ruby/hash.c
static inline int
RHASH_TABLE_EMPTY_P(VALUE hash)
{
  return RHASH_SIZE(hash) == 0;
}
// …
static VALUE
rb_hash_empty_p(VALUE hash)
{
    return RHASH_EMPTY_P(hash) ? Qtrue : Qfalse;
}

String#empty?

String is a bit less intuitive, though makes sense alongside Array#empty? and Hash#empty. It’s defined as string.length == 0:

// ruby/string.c
static VALUE
rb_str_empty(VALUE str)
{
  if (RSTRING_LEN(str) == 0)
    return Qtrue;
  return Qfalse;
}

The gotcha is, it doesn’t account for whitespace, e.g., " " containing a single space is not considered empty since it has a length of 1, even though we might talk about it being an “empty string.” This is a scenario where ActiveSupport steps in and redefines behavior for String#blank?:

# lib/active_support/core_ext/object/blank.rb
class String
  BLANK_RE = /\A[[:space:]]*\z/

  def blank?
    # The regexp that matches blank strings is expensive. For the case of empty
    # strings we can speed up this method (~3.5x) with an empty? call. The
    # penalty for the rest of strings is marginal.
    empty? ||
      begin
        BLANK_RE.match?(self)
      rescue Encoding::CompatibilityError
        ENCODED_BLANKS[self.encoding].match?(self)
      end
  end
end

I think the interesting bit to this implementation is that it defers back to #empty? for performance reasons. The implementation that Ruby went with is efficient which makes it a good choice for the core library. Then only if the string is not empty do we regex to see if every character is whitespace.

Why not just use #empty?

What surprised me about ActiveSupport’s implementation of #blank? (and by extension, #present?) is how it is basically an alias for #empty?. It pushes me toward using #empty? more, especially when performance is critical. So the benefits of #present?/#blank? are normalizing interface and enabling “empty checks” on all objects (even nil), rather than redefining behaviour. There’s also the nicety of array.present? (possibly) reading better than !array.empty?, though on the flip side I think array.empty? reads better than array.blank?.

#presence (Bonus XP)

A related method that’s useful to know about is #presence which is based on #present? and available on all objects. It checks whether an object is #present?, and if so returns the object; otherwise you get nil.

# activesupport/lib/active_support/core_ext/object/blank.rb
class Object
  def presence
    self if present?
  end
end

Here are some examples of how it works:

"Alice".presence
#=> "Alice"
nil.presence
#=> nil
"".presence
#=> nil
"Alice".presence || "Guest"
#=> "Alice"
nil.presence || "Guest"
#=> "Guest"
"".presence || "Guest"
#=> "Guest"

It’s a great way to avoid if-present ternaries. I find it especially useful in initializers:

# example using `present?`
def initialize(name)
  @name = name.present? ? name : "Guest"
end

# refactored to use `presence`
def initialize(name)
  @name = name.presence || "Guest"
end

tl;dr