I recently ran into a small but frustrating production bug caused by a common Ruby mistake.
I had a feature controlled by an environment variable:
DISABLE_THING=false
The application restarted successfully, but the feature was still disabled.
The reason? In Ruby, environment variables are always strings.
ENV["DISABLE_THING"]
# => "false"
And in Ruby, a non-empty string is truthy.
if "false"
puts "This runs!"
end
# This runs!
So even though the value looks like false, Ruby sees it as a string containing five characters, not the boolean value false.
Ruby only has two falsey values
Ruby has a very simple truthiness rule:
falseis falseynilis falsey- Everything else is truthy
That means:
!!"false"
# => true
!!"0"
# => true
!!""
# => true
The empty string is also truthy in Ruby.
This is different from some other programming languages where empty strings or values like "false" may be treated as false.
The common ENV mistake
A common pattern looks like this:
if ENV["ENABLE_FEATURE"]
enable_feature
end
This works when the variable is missing:
ENV["ENABLE_FEATURE"]
# => nil
But it breaks when someone sets:
ENABLE_FEATURE=false
because Ruby receives:
ENV["ENABLE_FEATURE"]
# => "false"
and:
if "false"
enable_feature
end
The feature gets enabled.
Convert ENV values into booleans
Before using environment variables as booleans, always cast them.
Option 1: ActiveSupport Boolean casting
If you are using Rails, ActiveSupport already provides a boolean type caster:
ActiveModel::Type::Boolean.new.cast(ENV["ENABLE_FEATURE"])
Examples:
ActiveModel::Type::Boolean.new.cast("true")
# => true
ActiveModel::Type::Boolean.new.cast("false")
# => false
ActiveModel::Type::Boolean.new.cast(nil)
# => nil
This is usually my preferred approach in Rails applications.
Option 2: Create a small helper
For smaller applications, a simple helper works well:
def env_truthy?(key)
ENV[key].to_s.match?(/\A(true|t|1|yes|y|on)\z/i)
end
Usage:
if env_truthy?("ENABLE_FEATURE")
enable_feature
end
Now:
ENABLE_FEATURE=true
enables the feature, while:
ENABLE_FEATURE=false
does not.
Prefer positive feature flags
Another small improvement is to avoid negative flags.
Instead of:
DISABLE_NEW_CHECKOUT=false
prefer:
ENABLE_NEW_CHECKOUT=true
Negative flags often create confusing conditions:
unless ENV["DISABLE_NEW_CHECKOUT"]
enable_checkout
end
Positive flags are easier to reason about:
if env_truthy?("ENABLE_NEW_CHECKOUT")
enable_checkout
end
A quick reference table
| ENV value | Ruby value | Boolean result |
|---|---|---|
"true" |
String | truthy |
"false" |
String | truthy |
"1" |
String | truthy |
"0" |
String | truthy |
"" |
String | truthy |
nil |
nil | falsey |
Takeaway
Environment variables are strings. Always.
Never assume this:
ENV["FLAG"] == false
or:
if ENV["FLAG"]
means what you think it means.
Convert the value first:
ActiveModel::Type::Boolean.new.cast(ENV["FLAG"])
or use a dedicated helper.
A five-second boolean conversion can save hours of debugging in production.