Build a minimal feature flags manager in under an hour
When you deploy your code continuously, feature flags (or feature toggles) are a neat way to hide in-progress features from your users. In the past, I’ve written about my process for releasing large features. But after using feature flags in the last six months, I’ve come to like them better than my former process.
Still, hiding behavior behind
if ENV['MY-FEATURE'].present?-type conditionals bothers me.
First, I don’t think the syntax is very legible. Second, I know it’s hard to maintain a naming convention across a team. You’ll soon end up with endless variations:
ENV['TOGGLE-FEATURE'], etc… These discrepancies make it hard to keep track of all your feature flags.
Sure, you could use a gem or a third-party service. But for those who just need an on-and-off system, here’s a minimal feature flag manager that’ll take you less than an hour to build. It’s a plain Ruby object but its configuration leans on Ruby on Rails. I’m sure a Ruby-only implementation wouldn’t be too hard to whip up, though.
if ENV['EDITORIAL_FEED_FEATURE'].present?, I’d like to see this syntax:
While looking at it, a few ideas come to mind:
Featuresclass will need access to all available feature flags and check if the flag passed as argument is present and enabled.
- All my feature flags will need to follow the same naming strategy.
- Since I’ll probably still use environment variables at some level of my stack, I’ll need somewhere to centralize all those variables related to feature flags.
- My method
enabled?should return either
A basic implementation of my
Features class could be this:
A shameless green version
What behavior should we expect from our
If you pass a feature’s name to
Features.enabled?, the method should:
- Check if the feature’s name is present in a list of feature flag.
- Check if the feature flag is either
- Return a boolean.
Let’s draft our first test.
A minimal implementation could be:
This code is problematic on several levels. Our test is green, though. If you were to put this code into the world right now, it’ll work. I also like that all feature flags are neatly organized in one place.
Some of the problems we can guess:
- Every time we need to switch a feature flag from
true, we’ll have to push changes to production.
- Having a constant in the class might prove inefficient when the number of flags grows.
- We’re not relying on environment variables like we originally intended to.
Let’s try and tackle the latter first.
Use environment variables
The smallest step we can take is to replace hardcoded values with environment variables.
With this code, our test still passes but not for the right reasons.
The application evaluates the presence of environment variables at runtime. If one misses in
ENV, the variable will default to
false instead of crashing (thanks to
fetch accepting a default value).
So our test does not really evaluate the presence and setting of our feature flags. It only tests if the return value is truthy or falsy. And it is, since
fetch defaults to
false every time. We could mock the value
ENV returns, but it’ll couple our tests too tightly to our testing environment. This smells like a bad idea.
Our tests could be better if we changed the expectation from
.to be_in([true, false]) to a more constrained
.to be(true). This would make sure that the tests don’t return a false positive.
Another thing I don’t like is passing variables - which are variable by definition - to a constant? Meh.
I’d rather group these variables into a dedicated file. If we could load all these feature flags’ variables at boot time, we’d save up some time when our application needs to evaluate those flags.
Move your environment variables to a single file and load them at boot time
One way to group your feature flags in the standard Rails configuration. And it’s simpler than it sounds.
You can create a custom accessor in your Rails configuration that leans on a single YAML file.
First, go to your
application.rb. At the end of the
Application class, add your own configuration.
What’s happening here? Now, your
Rails::Application::Configuration class has a
features accessor that you can call with
Rails.config.features will return an
ActiveSupport::OrderedOptions that inherits from
Hash and provides a dynamic accessor method.
Your application will now look for a
YAML file named
config directory, create a
features.yml file. The first key represents the environment where your variables will apply. In our example, I’ve used
shared which means that all environments will share the information.
We usually store strings in
YAML files, but it’s possible to execute Ruby code with the help of the
<%= => syntax.
Now, we have a single file where all our feature flags and their respective environment variables will be neatly gathered. I used to be a librarian, I love things arranged neatly.
Access our feature flags through Rails configuration
Now, a single file centralize all our feature flag keys and their respective environment variables. I used to be a librarian. I love things arranged neatly.
It’s time to get back to our
We don’t need our constant anymore. But we need to pull the configuration in its place.
Let’s whip up a second test.
Let’s try to code our method and make the test green.
Easy right! The test passes. On the other hand, we’re creating a dependency between our
Features class and the Rails configuration.
Now that we’ve made the change easy, it’s just a matter of changing
configuration in our
The full test suite is:
Okay, so why does my second test throw a failure?
Well, we created a dependency between
Features.configuration and the Rails configuration. And that dependency needs to be mocked!
This’ll take care of the dependency. Now, my tests pass.
Check for different return scenarii
What happens if the feature we’d like to check is not in our list?
configuration[feature.to_sym] will return
nil. Let’s update our tests accordingly.
My third test passes. But by convention,
enabled? should return a boolean, not
nil. So we need to enforce its return type.
Enforce a boolean return type
First, let’s update our tests.
There are several ways of doing this. Initially, I’d decided to modify
NilClass to accept a
to_boolean method. Several readers pointed out it was a tad overkill.
Use hashes capabilities
.configuration return an object inheriting from
Hash, we can use
.fetch on it.
What happens is if our
feature we’re passing along,
.enabled? will return the boolean stored in the YAML. If
feature is not present,
.fetch will default to
Another suggestion was using
.present? on my configuration hash.
The return results’ strategy works as in the previous example.
Use the double bang
A last suggestion was using
!! a.k.a the double bang. This would look like this:
The rationale behind it is:
If you negate something, that forces a boolean context. Of course, it also negates it. If you double-negate it, it forces the boolean context, but returns the proper boolean value.
And voilà! Your feature flags manager is ready. Now, you can safely wrap your features with
Features.enabled? :editorial_feed conditionals!
Hope you liked this code-along as much as I did!
Rémi - @firstname.lastname@example.org
PS: Many thanks to @NotGrm, @sunfox, @_swanson, and Kaloyan for their suggestions!
PPS: John Nunemaker wrote a Rebuttal and Addendum to this post. John - whose blog I love - makes some great suggestions to better the feature flags manager. I like the idea of switching features during runtime without restarting your dynos. Obviously, as the creator of Flipper - a gem dedicated to flipping features - he’s created something much more comprehensive for people who need more control over their feature toggles.
Enjoyed this post? Consider ✨ becoming a sponsor ✨.