Skip to content

Upgrading To Chamber 3.0

Jeff Felchner edited this page Mar 6, 2023 · 6 revisions

Making breaking changes in a library is never easy. I know there are a ton of users out there that love Chamber and love it exactly the way it is. We have not had an intentional breaking change since Chamber was released as a 2.0 over nine years ago. Back then the number one hit song was "Wrecking Ball" by Miley Cyrus.

So I hope you all will understand that the breaking changes are done with good reason and bear with me while I try to get you all through this with as little pain as possible.

TL;DR

dig! is available as of chamber 2.14.1 (which was also the version that enabled deprecation warnings). If you get rid of all of the warnings (preferably by converting to using dig!, you will be able to upgrade to Chamber 3.0 with no problems.

Removal Of Hashie As A Dependency

Hashie has been both the reason for the extra power that has allowed Chamber users to access their settings however they'd like, and also the bane of my existance in terms of maintenance.

First, you should go read Richard Schneeman's great blog post "Hashie Considered Harmful" for some in-depth rationales on why Hashie is problematic regardless of what it is being used for.

Additionally Hashie's own creator said that Hashie was never intended for production use. It was to be used primarily for prototyping. (Note: I know this is true however I failed to find the quote. If someone can provide it to me, I'd be grateful.)

Even moreso than all of the above is the amount of indeterminism that arose in the code from having a thing that was "kind-of-a-Hash-but-not-really". There were corner cases and branches that only existed because the object that was being used was a Hashie::Mash instead of a proper Hash.

Syntax Changes

The removal of hashie required additional constraints on how users could access their settings. This ended up being a significant reduction in internal logic in its own right. There were three places in the code where we were needing to do method_missing to catch attempts to access settings rather than call a legitimate method on the object. In almost all cases, reducing metaprogramming is a Good Thing² and this was no exception.

Removal Of Object Notation Access

In Chamber 2.x you could access your settings like so:

Chamber.env.smtp.username # => "my_username_example"

In Chamber 3.x you would access that same setting like so:

Chamber.dig!('smtp', 'username') # => "my_username_example"

or you can use symbols if you would prefer:

Chamber.dig!(:smtp, :username) # => "my_username_example"

Removal Of Bracket Indifferent Access

In Chamber 2.x you could either access your settings like so:

Chamber.env['smtp']['username'] # => "my_username_example"

or like so:

Chamber.env[:smtp][:username] # => "my_username_example"

In Chamber 3.x, you must use strings to access settings with bracket syntax:

Note: You can now call [] directly on the Chamber constant (ie Chamber['smtp']['username'])

If you really want to use symbols, you should use the dig syntax:

Chamber.dig!(:smtp, :username) # => "my_username_example"

Removal Of Predicate Accessors

In Chamber 2.x, you could utilize predicate methods.

In Chamber 3.x, the user experience defaults the user to one where you simply cannot access configuration values that do not exist. Therefore these predicate matchers are of very limited use. They were also a feature that few Chamber users used. As such, they have been removed from Chamber 3.x.

If you absolutely require the knowledge of whether a key exists, you should use Ruby's built-in hash methods like has_key?.

Removal Of Cloud Settings Integrations

Ever since Chamber 1.0 it has had the ability to be able to push or pull settings from cloud environments such as Heroku and CircleCI. This worked somewhat well at first, but became a maintenance burden.

Why Did It Get Removed?

  1. Testing Difficulties

Automated tests for the cloud providers frequently failed due to changes in the APIs or issues with rotating test credentials.

  1. Lack of Use

This integration was rarely used ever since Chamber added support for better alternatives (I will describe in a second) for this process.

  1. Code Complexity

There was a surprisingly large amount of code that existed to support these integrations.

  1. Maintenance Burden

For a feature that was rarely used, it had quite a lot of people submitting issues about it. Additionally, if I wanted to support another service, I would have had to write custom code to integrate with that services API. Considering my limited time, this wasn't going to work.

What Do We Do Instead?

Fortunately Chamber has a much better alternative to this. If you send your private key to your cloud service of choice, when Chamber loads, it will automatically decrypt the settings in your YAML files.

What does this mean? A single push when you start your project and, unless you rotate your Chamber key, you never have to sync your settings ever again (as long as you push your YAML files to the service).

I will do my best to keep that page up-to-date with new commands for new services. Please submit a pull request if one is missing.

Requiring Ruby 2.7+

I don't technically consider removing older Ruby versions as a breaking change however I'm adding it here for completeness.

Ruby 1.9.3 is, at this point over eleven years old. If some of you are on that version of Ruby, then 2.13.x will likely serve you well for the foreseeable future. However we've got to move on at some point and the benefits this brought to the codebase made it worth the change to me.

Zero Monkeypatching (Native Methods)

Although I always touted Chamber as a "zero monkeypatching" library, that wasn't technically true. If the version of Ruby you were using didn't have transform_keys defined on the Hash class, it would inject that method onto it. A very minor patch to be sure, but still, I loathe monkeypatching and think it's bad in all its forms. Because Ruby 2.5.5 introduced a native transform_keys method, I can remove the monkeypatch.

Zero Monkeypatching (Refinements)

Ruby 2.0 introduced refinements and even though they have their own problems, they are a good solution to allow Rubyists to inject code into core classes without it affecting the rest of the world.

Since I was able to upgrade to Ruby 2.0+, I was able to also inject some methods that mimiced some of the things that Hashie gave me, without needing to use the gem.

Better Syntax

While not a huge factor, Ruby 2.1+ allowed me to use some improved syntax in the code which simplified a lot of things. The most notable of these was keyword arguments. Previously I was passing around options hashes which is not ideal.

Bracket Access Now Fails On Non-Existent Keys

In Chamber 2.x, with this data:

development:
  smtp_enabled: false

test:
  smtp_enabled: false

production:
  smtp_enabled: true

You could do:

Chamber.env['my_nonexistent_key'] # => nil

In Chamber 3.x, this will fail with a KeyError. Everything about Chamber 3.x's changes steers the user into helping make sure that their configuration is as they expect it. One could just as easily have put somthing like:

Chamber.env['smpt_enabled'] # => nil

Note that the above is misspelled. Since this is a boolean, it works fine in development and test since nil and false behave almost identically, however in production you would have a bug. You would be receiving nil when you were expecting true.

In Chamber 3.x, that same line would throw a KeyError. Alerting you to the fact that the thing you expected to be there, was not.

This also pushes you to make sure that the various namespace combinations you have all result in a configuration data structure that is equivalent. This is also a Good Thing.

All Settings Keys Are Now Stored As Strings

This will have almost zero impact for Chamber uses as I'm sure most of you are not doing this, but as it is a breaking change, I'm listing it here:

In Chamber 2.x, because Hashie was allowing for indifferent access, you could technically define keys as symbols in YAML like so:

:my_symbol_key: "my_value"

and Chamber would read it in and store :my_symbol_key as the key in the hash. Now that we're using plain Hash objects, we want to be deterministic about how keys are stored in the data structure so that, conversely, we know how we should access the data in the structure when the user requests it.

In Chamber 3.x, the same structure above would have 'my_symbol_key' as the key for the value 'my_value'.

If you attempt to load settings with keys that are anything other than a String, you will receive a Chamber::Errors::NonConformingKey error.

Limiting Complex Classes

Ruby 3.1 now defaults the Psych YAML parser to "safe" mode. By default, safe mode does not allow aliases or any objects other than:

  • TrueClass
  • FalseClass
  • NilClass
  • Integer
  • Float
  • String
  • Array
  • Hash

Because Chamber is not supposed to be parsing user-provided YAML (read: YOU SHOULD BE VETTING EVERY YAML FILE CHANGE THAT IS MADE TO YOUR CHAMBER SETTINGS) I am relaxing those constraints a little bit, but I do think it is prudent not to allow any and all classes to be loaded.

As of Chamber 3.0 only the above classes will be able to be loaded in your settings files with the addition of:

  • Regexp
  • Date
  • Time

YAML aliases are also still supported.

What About Symbols?

Symbols are a whole can of worms in Ruby and because of that and because we're removing Symbol access for the keys in Chamber 3.0 anyway, I've kept them restricted on load for values as well.

"Zero" Dependencies

While the chamber command line tool still relies on thor, thor is an incredibly stable gem and has had minimal changes over the years.

The core library code does not depend on any other libraries for it to do what it does and is therefore super resiliant and much more able to be installed securely with the Rubygems HighSecurity option enabled.

Conclusion

I wish that backwards incompatible changes never needed to happen, but that would mean we would all have to be perfect from day one. Unfortunately that is not me.

I do hope that I have made the upgrade path for you all as easy as possible (hopefully a few find and replaces at most).

I'm looking forward to brining you all more features in the future including Github Actions support, and automatic re-keying if you have to change your Chamber private key for some reason (or want to introduce additional namespaces into an already existing project).

❤️

Clone this wiki locally