Feature flagging is a widely adopted technique used in software development that enables developers to turn features or sections of their code on or off for different groups of users. It allows for the gradual release of new features to subsets of users, which makes it easier to test and monitor performance before enabling it for everyone. It provides developers with more flexibility and control over their releases, which can help avoid potential issues or rollbacks.
For example, imagine a team is working on a new feature that involves a significant change to the login process for a web app. Without feature flagging, the team would have to deploy the new code to everyone at once, potentially causing breaking the web app for all users. With feature flagging, the team can gradually enable the new login process for specific groups of users, testing and monitoring performance along the way. If any issues arise, the team can quickly turn off the feature flag to avoid widespread issues.
Even if you’re working on your own solo developer low throughput Rails app, feature flagging can be useful in isolating and eliminating problematic releases. I recently had to implement a feature flag system for Firecode.io, which is written primarily in Rails. In this blog post, we will look at how to use the Flipper gem to implement a basic feature flagging system in a Rails application. I have taken semi real examples and code from Firecode.io to illustrate the implementation with an example.
To get started with Flipper, add the following lines to your Gemfile:
### Feature flagging with Flipper gem 'flipper' gem 'flipper-active_record' gem 'flipper-ui'
These gems provide the core functionality for feature flagging with Flipper, as well as a user interface for managing your feature flags.
Next, create a file
flipper.rb in your
config/initializers directory, and add code for configuring user groups. In this example we have created two user groups which can be accessed in the Flipper UI, and features can be enabled or disabled for these groups individually.
This code registers two feature blocks with Flipper:
:patron. These blocks define the policy for enabling or disabling the features based on the properties of the current user. As an example, Firecode.io offers many perks that are reserved for Patrons, and the
:patron policy block we can apply features that are active only for Patrons.
# Initializer for the Flipper gem. Setups up the Flipper UI and registers # feature groups. # admin_user policy block Flipper.register(:admin_user) do |actor, _context| actor.respond_to?(:admin?) && actor.admin? end # patron policy block Flipper.register(:patron) do |actor, _context| actor.respond_to?(:patron?) && actor.patron? end
To make it easy to manage your feature flags, you can mount the Flipper UI in your application. To do this, add the following code to your
Rails.application.routes.draw do # Flipper for feature flags mount Flipper::UI.app(Flipper) => '/flipper', constraints: RoleConstraint.new(:manage, :all) end
This code mounts the Flipper UI at the
/flipper endpoint in your application. The
RoleConstraint class is used to restrict access to the UI to users who have the
manage role. You can customize this constraint to suit your specific needs. In this case, we're using the CanCanCan gem to gate specific routes to admin users. If you haven't worked with CanCanCan before, ignore the
To make it easy to manage the feature symbols and check the state of your feature flags in your application code, you can create a
FeaturesRepo module that provides a simple API for checking the state of your feature flags. Create a file called
features_repo.rb in an
app/repositories directory, and add the following code:
# frozen_string_literal: true # Repository of feature flags. Uses the Flipper gem to manage feature flags. module FeaturesRepo # All feature flags should be defined here. Do not use strings. FORWARD_INCOMING_WEBHOOKS = :forward_incoming_webhooks # Returns the feature flag value for a given feature # @param feature [Symbol] the feature to check # @return [Boolean] the feature flag value def self.enabled?(feature) Flipper.enabled?(feature) end # Enables a feature flag # @param feature [Symbol] the feature to enable # @return [Boolean] the feature flag value def self.enable(feature) Flipper.enable(feature) end # Disables a feature flag # @param feature [Symbol] the feature to disable # @return [Boolean] the feature flag value def self.disable(feature) Flipper.disable(feature) end end
This code defines a module called
FeaturesRepo that provides methods for checking the state of your feature flags. The
FORWARD_INCOMING_WEBHOOKS constant is defined as an example of a feature flag that can be used in your application code - we’ll take a look at how it is used below.
You should also create a test for this repo, just in case the Flipper API changes in the future or if our AI overlords generate garbage code and break our app. Create a file
test/repositories/features_repo_test.rb with the following tests:
# frozen_string_literal: true require 'test_helper' require 'minitest/autorun' # Tests for the FeaturesRepo module class FeaturesRepoTest < ActiveSupport::TestCase setup do print_test_case_running end teardown do print_test_case_pass end test 'enabled? should return false if a feature is disabled' do Flipper.disable :test_feature assert_equal(false, FeaturesRepo.enabled?(:test_feature)) end test 'enabled? should return true if a feature is enabled' do Flipper.enable :test_feature assert_equal(true, FeaturesRepo.enabled?(:test_feature)) end test 'enable should enable a feature' do Flipper.disable :test_feature assert_equal(false, FeaturesRepo.enabled?(:test_feature)) FeaturesRepo.enable :test_feature assert_equal(true, FeaturesRepo.enabled?(:test_feature)) end test 'disable should disable a feature' do Flipper.enable :test_feature assert_equal(true, FeaturesRepo.enabled?(:test_feature)) FeaturesRepo.disable :test_feature assert_equal(false, FeaturesRepo.enabled?(:test_feature)) end end
Now that you have set up Flipper and created a repository for your feature flags, you can start using feature flags in your application code. For example, here is some semi real code from Firecode.io for a
WebhooksController that checks the state of a feature flag before forwarding incoming webhooks to Slack - it has been (over) simplified for conciseness, always remember to sanitize any external data before forwarding it to other destinations.
class WebhooksController < ApplicationController # Bypass authenticity token check since we are receiving webhooks from external services skip_before_action :verify_authenticity_token def send if FeaturesRepo.enabled? FeaturesRepo::FORWARD_INCOMING_WEBHOOKS ForwardService.forward(message: build_forward_request_output(request)) end render json: ApiConstants::EMPTY_JSON_RECEIVED, status: :ok end end
This code checks the state of the
FORWARD_INCOMING_WEBHOOKS feature flag before forwarding incoming webhooks. If the feature flag is disabled, the message is not forwarded.
To ensure that your feature flags are working as expected, it is important to write tests that cover all of the possible scenarios. Here is an example test for the hypothetical
# frozen_string_literal: true require 'test_helper' require 'sidekiq/testing' module Api module V1 class WebhooksControllerTest < ActionDispatch::IntegrationTest include Devise::Test::IntegrationHelpers ... test 'send should not use ForwardService to enqueue a SlackWorker job if the feature flag is disabled' do Flipper.disable FeaturesRepo::FORWARD_INCOMING_WEBHOOKS assert_equal(0, Sidekiq::Worker.jobs.size) post @send_path assert_equal(0, Sidekiq::Worker.jobs.size) end test 'send should use ForwardService to enqueue a SlackWorker job if the feature flag is enabled' do Flipper.enable FeaturesRepo::FORWARD_INCOMING_WEBHOOKS assert_equal(0, Sidekiq::Worker.jobs.size) post @send_path assert_equal(1, Sidekiq::Worker.jobs.size) assert_equal('SlackWorker', Sidekiq::Worker.jobs['class']) end end end end
When you deploy this code to production or test it locally, you'll have to first create a feature flag called
forward_incoming_webhooks in the Flipper UI (mounted at /flipper and accessible to admin users) and enable it. Fortunately, the Flipper UI is awesome and makes managing features really easy.
That’s it! You now have a fully configured feature flagging system in your Rails app. Notice we didn’t cover some more advanced features that Flipper offers, including enabling features for a user group or individual users. For that, check out Flipper on Github. We also didn’t cover feature flagging frontend features in this post - if that becomes a requirement we could easily create an endpoint that uses the
FeaturesRepo and sends enabled features to the frontend to toggle. If you learned something new consider following me here - I’ll be putting out more content on Ruby on Rails and software development as I work on Firecode.io. Preparing for a coding interview? Check out Firecode.io.