Feature flags in Rails: How to roll out and manage your features like a pro
Ackshaey Singh
Posted on April 6, 2023
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.
Step 1: Add the Flipper Gem to Your Gemfile
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.
Step 2: Configure Flipper in Your Application
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: :admin_user
and :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
Step 3: Mount the Flipper UI in Your Application
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 config/routes.rb
file:
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 RoleConstraint
portion.
Step 4: (Optional) Create a Features Repository and test it
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
Step 5: Use Feature Flags in Your Application Code
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.
Step 6: Test Your Feature Flags
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 WebhooksController
:
# 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[0]['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.
Posted on April 6, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.