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.

Can we write “interrobang” methods in Ruby on Rails?

By convention in Ruby on Rails applications, methods ending with a ? character return a boolean, while methods ending with a ! character may modify some state or raise an exception. These are referred to as boolean methods and bang methods:

However, what if we wanted to write a method that modified some state and returned a boolean? For example, the SETNX method in Redis will attempt to set a key, but will only succeed if the key is not currently set. This method returns true or false based on if it succeeded. If we were to write a wrapper around the SETNX method, we might try to name it something like modify_some_state!? to communicate that it may have modified some state and returned a boolean.

Does Ruby on Rails allow us to write a method name using !? as its ending? For example, what happens if we try to run this code?

If we try to load Foo in the Rails console, we see the following error:

$ rails c
Running via Spring preloader in process 15120
Loading development environment (Rails 6.1.3)
2.5.1 :001 > Foo.new
Traceback (most recent call last):
        1: from (irb):1
SyntaxError (/Users/eric/some_app/app/models/foo.rb:2: syntax error, unexpected '?', expecting ';' or '\n')
    def modify_some_state!?

Unfortunately, this results in a syntax error in our application, since we can only use a ! or ? character at the end of a method name.

However, Ruby allows us to use non-ASCII characters in our method names! The interrobang (represented as ) is a punctuation mark that combines the exclamation and question marks. If we really wanted to, we could use the Unicode character in a Ruby method name. The following Foo definition is valid Ruby code:

However, this code has the potential to be extremely confusing and unmaintainable. The character looks similar to a question mark, and doesn’t appear on a standard QWERTY keyboard. So, even if the above naming convention is possible, I would not recommend actually using it when writing Ruby code 🙂

Here’s my recommendation on naming a method that returns a boolean and modifies state: if you’re ending your method with ?, use the rest of the method name to make it clear that you’re modifying some state. If you’re ending your method with !, use the rest of the method name to make it clear that you’re returning a boolean.

Returning to the SETNX wrapper example, two potential names we could use for that method are:

  • successfully_set_key?: the ? communicates that we’re returning a boolean, but the set verb also shows that we’re modifying some state.
  • set_key_if_empty!: the ! communicates that we’re modifying some state, but the conditional if implies some boolean value associated with the result of the method.

Selecting a ? or ! suffix is also dependent on how the method will be used within the program. A method used as a guard clause, for example, should end with a ?. A method whose result won’t affect the control flow, on the other hand, may be better suited to ending with a !.

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

Prefer .find_by to .find in ActiveRecord

ActiveRecord is the object-relational mapping (ORM) library used in Ruby on Rails. .find and .find_by are two of its methods that, given an ID, can query a record from the database for a given model.1 At first glance, .find and .find_by look pretty similar. In this blog post, however, we’ll discuss the readability benefits of .find_by over .find when we want to query by ID.

Suppose that we’re using our Rails console, and we want to read an instance of Foo that has an ID of 1 from the database. Using .find and .find_by looks like so:

2.5.1 :001 > Foo.find(1)
  Foo Load (0.1ms)  SELECT "foos".* FROM "foos" WHERE "foos"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 => #<Foo id: 1, created_at: "2021-03-07 23:55:46.994305000 +0000", updated_at: "2021-03-07 23:55:46.994305000 +0000"> 

2.5.1 :002 > Foo.find_by(id: 1)
  Foo Load (0.1ms)  SELECT "foos".* FROM "foos" WHERE "foos"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 => #<Foo id: 1, created_at: "2021-03-07 23:55:46.994305000 +0000", updated_at: "2021-03-07 23:55:46.994305000 +0000"> 

In .find, we pass in 1 as a positional argument, while in .find_by, we pass 1 in as a keyword argument for id.

The generated SQL is identical for .find and .find_by, and both returned the same object. However, what happens if we try to query a Foo that doesn’t exist? Suppose we don’t have a Foo with the ID of 100 in our database, and we try to query it:

2.5.1 :003 > Foo.find(100)
  Foo Load (0.1ms)  SELECT "foos".* FROM "foos" WHERE "foos"."id" = ? LIMIT ?  [["id", 100], ["LIMIT", 1]]
Traceback (most recent call last):
        1: from (irb):16
ActiveRecord::RecordNotFound (Couldn't find Foo with 'id'=100)

2.5.1 :004 > Foo.find_by(id: 100)
  Foo Load (0.1ms)  SELECT "foos".* FROM "foos" WHERE "foos"."id" = ? LIMIT ?  [["id", 100], ["LIMIT", 1]]
 => nil 

.find raises a RecordNotFound exception when a Foo with ID of 100 doesn’t exist. find_by, on the other hand, returns nil without raising an exception – if we did want to raise a RecordNotFound exception, we would call the bang method find_by!:2

2.5.1 :005 > Foo.find_by!(id: 100)
  Foo Load (0.1ms)  SELECT "foos".* FROM "foos" WHERE "foos"."id" = ? LIMIT ?  [["id", 100], ["LIMIT", 1]]
Traceback (most recent call last):
        1: from (irb):27
ActiveRecord::RecordNotFound (Couldn't find Foo)

Based on the examples above, there are two readability advantages to using .find_by, which we’ll discuss below:

First, when we use .find_by, it’s clearer that we’re querying by ID. The examples above clearly stated that 1 and 100 were IDs for our Foo records, but what if we were working with a snippet of code like so?

some_undescriptive_variable_name = 1
Foo.find(some_undescriptive_variable_name)

If we haven’t named our variables well, it may not immediately clear to a reader that we’re querying by ID, especially if our reader is unfamiliar with Ruby on Rails. Foo.find_by(id: some_undescriptive_variable_name), on the other hand, makes this more explicit via its id keyword argument.

Second, .find_by allows us to be more explicit about whether or not we want to raise an exception. From its name alone, non-obvious that .find will raise an exception if it can’t find a record. On the other hand .find_by! follows the bang method convention, s0 it’s more clear that we intend to raise an exception when we call it. And if we don’t want to raise an exception, we can simply call .find_by.

This preference for .find_by over .find only applies to the case where we’re querying on a single ID, since these two methods actually generate different SQL when querying multiple IDs. When we pass in an array of IDs to .find, it returns an array of the matching records, while .find_by would truncate the result to one record:

2.5.1 :006 > Foo.find([1,2])
  Foo Load (0.2ms)  SELECT "foos".* FROM "foos" WHERE "foos"."id" IN (?, ?)  [[nil, 1], [nil, 2]]
 => [#<Foo id: 1, created_at: "2021-03-07 23:55:46.994305000 +0000", updated_at: "2021-03-07 23:55:46.994305000 +0000">, #<Foo id: 2, created_at: "2021-03-08 00:10:12.849400000 +0000", updated_at: "2021-03-08 00:10:12.849400000 +0000">] 

2.5.1 :007 > Foo.find_by(id: [1,2])
  Foo Load (0.2ms)  SELECT "foos".* FROM "foos" WHERE "foos"."id" IN (?, ?) LIMIT ?  [[nil, 1], [nil, 2], ["LIMIT", 1]]
 => #<Foo id: 1, created_at: "2021-03-07 23:55:46.994305000 +0000", updated_at: "2021-03-07 23:55:46.994305000 +0000"> 

For cases where we want to query a single ActiveRecord record by ID, however, we should prefer .find_by to .find, as this produces more readable code.

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

1: This is a non-exhaustive set of methods. .where is another ActiveRecord method that can query by ID.

2: By convention, a bang method in Ruby is expected to modify the object it’s called on, has a state-changing side effect, or will raise an exception.

Double-check your Rails migrations’ auto-generated SQL

Suppose we want to create a join table in a Ruby on Rails application between two models, Foo and Bar. There’s plenty of material available online describing join tables in detail (the Wikipedia page on associative entities is pretty good), but to begin, let’s go over a brief recap and an example:

Join tables implement many-to-many relationships between entities in a relational database. An entry in a join table will hold the primary keys of the entities it associates. For example, the linked Wikipedia article above (at time of writing) shows that we can use a Registration entity to implement a join table, and this join table holds a relationship between a Student and a Course. This Registration entity contains the primary keys of a Student and a Course.

Having seen an example, let’s a join table between a Foo and a Bar. A join table between Foo and Bar will hold the primary key of a Foo and the primary key of a Bar (ie. the IDs of these two models). Searching through the Ruby on Rails documentation, we may find that create_join_table is a method that is available inside of a database migration. To use this method, we’d write a migration like so:

If we run the migration, we do get a working join table in our database. However, if we look at the underlying SQL this migration generates, it might not quite match what we expect to get. In this blog post, I’m using SQLite (the default option provided by Rails), but we would see similar results when using a different database like PostgreSQL. The generated SQL is as follows:

There are two interesting things to note about the above SQL.

Firstly, our join table doesn’t actually use a foreign key constraint. This means that our SQL database will let us insert any integer into the values of foo_id and bar_id, even if such an ID doesn’t exist in our tables for Foo and Bar.

Secondly, the generated SQL has NOT NULL constraints, meaning our join table entries must always have an integer value set for foo_id and bar_id. This makes sense in some, but not all, use cases.

In the Student and Course example from the beginning of the article, NOT NULL fits the use case – it doesn’t make sense to have a Registration with a Course but no Student, or a Registration with a Student but no Course.

However, let’s look at a slightly different use case – suppose we’re trying to keep track of floating software licensing, where we limit the number of users who have permission to use some software at a given time. In this case, we may have a join table named something like LicenseAssignment between a User and a SoftwareApplication. In this case, however, it makes sense that User could be NULL – it would just mean that not all of our licenses are currently being used. In this case, we wouldn’t want a NOT NULL constraint.

As of writing, most hits on the first page of Google for “rails join table tutorial” use database migrations that implement pretty different results from what we see above. I’ve excluded database indexes for brevity, but for the most part, they look like so:

This migration generates the following SQL:

If we use the above SQL, we can rely on our database to enforce some level of data integrity via its foreign key constraints, and we support the use case where a Foo ID or a Bar ID can be NULL.

So, what’s the lesson behind all of this? Double-check your Rails migrations’ auto-generated SQL, and make sure it fits your assumptions and use case. Superficially, the two Rails migrations outlined in the article look similar, but their underlying data models are actually very different.

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

Don’t iterate to convert built-in Ruby data types

Both the Ruby language and the Ruby on Rails framework provide a lot of convenience methods compared to other technology stacks, improving ease of use and developer productivity. However, it’s difficult when starting out with Ruby to know what ease-of-use functions are available to you as a programmer.

Here’s a heuristic to help with that: if you’re writing a Ruby program, and you find yourself writing code that uses iteration to convert one built-in data type to another, there’s probably a language feature that could simplify your code. Let’s go through a few examples of this that I’ve encountered since I started coding in Ruby.

Generating an array of numbers between 1 and N

Suppose you want to generate an ordered array of integers between 1 and N. We might be tempted to use the .times method on our integer N, convert that enumerator to an array, then map each number to be increased by 1 (since .times provides us numbers from 0 to N – 1). For example, if N is 5, we can use the following code:

5.times.to_a.map{|n| n + 1}
=> [1, 2, 3, 4, 5] 

This works, but Ruby provides simpler ways for us to achieve the same goal using upto or a Range:

1.upto(5).to_a
 => [1, 2, 3, 4, 5] 

(1..5).to_a
 => [1, 2, 3, 4, 5] 

Convert the values of a hash into an array

Suppose you want an array containing the values of a Hash, and don’t need to know what the keys are. We might be tempted to call .map on the Hash, and then return the value in that block:

{foo: 1, bar: 2, car: 3}.map{ |_key, value| value }
 => [1, 2, 3] 

With far fewer keystrokes, .values lets us do the same thing:

{foo: 1, bar: 2, car: 3}.values
 => [1, 2, 3] 

(Of course, if we’re interested in the keys and not the values, Ruby provides a .keys method as well.)

Combining two hashes

Suppose have two Hash variables, h0 and h1, and want to create a new Hash containing the keys and values in both of them. In the case of duplicate keys between h0 and h1, we’ll take the value from h1 (this is arbitrary; using the value from h0 would be valid too). We could write a method to iterate over two hashes like so:

However, the far simpler approach is to use the .merge method provided by Ruby:

{foo: 1, bar: 2}.merge({bar: 3, car: 4})
 => {:foo=>1, :bar=>3, :car=>4} 

Conclusion

Ruby (and Ruby on Rails) provides us with a lot more convenience methods than other popular languages like Go and JavaScript. Learning all of them requires reading the language documentation, along with simply practicing with the language over time. However, these convenience methods aren’t created at random; there are patterns in them that we can identify to become more effective Ruby developers.

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

Use RSpec to improve performance via precomputation

Suppose that we have a Ruby class called SomeClass, and SomeClass has an array class constant called SOME_ARRAY. SomeClass also has a method called same_elements? that checks if an input array has the same elements as SOME_ARRAY without regard to ordering. We may be tempted to write code that looks like this:

This code works, but it’s an inefficient solution. We sort SOME_ARRAY every time same_elements? is called, even though it’s a constant that we defined ourselves!

We could define SOME_ARRAY as ['foo', 'bar', 'car'].sort, and that gets us most of the way to improved performance. We only expect Rails to load our class definition for SomeClass once when we start our application, so even if we’re creating many instances of SomeClass (eg. once per request to a REST endpoint), we’re still only calling .sort on SOME_ARRAY once.

However, if we want to be even more efficient with our compute power at runtime, we can simply define SOME_ARRAY to be sorted when we write our code, which lets us avoid calling .sort when we load the class definition.

This revised code has even fewer calls to .sort than when we defined SOME_ARRAY as as ['foo', 'bar', 'car'].sort. However, this introduces a new problem into our code: if we ever rewrite or modify SOME_ARRAY in the future, we need to ensure that that constant remains sorted.

For example, a future commit could change SOME_ARRAY to ['new element', 'bar', 'car', 'foo'], which would cause a bug in our same_elements? method. Hopefully our code review process and tests on same_elements? would stop us from introducing that bug.

However, through RSpec, we can actually enforce that SOME_ARRAY remains sorted. We introduce a test like so into our SomeClass test suite:

This ensures that SOME_ARRAY remains ordered when we introduce new elements to it. By precomputing the sorted order of SOME_ARRAY in our testing and continuous integration environments, we’re able to do fewer .sort computations at runtime in our application.

Note that we should only use this method when there’s no impact to SOME_ARRAY‘s readability. If SOME_ARRAY is defined as ['foo', 'bar', 'car'] (ie. random elements where we don’t care about the ordering), making its definition alphabetical in the source code makes sense. Using this method also makes sense when SOME_ARRAY is something we already expect to be alphabetized (eg. ['alpha', 'bravo', 'charlie']). However, if we expect some other sort of non-alphabetized ordering of SOME_ARRAY, then making our code less readable in order to save compute power would be the wrong tradeoff. For example, redefining ['one', 'two, 'three'] to the sorted ['one', three', 'two'] makes our code less readable, which isn’t worth the slight performance gains.

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

This is how percentages work in Flipper

Flipper is a Ruby library that implements feature flags. We use it to turn features on and off via configuration data that our application reads at runtime, meaning we can enable and disable features without modifying our actual Ruby code. Flipper has the concept of a Gate to determine whether or not a feature is enabled – for example, the simplest Gate is a boolean that globally determines whether a feature is turned on/off for the whole application. However, Flipper also lets us turn a feature on/off for a certain percentage of actors. Within this blog post, I’ll treat a “user” and “actor” as being interchangeable concepts.

If we want to gradually roll out a new feature to our application’s users, we may start by only enabling a Flipper Gate for 1% of users – then gradually increase that percentage to 2%, then 3%, and so on until we reach 100%.

This raises multiple questions, though:

  • Which 1% of users do we enable this feature for? Are these users selected randomly or deterministically?
  • When we increase that percentage from 1% to 2%, are the users from the 1% cohort a subset of the 2% cohort? We’d hope that Flipper is designed this way, since we don’t want a user to see a new feature in our application toggled on/off during a gradual rollout.
  • Are we introducing additional state to a user when a feature is enabled for them?

Let’s find out by reading the percentage_of_actors.rb file in the Flipper source code. open? looks like a promising method to read in that file:

We can avoid diving deeply into how Types::Actor.wrappable? works, since it’s an internal implementation detail of Flipper that is outside of the scope of this blog post. We can assume that actor.value returns the unique flipper_id that we see examples of in Flipper’s documentation. For example, if we’re dealing with a feature flag called foo_enabled for a user with an ID of 123, actor.value should be "User;#123", meaning the local variable id in the above snippet is "foo_enabledUser;123".

We then calculate the CRC32 of that id. CRC32 is an algorithm designed to check data integrity, but it works pretty well as a hash function if we’re not using it in a security-related context. This means that the output of CRC32 is pseudorandom and fairly uniformly distributed, even though it’s calculated deterministically. So, it should look like the users who have a feature enabled were selected at random, even though their eligibility is deterministic.

The inequality below (taken from the code snippet) will tell us whether or not cohorts for smaller percentages will be subsets of cohorts for larger percentages:

Zlib.crc32(id) % (100 * scaling_factor) < percentage * scaling_factor

percentage is the variable that we defined to be strictly increasing from 1% to 100%. scaling_factor is hardcoded to 1000, meaning percentage * scaling_factor is strictly increasing as well. Ruby’s implementation of CRC32 also interprets its output as an unsigned (ie. positive) integer. This means that all of the IDs for whom the inequality held true with a given percentage will also hold true for a larger percentage.

Let’s use an example: if percentage is 1, then any users where Zlib.crc32(id) % (100 * scaling_factor) is less than 1000 will have the foo_enabled feature turned on for them. Roughly 99% of our users will have a value of over 1000 in this check, so this feature will be turned off for 99% of users. However, for user 24 (ie. with id value set to "foo_enabledUser;24“, the CRC of their ID is 278800337, and 278800337 % (100*scaling_factor) returns 337.

The key point here is that this will always be 337 for user 24, regardless of what our percentage is set to! 337 is less than 1000, and if we strictly increase percentage to values such as 2 and 3, this inequality also holds true for their corresponding values for percentage * scaling_factor (such as 2000 and 3000).

To conclude:

  • We can expect our users to be sampled more-or-less at random by Flipper, even though their eligibility for a feature is calculated deterministically via CRC32. This is because CRC32 can be used as a hash function in this context.
  • If we’re strictly increasing our percentage of users enabled, then all users from the cohort of smaller percentages will also be included in the cohorts of larger percentages too.
  • Calculating a user’s eligibility for a feature via CRC32 doesn’t add additional state to the user.

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

Gem::Version != SemVer

Using dependencies with semantic versioning (also known as SemVer) is a key piece of modern software development, and if we’re writing Ruby code that checks the version of a dependency, we often use Gem::Version for our operations.

The initializer for Gem::Version checks if we’ve passed in a valid version number (such as 1.9.10), and raises an ArgumentError if it’s invalid:

$ irb
3.0.0 :001 > Gem::Version.new('1.9.10')
 => #<Gem::Version "1.9.10"> 
3.0.0 :002 > Gem::Version.new('foo bar car')
ArgumentError (Malformed version number string foo bar car)

If we wanted to test this behaviour in RSpec, it would look something like so:

Most dependencies use release numbers with the MAJOR.MINOR.PATCH pattern (eg. 1.9.10), but the SemVer allows more complex identifiers than that. For a list of interesting edge cases that illustrate what is and isn’t allowed, the SemVer specification links to a Regex101 page containing a list of test strings.

Let’s pass those into Gem::Version via RSpec and see what happens! In the code below, we pass in each valid test string into the initializer for a new Gem::Version, and check that it successfully creates a Gem::Version (ie. doesn’t raise an error).

We actually receive an ArgumentError if we pass in a version number with metadata (ie. everything after the + sign), so we strip that out before passing that in. It would be ideal if Gem::Version did that metadata parsing for us, but ignoring that edge case, all of our test cases pass:

What happens when we look at the invalid test cases? Let’s look at the RSpec below:

We expect to receive an ArgumentError if we’re passed in an invalid version number, which we check for in our expect statements. We also skip some of our test cases with this snippet:

.reject { |version_string| version_string.include?('+') }

We skip these cases because we already know that Gem::Version doesn’t handle metadata correctly. Let’s run the above RSpec tests:

We see that a lot of version identifiers that are invalid under the SemVer specification don’t actually raise errors in these tests, and can be used to instantiate a Gem::Version. For example, Gem::Version.new('1.2.3.DEV') creates a new object and doesn’t raise an error, even though that’s not a valid version number under SemVer.

As it turns out, Gem::Version (ie. the versioning used by RubyGems) is much more permissive than the SemVer specification. This means that if we’re trying to adhere to the strictest definition of a SemVer version number, we can’t actually use Gem::Version to determine if a version string is valid due to this permissiveness. We can only use Gem::Version to tell us if a version string is invalid (barring the aforementioned exception around metadata and the + sign).

My favourite behaviour of Gem::Version isn’t actually covered in the negative test cases of the SemVer spec. Gem::Version lets us be arbitrarily granular with the number of dots in our version number. This isn’t valid in the SemVer specification either, but Gem::Version will happily parse it and do version comparisons with it:

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