I’ve seen a lot of Rails developers abuse Active Support’s present? method in views and controllers. Most of the times what they want to check is that an object is not nil. Rails’ present? checks if an item is not empty and I think the usage of present? for this purpose is overkill.

For an idea of what I mean, here’s a snippet I found in an actual open-source project.

if params[:reply_to].present?
  @comment.reply_to = params[:reply_to].to_i
  ...
end

Let’s look at how present? is implemented:

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

According to the documentation

An object is present if it’s not blank.

It returns a boolean and it calls empty? under the hood looks like this:

 def blank?
    respond_to?(:empty?) ? !!empty? : !self
  end

The critical part here is respond_to?(:empty?) where blank? checks the object on which it’s called whether it responds to the empty?, to do this it has to check whether the object is not an instance of some list of classes that implement empty?. Let’s turn to IRB for an idea of how the list may look like:

ObjectSpace.each_object(Class).select { |klass| klass.instance_methods.include? :empty? }

 # => [Thread::SizedQueue, Thread::Queue, #<Class:FileTest>, #<Class:#<Object:0x00007fdc4b0905b8>>, Hash, Array, Warning::buffer, Symbol, String, SortedSet, Set, Gem::RequestSet::Lockfile::Tokenizer, Gem::Resolver::RequirementList, #<Class:#<Object:0x00007fdc4d0b5280>>, #<Class:#<Hash:0x00007fdc4d0e7230>>, #<Class:#<String:0x00007fdc4d0e6c40>>, #<Class:#<String:0x00007fdc4d0e6cb8>>]

Here we’re checking 17 objects whether or not they implement an empty? method; this is not inside of a Rails app. The list is much longer in a Rails app.

For classes that do implement empty?present? does more work than checking for nil. And since at runtime, a lot of classes implement empty? and you have the respond_to? check, you can expect it to be slower. But more importantly, with that many different implementations on different classes, it’s hard to keep in mind what it does. But we can avoid it by only getting rid of it, backed by tests and making sure nil is what we expect for the absence of an object. The same is true for when present? is used in views.

The params check above could instead be simply modified to avoid present?

if params[:reply_to]
  @comment.reply_to = params[:reply_to].to_i
  #...
end

Here’s a simple benchmark to back for some idea of how slower we can get our apps.

require 'active_support/all'
require 'benchmark/ips'

params = { reply_to: "Emmanuel" }

Benchmark.ips do |x|
  x.report("prezent") { 10000.times { params.present? }}
  x.report("abzent") { 10000.times { params }}

  x.compare!
end

Warming up --------------------------------------
             prezent   128.000  i/100ms
              abzent   251.000  i/100ms
Calculating -------------------------------------
             prezent      1.273k (± 1.5%) i/s -      6.400k in   5.028756s
              abzent      2.520k (± 1.7%) i/s -     12.801k in   5.081013s

Comparison:
              abzent:     2520.1 i/s
             prezent:     1273.0 i/s - 1.98x  slower

This is all pernickety. But recently I found a tweet that summarises the rest of my thoughts:

ActiveSupport's #present?/#blank? adds additional uncertainty on what type an object is.

If it's clear that an object can either be a truthy object (array, string, number) or nil, then just use `if object`. It's shorter and increases confidence.— Janko Marohnić (@jankomarohnic) January 24, 2020

Active Support is excellent and has so many useful methods that I wish were part of Ruby Core, but some of these methods come with some overhead cost, in most cases, we can prevent these. And seemingly minimal, watching out for little things like this may help with some speed gains.

Last Update: January 07, 2024