Zero is not always truthy in the Rails ecosystem

In Ruby, the numeric 0 is a truthy value, meaning it is converted to true when evaluated in a boolean expression. This is unlike languages such as JavaScript and Python, where the numeric 0 is falsy (ie. evaluates as false in a boolean expression). This is also a departure from the underlying hardware that our programs run on, where a 1 bit represents true and a 0 bit represents false. We can check that 0 is truthy in an irb console by using a double bang to to convert 0 to its boolean value:

~ irb
3.0.2 :001 > !!0
 => true 

In Ruby, nil and false are the only falsy values, which is an elegant part of the language’s design. This philosophy of 0 as truthy is seen in some of ActiveSupport’s widely-used methods:

3.0.2 :001 > 0.present?
 => true 
3.0.2 :002 > 0.blank?
 => false 

0.present? returns true because it is not blank. 0 is not blank because it is not logically false, and not considered to be “empty” the way " ", { }, and [] are.

However, did you know that this philosophy of “0 as truthy” doesn’t extend to all methods provided by ActiveRecord and ActiveSupport?

For each attribute on an ActiveRecord object, ActiveRecord generates a “query method” that ends with a ? character. This method checks for the presence of that attribute. For example, if we have a model named Person with an attribute called age, ActiveRecord generates a method named age? that behaves like so:

p = Person.new
p.age = nil
p.age? # returns false
p.age = 18
p.age? # returns true

How does .age? behave when age is set to 0?

p.age = 0
p.age? # returns false

This behaviour (0 returning false when being read in a query method) is well-documented in the ActiveRecord::Base documentation, and its source code in the ActiveRecord codebase is easy to follow. In some cases, this can result in more readable code – for example, if we have a BankAccount model with a balance attribute, we could check if it has a non-zero balance by calling the .balance? method. However, it’s a departure from the 0 as truthy” design philosophy, and so it’s an interesting edge case to consider in the Rails ecosystem!

Start your journey towards writing better software, and watch this space for new content.

How can I retry a Sidekiq job without reporting an error?

When a Sidekiq job raises an error, Sidekiq will retry that job up to 25 times following its exponential backoff schedule (or until the job succeeds). However, there are some cases where we want to retry a job, but don’t want to report an error to whatever monitors our Sidekiq errors.1 Suppose that we have some worker that tries to read a resource from an external API, and that resource may or may not be available yet:

In this case, the most straightforward way to retry without reporting an error is to define some custom error type that we only use to trigger a retry:

It’s important to use a custom error type here, since we don’t want to mask any real errors that could occur in our worker! We can then configure our error monitoring service to ignore our custom ResourceNotYetAvailableSidekiqRetryError. For example, if we’re using Sentry for our error monitoring, we can add this custom error type to config.excluded_exceptions to ignore it.

Sidekiq will then retry our ReadResourceFromExternalAPIWorker according to its exponential backoff schedule,2 which we expect to eventually succeed when the external resource becomes available. If we have configured our error monitoring service correctly for ResourceNotYetAvailableSidekiqRetryError, we will not report any errors when we do this retry.

Start your journey towards writing better software, and watch this space for new content.

1: For example, a high error rate on a particular job could trigger some sort of alerting logic in a system like PagerDuty, which is not the desired behaviour for an expected retry!

2: If we don’t want to follow Sidekiq’s default exponential backoff schedule, the enterprise edition of Sidekiq allows us to define a rate limiter with a custom-defined backoff schedule.