Skip to content

Feature toggles

A feature toggle hides a feature behind a runtime switch, so you can deploy unfinished work and enable it for selected users or projects later.

EasyFeature (easy_lib/easy_features/easy_feature.rb) is built on Flipper. Register every toggle once with EasyFeatures.register, then check it with EasyFeature.enabled?.

  • EasyFeature.enabled?(key, actor = User.current) delegates to Flipper and caches the result per actor for EasyFeature::CACHE_TTL (5 seconds).
  • An unregistered key returns false.
  • When the database or Redis fails, the error is logged and the result is false.
easy_lib/easy_features/easy_feature.rb
  CACHE_TTL = 5.seconds

  # @param [Symbol, String] key
  # @param [ApplicationRecord] actor, usually User
  # @param [Boolean] default
  # @return [Boolean]
  def self.enabled?(key, actor = User.current)
    return false unless EasyFeatures.known?(key)

    cache_key = "flipper:#{key}:#{actor.flipper_id}"

    Rails.cache.fetch(cache_key, expires_in: CACHE_TTL) do
      Flipper.enabled?(key, actor)
    end
  rescue ActiveRecord::StatementInvalid, ActiveRecord::ConnectionNotEstablished,
         Redis::BaseError, RedisClient::Error => e
    Rails.logger.error("[feature] flipper error key=#{key} #{e.class}: #{e.message}")
    false
  end

Register a feature

Register the toggle during boot, for example in an initializer of your plugin (config/initializers/04_features.rb). The key is the first, positional argument and must be a symbol.

config/initializers/04_features.rb
1
2
3
4
5
6
7
8
9
EasyFeatures.register(
  :my_feature,
  owners: %w[my-team],
  ref_id: "1234",
  introduced_in: "16.0.0",
  status: :experimental,
  target_version: "16.2.0",
  description: "Short description shown in the Flipper UI"
)

Arguments:

  • key: a non-empty symbol. Registering the same key twice raises ArgumentError.
  • owners: an array of team names, not individuals. Required.
  • ref_id: a reference to the task that tracks the feature, in whatever tracker you use. Required, any string.
  • introduced_in: the version in which the toggle was added. Required, parsed with Gem::Version.
  • status: :experimental, :beta, :stable or :released. Required; use :experimental for new features.
  • target_version: the version in which the toggle is planned to be released or removed. Optional. Feature#expired? is true once the platform version is higher.
  • description: optional. It appears on the Flipper feature list and detail pages. Use it for operator guidance, including deprecation and migration instructions. A DEPRECATED description is informational only: it does not disable the feature or prevent editing.

Enable a feature

Administrators enable registered features in the Flipper UI at /flipper. You can enable a feature for everyone or for selected actors. The usual actor is a User, but it can be a Project or another class. The Flipper ID has the form Class;ID, for example User;28628 or Project;42.

Use a feature

Wrap EasyFeature.enabled? in a helper and call the helper from controllers, jobs and services:

lib/my_plugin.rb
1
2
3
4
5
module MyPlugin
  def self.my_feature_enabled?
    EasyFeature.enabled?(:my_feature)
  end
end
app/controllers/my_feature_controller.rb
class MyFeatureController < ApplicationController
  before_action :ensure_feature

  private

  def ensure_feature
    render_404 unless MyPlugin.my_feature_enabled?
  end
end

Results are already cached per actor, so do not add your own cache. Use short symbol keys (:issue_suggestions) and name the helper after the key plus _enabled?.

Legacy API

EasyFeature.active? reads an EASY_FEATURE_*_ACTIVE environment variable and logs a deprecation warning. Do not use it in new code.