-
-
Notifications
You must be signed in to change notification settings - Fork 25
Defining Settings
All you really have to do is put a single level of settings in your YAML files like this:
# settings.yml
redis_url: 'redis://localhost:6379'
redis_username: 'h'
redis_password: 'my_redis_pass'
smtp_server: 'smtp.example.com'
smtp_port: '587'
smtp_tls: true
smtp_authentication: 'login'
smtp_username: 'my_user'
smtp_password: 'my_pass'
smtp_headers:
X-MYAPP-NAME: 'My Application Name'
X-MYAPP-STUFF: 'Other Stuff'
Then you can access these via Chamber.dig!
as discussed in the next section
Accessing Flat Settings.
At this point you can follow the instructions in File-Based Namespaces with your different values per environment. While not very powerful, this is the simplest to understand.
The next step is to start nesting your settings. You may have noticed that the
settings above all start with smtp
or redis
. In YAML you can make that
a bit more readable.
# settings.yml
redis:
url: 'redis://localhost:6379'
username: 'h'
password: 'my_redis_pass'
smtp:
server: 'smtp.example.com'
port: '587'
tls: true
authentication: 'login'
username: 'my_user'
password: 'my_pass'
headers:
X-MYAPP-NAME: 'My Application Name'
X-MYAPP-STUFF: 'Other Stuff'
Now you've not only made it more readable, but you've removed the duplication.
Then you can access these via Chamber.dig!
as discussed in the next section
Accessing Nested Settings.
In our previous example, we had one username and one password for our SMTP
server. This isn't really practical. Generally an SMTP server such as SendGrid
will give you the ability to create a "dev" user. This user is limited in what
it can do and who it can send to. In test
we don't actually even need
a username and password because emails aren't even delivered in that
environment.
Chamber makes this easy to define:
# settings.yml
redis:
url: 'redis://localhost:6379'
username: 'h'
password: 'my_redis_pass'
development:
smtp:
server: 'smtp.example.com'
port: '587'
tls: true
authentication: 'login'
username: 'my_dev_user'
password: 'my_dev_pass'
headers:
X-MYAPP-NAME: 'My Application Name'
X-MYAPP-STUFF: 'Other Stuff'
test:
smtp:
server: null
port: null
tls: null
authentication: null
username: null
password: null
headers: {}
production:
smtp:
server: 'smtp.example.com'
port: '587'
tls: true
authentication: 'login'
username: 'my_user'
password: 'my_pass'
headers:
X-MYAPP-NAME: 'My Application Name'
X-MYAPP-STUFF: 'Other Stuff'
Now if you load your app with the development
namespace, you'd get:
Chamber.dig!('smtp', 'username')
# => "my_dev_user"
And if you load your app with the production
namespace, you'd get:
Chamber.dig!('smtp', 'username')
# => "my_user"
However, there's one slight issue. Try executing:
Chamber.dig!('redis', 'url')
You'll get an error. This is because, even though redis
is in the YAML file,
as soon as you add a namespace to a file, the rest of the settings that are not
nested under that namespace are ignored.
Why?
Well, we know that in development
we want to ignore test
and production
settings, it would be silly to load those. And because we're humans, we can
understand that redis
is not an environment of our app, nor another namespace
such as a host name, and therefore should be loaded. But Chamber is built so
that namespaces are flexible and we can't make the assumption that somewhere,
somehow, someone isn't going to want an environment or other namespace by the
name of redis
.
Because of this, the redis
key now also needs to be defined under the
namespaces.
# settings.yml
development:
redis:
url: 'redis://localhost:6379'
username: 'h'
password: 'my_redis_pass'
smtp:
server: 'smtp.example.com'
port: '587'
tls: true
authentication: 'login'
username: 'my_dev_user'
password: 'my_dev_pass'
headers:
X-MYAPP-NAME: 'My Application Name'
X-MYAPP-STUFF: 'Other Stuff'
test:
redis:
url: 'redis://localhost:6379'
username: 'h'
password: 'my_redis_pass'
smtp:
server: null
port: null
tls: null
authentication: null
username: null
password: null
headers: {}
production:
redis:
url: 'redis://localhost:6379'
username: 'h'
password: 'my_redis_pass'
smtp:
server: 'smtp.example.com'
port: '587'
tls: true
authentication: 'login'
username: 'my_user'
password: 'my_pass'
headers:
X-MYAPP-NAME: 'My Application Name'
X-MYAPP-STUFF: 'Other Stuff'
And now if you try to get the value of the Redis URL, you'll get the correct answer.
But as you can tell, this is starting to get a bit messy. We have a ton of duplication (and for the purposes of this example, we're going to say that we want all the Redis settings to be the same for every environment).
Fortunately for us, YAML has a lot of ways of dealing with that. The main one
we'll use are what's called anchors. Anchors are defined by an &
followed by
whatever name you'd like to give it. When you want to use the value of the
anchor, just use an *
followed by the same name. You can use it as many times
as you'd like.
# settings.yml
redis: &redis_defaults
url: 'redis://localhost:6379'
username: 'h'
password: 'my_redis_pass'
smtp: &smtp_defaults
server: 'smtp.example.com'
port: '587'
tls: true
authentication: 'login'
headers:
X-MYAPP-NAME: 'My Application Name'
X-MYAPP-STUFF: 'Other Stuff'
development:
redis:
<<: *redis_defaults
smtp:
<<: *smtp_defaults
username: 'my_dev_user'
password: 'my_dev_pass'
test:
redis:
<<: *redis_defaults
smtp:
<<: *smtp_defaults
username: null
password: null
production:
redis:
<<: *redis_defaults
smtp:
<<: *smtp_defaults
username: 'my_user'
password: 'my_pass'
This now looks much better. We've defined the common redis
and smtp
settings in one place and then referenced them in each namespace. In fact, the
redis
key looks almost idential to the way it did when we moved it in the
first place!
A YAML anchor basically says "I represent anything after me." For more information on what you can do with YAML, take a look at Learn YAML in Y Minutes.
And remember, because Chamber ignores anything else once it finds a namespace in
a file, those initial redis
and smtp
keys are simply passed over. The only
reason they're ever registered is because they were referenced inside the
namespace value.
This isn't exactly as organized as I'd like to have made it. The readability is
suffering a bit. I'd like to just be able to define the redis
key once
because I want it to be the same in all namespaces. It's also starting to be
difficult to view all the settings together.
We can solve this by moving from Chamber's single-file layout, to its
directory layout. Depending on the project type you're in (Rubygem, Rails,
Sinatra, etc) Chamber looks for settings files in a given directory. For our
purposes, we're going to call it <basename>/settings
.
Now what we can do is to create two files. One for redis
and one for
smtp
. Each of these files will contain only the settings that are related.
# <basepath>/settings/redis.yml
redis: &redis_defaults
url: 'redis://localhost:6379'
username: 'h'
password: 'my_redis_pass'
development:
redis:
<<: *redis_defaults
test:
redis:
<<: *redis_defaults
production:
redis:
<<: *redis_defaults
# <basepath>/settings/smtp.yml
smtp: &smtp_defaults
server: 'smtp.example.com'
port: '587'
tls: true
authentication: 'login'
headers:
X-MYAPP-NAME: 'My Application Name'
X-MYAPP-STUFF: 'Other Stuff'
development:
smtp:
<<: *smtp_defaults
username: 'my_dev_user'
password: 'my_dev_pass'
test:
smtp:
<<: *smtp_defaults
username: null
password: null
production:
smtp:
<<: *smtp_defaults
username: 'my_user'
password: 'my_pass'
Quite a bit better. Now for one last cleanup step. Namespace detection is
per-file and not "all or nothing". So while smtp.yml
needs namespaces
because it has different settings per namespace, redis.yml
has the same
settings. Therefore it does not need namespaces and can be rewritten:
# <basepath>/settings/redis.yml
redis:
url: 'redis://localhost:6379'
username: 'h'
password: 'my_redis_pass'
...MUCH better.
Next Step: Accessing Settings
Learn More:
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