-
-
Notifications
You must be signed in to change notification settings - Fork 25
Environment Variable Coercions
There are many reasons why environment variables are a poor choice for storing your application settings, but one of the most directly developer-facing issues is the fact that environment variables can only be strings.
This means that using environment variables requires gymnastics like this:
if ENV['MY_FEATURE_ENABLED'] == 'true'
# Do stuff with my feature
end
and imagine if you had to use this variable in a bunch of different places. You end up with a bunch of duplicated and non-ideomatic logic.
"BUT WAIT!" I hear you say, all you have to do is to say:
unless ENV['MY_FEATURE_ENABLED'].nil?
# Do stuff with my feature
end
or
if ENV['MY_FEATURE_ENABLED']
# Do stuff with my feature
end
That seems much better right? Not so fast. This brings me to what I like to call the "nil / false / Unknown Conundrum" (yeah it's a mouthful). The problem with using an unset environment variable to mean "false" is:
- It could be intented to be
nil
and notfalse
(which are two different things) - It could truly be intented to be
false
- It could have been set to something like the string
'false'
, in which case the above code would evaluate it totrue
. - It could have been forgotten to have been set altogether (in other words, it
should be the string
'true'
, but we forgot to set it)
Let's look at this with a Chamber example.
For the purposes of this demostration, we're going to assume we have the following setting.
# settings.yml
my_feature:
enabled: true
And we're going to assume that we have the following environment variable to override that setting:
export MY_FEATURE_ENABLED="false"
if Chamber.dig!('my_feature', 'enabled')
# Do stuff with my feature
end
Note: We can't leave
MY_FEATURE_ENABLED
unset in order to denotefalse
-ness because it could just as easily have not been set because we didn't want to override it in the first place.
Let's look step-by-step at what happens when Chamber loads its settings and processes that code:
- Chamber initially reads
my_feature.enabled
as aBoolean
from the YAML file - Then it sees the environment variable and as we stated in the previous guide it understands that we actually want that value instead of what's in the YAML
- It sets
my_feature.enabled
to theString
of'false'
- It looks at
Chamber.dig!('my_feature', 'enabled')
and because in Ruby, allString
s are "truthy", it evaluates it totrue
instead of the correct value offalse
So, what are we to do?
Chamber takes a different approach to environment variables. It still reads
them in and overrides your YAML settings if they match (this is similar to what
dotenv
and figaro
both do), but then it goes a step further... it converts
the String
into what the user intended it to be.
I have a confession to make. The Chamber example from the previous section? It works just fine. Let's look at the steps we missed from the previous example (new steps in bold).
- Chamber initially reads
my_feature.enabled
as aBoolean
from the YAML file - Then it sees the environment variable and as we stated in the previous guide it understands that we actually want that value instead of what's in the YAML
- It sets
my_feature.enabled
to theString
of'false'
- It notices that the original value from the YAML file was a
Boolean
and converts theString
'false'
to theBoolean
false
It looks atChamber.dig!('my_feature', 'enabled')
and because in Ruby, allString
s are "truthy", it evaluates it totrue
instead of the correct value offalse
- It looks at
Chamber.dig!('my_feature', 'enabled')
and sees the value offalse
and correctly evaluates the statement.
Chamber will coerce most of the types you care about from environment variable strings. These include:
Type | Details |
---|---|
Integer |
|
Float |
|
Array |
As long as it's a valid YAML array string |
Time |
As long as it's in full ISO8601 format |
Boolean |
Values of yes , no , on , off , true , false , t , f , 1 or 0
|
NilClass |
Values of ___nil___ or ___null___ will be treated as literal nil
|
If any of the conversions error out, or return a type that is not what was expected, an error will be thrown.
Copyright ©2023
- Release News
- Gem Comparison
- 12-Factor App Rebuttal
- Environment Variable Problems
- Installation
- Basics
- Defining Settings
- Accessing Settings
- Verifying Settings
- Namespaces
- Environment Variables
- Integrations
- Encryption
- Advanced Usage
- Command Line Reference