Skip to content

Defining Settings

Jeff Felchner edited this page Dec 30, 2020 · 3 revisions

Just Getting Started?

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.

Venturing On...

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.

Inline Namespaces

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"

A Gotcha

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.

Using YAML's Superpowers

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.

But Still...

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:

Clone this wiki locally