DIY Feature Flags for Ruby on Rails

Recently, while working on a project at


we encountered a very common issue: ingesting data from various API’s. This is nothing out of the ordinary, and we were able to resolve it by mapping the data into models and storing them locally.

Everything was fine, until we started seeing errors in Bugsnag in the staging environment. This is because we did not implement a safeguard that makes the ingestion run only in the Production environment.

You may have seen something like this in your codebase:

class DataSynchronizer
  def self.ingest_data
    return unless Rails.env.production?
    data = ExternalService::Api.fetch_resources

This solution works and is easy to implement: add a single line on top of your method body and you’re good to go. However, after taking a closer look at it, you may come to realize that this implementation, although fast, is not that great. Now, you are adding environment-dependent code to it and may run into hard-to-debug issues in the future.

Enter feature flags

Feature Flags

Feature flags are a way to enable or disable certain features in your application at runtime. This can be useful for a number of reasons:

  • You can use feature flags to test new features with a small group of users before rolling them out to everyone.
  • Feature flags can help you roll back changes quickly if something goes wrong.
  • You can use feature flags to customize the experience of groups of users.

Although there are many different implementations out there already (see the great flipper gem), they don’t always adapt 100% to your situation. This is why we decided to go with our own approach.

The requirements were the following:

  1. Have an easy way for the dev to toggle features on/off without doing any commits to the code.
  2. Be able to have a default logic for the feature flags but be able to override them in different environments.
  3. Have an easy way to test the features, with separate contexts for the possible states.
  4. We are not taking users/audiences into consideration for now, but we may want to add this capability in the future.

The solution we came up with was to have a default logic for the feature flags, but be able to override them with environment variables.

After thinking about this for a while, this is the interface we came up with:

# Create a feature flag with a name and a block which
# is evaluated each time the flag is checked.
Feature.create_feature :feature_name { some_ruby_code }

# Check if a feature flag is enabled

# check if a feature flag is enabled

# override the feature logic so that it is always enabled
Feature.feature_name = true

# override the feature logic so that it is never enabled
Feature.feature_name = false

# unset the feature override
Feature.feature_name = nil

We knew that to keep it dry, we would have to use a bit of metaprogramming to define singleton methods with the same name as the feature flag. Having these methods would help make code clearer and easier to understand.

This is the implementation

# app/services/feature.rb

# frozen_string_literal: true

class Feature
  class MissingFeatureError < StandardError; end

  class << self
    def enabled?(feature_name)
      return send(feature_name) if feature_defined?(feature_name)

      raise MissingFeatureError, "Feature #{feature_name} is not defined"

    def enable(feature_name)
      send("#{feature_name}=", true) if feature_defined?(feature_name)

    def disable(feature_name)
      send("#{feature_name}=", false) if feature_defined?(feature_name)

    def unset(feature_name)
      send("#{feature_name}=", nil) if feature_defined?(feature_name)

    def override?(feature_name)
      raise MissingFeatureError, "Feature #{feature_name} is not defined" unless feature_defined?(feature_name)

      env_name = "#{OVERRIDE_ENV_PREFIX}#{feature_name}".upcase
      res = ENV.fetch(env_name, nil)

      if res.is_a?(String)
        %w[true yes 1].include?(res.downcase)

    def feature_defined?(feature_name)
      return false if %i[enabled? override? create_feature? feature_defined?].include?(feature_name)


    def create_feature(feature_name)
      raise ArgumentError, "Feature #{feature_name} already defined" if feature_defined?(feature_name)

      define_singleton_method(feature_name) do
        override_value = override?(feature_name)

        return override_value unless override_value.nil?


      define_singleton_method("#{feature_name}?") do

      define_singleton_method("#{feature_name}=") do |value|
        override_name = "#{OVERRIDE_ENV_PREFIX}#{feature_name}".upcase

        if value
          ENV[override_name] = 'true'
        elsif value.is_a?(FalseClass)
          ENV[override_name] = 'false'

      define_singleton_method("override_#{feature_name}?") do


  create_feature :feature_name, -> { Rails.env.production? }

Now you can go ahead and create the feature flags you want at the bottom of the class and paste it into the app/services/feature.rb. Overriding features in your environments should be as easy as adding an environment variable:

# makes feature flag return always true. Possible values are true, yes, 1

# set the value to anything else to disable. If the ENV variable is not present,
# it will execute the code block passed to the create_feature method

Use it in your code wherever with:

def call
  return unless Feature.feature_name?


To override feature flags in your specs, simply add this to your specs:

before { Feature.feature_name = true|false|nil }

That’s it! With this you are now ready to remove that pesky return unless Rails.env.production? guard from your methods.

If you found this blog insightful and want to learn more, please do no hesitate to reach out

Other posts you may like: