Global feature flag

Hi everyone,

I’ve got a question about testing approach, but also about rolling out a new feature to a project (something Thoughtbot have a lot of experience at :slight_smile: ).

I work on an open source project that helps people give feedback on development applications. Currently you can write to the local planning authority about an application. We’ve been working on a new feature that allows people to also start an exchange with one of their local councillors.

We’re rolling this out to just a few local areas for starters, and we want to be able to turn the feature on and off globally easily if things need a quick fix.

Does anyone here have experience using a global feature flag like this? Any examples of how it would be implemented and tested would be greatly appreciated.

Here’s what I’m imagining: we can add an environment variable ‘COUNCILLORS_ENABLED’ to toggle the ability to choose who to write to:

scenario "can see councillor messages sections when feature is switched on" do
  ClimateControl.modify(COUNCILLORS_ENABLED: true) do
     visit application_path(application)

     expect(page).to have_content("Who should this go to?")
   end
end

But, I can’t for the life of me get this test to work, I keep getting:

 Failure/Error: ClimateControl.modify(COUNCILLORS_ENABLED: true) do
 NoMethodError:
   undefined method `keys' for nil:NilClass

UPDATE: The problem was that a was setting the var to a boolean, but it must be a String

I feel like I could be on the wrong track with this whole approach :S

I just saw in the upcase repo the way Decks was switched on Allow all users to access Decks · thoughtbot/upcase@978662a · GitHub

Maybe it would be better to have a method councillors_enabled? that returns true if the environment variable is set. Then I could stub that method and not have to worry about managing the environment variables in tests. I might try that next.

I hope this is an interesting question :yum: I’m sure it’s something relevant to a lot of projects.

I did this once with the acts_as_singleton gem and just had a singleton that stored a bunch of booleans/configuration settings for the app. This was in a multitenant app where each tenant had their own configuration settings based on their subscription. It worked pretty well, but we ended up having to call the singleton in lots of controllers which is an extra db query each time. Maybe caching it in redis or something would be more performant.

Hello @equivalentideas, thanks for the question.

For the testing, I like the overall approach w/ ClimateControl as it makes the test nicely explicit, although I’m not sure I would use ENV vars for rollout type configuration. As one other note, I’d like extract the specific message and use a matcher like:

  expect(page).to have_councilor_selection_message
end

# lower in the test file

def have_councilor_selection_message
  have_content("Who should this go to?")
end

As for the mechanism for rollout, as I said above, I’m not sure I’d use ENV vars, although I don’t have a clear reason in my head as to why.

Instead, I’d consider the simple approach Ben and I took with the flashcards rollout. If you end up doing this sort of work often, I might consider a more complete solution like the rollout gem. This blog post on rollout has a great summary.

Thanks for your feedback here @christoomey. I ended up using the ENV var as thats a convention our team is using for another feature. The rollout gem looks pretty powerful and a bit beyond our requirements right now.

Here’s the commit if anyone’s interested Move from params to environment variable to enable councillors · openaustralia/planningalerts@f7d91f0 · GitHub