DEV Community

Cover image for Use Pundit as a Rails Feature Flag System
Steve Polito
Steve Polito

Posted on • Originally published at stevepolito.design

Use Pundit as a Rails Feature Flag System

In this tutorial, I'll show you how to create a feature flag system in Rails using pundit and a features column on the users table.

Resources

Step 1: Initial Setup

This tutorial assumes you are using devise and have a User model. However, you should still be able to follow along and implement this pattern even if that's not the case.

  1. Create a Post scaffold.
rails g scaffold Post title:string user:references meta_description:text
Enter fullscreen mode Exit fullscreen mode
  1. Add a features column to the users table by running the following command.
rails g migration add_features_to_users features:jsonb 
Enter fullscreen mode Exit fullscreen mode
  1. Set a default value on the features column.

class AddFeaturesToUsers < ActiveRecord::Migration[6.1]
  def change
    add_column :users, :features, :jsonb, default: {}
  end
end
Enter fullscreen mode Exit fullscreen mode

What's Going On Here?

  • We add a JSONB Column to our users table. This will allow us to store multiple features in one column, compared to making a column for each feature.
  • We add default: {} simply to add a formatted default value to this column.
  1. Run the migrations.
rails db:migrate
Enter fullscreen mode Exit fullscreen mode
  1. Set features on User model.
class User < ApplicationRecord
  ...  
  FEATURES = %i[enable_post_meta_description].freeze
  store :features, accessors: User::FEATURES
end
Enter fullscreen mode Exit fullscreen mode

What's Going On Here?

  • We create a FEATURES constant that will store the names of our features as symbols by calling %i on the array. We call .freeze to ensure this constant cannot be updated anywhere else.
  • We use ActiveRecord::Store to interface with the features column. This will allow us to call @user.enable_post_meta_description instead of user.features.enable_post_meta_description. By passing User::FEATURES into the accessors parameter we can continue to add new features in the FEATURES constant.

Setting a features column on the users table will allow us to enable/disable features on a per-user basis.

  1. Enable the enable_post_meta_description for a user. That way you have something to test.
User.last.update(enable_post_meta_description: true)
Enter fullscreen mode Exit fullscreen mode

Step 2: Install Pundit and Build a Policy

Next, we'll need to install and configure pundit.

  1. Install pundit.
bundle add pundit
Enter fullscreen mode Exit fullscreen mode
  1. Generate the base pundit files.
rails g pundit:install
Enter fullscreen mode Exit fullscreen mode
  1. Include pundit in the ApplicationController
class ApplicationController < ActionController::Base
  include Pundit
end
Enter fullscreen mode Exit fullscreen mode

Step 3: Build a Feature Flag Policy

  1. Generate a namespaced pundit policy.
rails g pundit:policy feature/enable_post_meta_description
Enter fullscreen mode Exit fullscreen mode
  1. Build the policy
class Feature::EnablePostMetaDescriptionPolicy < ApplicationPolicy
  def ceate?
    user.present? && (user.enable_post_meta_description == true)
  end

  def permitted_attributes
    if user.enable_post_meta_description == true
      [:title, :user_id, :meta_description]
    else
      [:title, :user_id]
    end
  end
  ...
end
Enter fullscreen mode Exit fullscreen mode

What's Going On Here?

  • We generate a policy under the feature namespace. This is not required, but it helps keep things organized and will allow us to add new policies for new features later. We also name this policy to match the name of the feature in the User model.
  • We build a ceate? method that returns true or false based on whether or not that user has the enable_post_meta_description feature set to true. We could have called the method index?, new?, update?, edit? or destroy? but create? makes the most sense in this context. We're building a policy that enables a user to create a meta description on a post.
  • We used pundit's permitted_attributes method to return an array of paramters to be used in the PostsController. This will allow us to conditionally permit the meta_description parameter.

Step 4: Implement the Feature Flag

  1. Update the post_params to hook into the permitted_attributes method.
class PostsController < ApplicationController
  before_action :authenticate_user!, except: %i[ show index ] 
  before_action :set_post, only: %i[ show edit update destroy ]

  private
    ...
    def post_params
      params.require(:post).permit(
        Feature::EnablePostMetaDescriptionPolicy.new(current_user, Post).permitted_attributes
      )
    end
end
Enter fullscreen mode Exit fullscreen mode

What's Going On Here?

  • We instantiate a new instance of the Feature::EnablePostMetaDescriptionPolicy policy class and pass in the current_user and Post per pundit's API. Then we call permitted_attributes to load the correct parameters based on whether the user has access to the meta_description.
  • Note that we call authenticate_user! before all actions except show and index since the Feature::EnablePostMetaDescriptionPolicy relies on a user.
  1. Conditionally show the meta_description in the post form partial.
# app/views/posts/_form.html.erb
<%= form_with(model: post) do |form| %>
  ...
  <% if Feature::EnablePostMetaDescriptionPolicy.new(current_user, post).create? %>
    <div class="field">
      <%= form.label :meta_description %>
      <%= form.text_area :meta_description %>
    </div> 
  <% end %>
  ...
<% end %>
Enter fullscreen mode Exit fullscreen mode

What's Going On Here?

  • We wrap the meta_description field in a new instance of the Feature::EnablePostMetaDescriptionPolicy policy class. We call create? which returns true or false based on whether the user has access to the meta_description.

Did you like this post? Follow me on Twitter to get even more tips.

Discussion (0)