DEV Community

Cover image for Feature flags in Rails: How to roll out and manage your features like a pro
Ackshaey Singh
Ackshaey Singh

Posted on

Feature flags in Rails: How to roll out and manage your features like a pro

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.

Features

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'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Feature Disabled

Feature Enabled

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.

Top comments (0)