This document describes our feature flag strategy for safely developing and deploying new features.
Feature flags allow us to:
Add flags to environment configuration:
# config/environments/development.rb
config.x.features.champion_portal = true # Enable for local dev
# config/environments/staging.rb
config.x.features.champion_portal = true # Enable for testing
# config/environments/production.rb
config.x.features.champion_portal = false # Disable until ready
Add to ApplicationController for easy access:
# app/controllers/application_controller.rb
helper_method :feature_enabled?
def feature_enabled?(feature_name)
Rails.application.config.x.features.send(feature_name)
rescue NoMethodError
false
end
class AlumniNetwork::PortalController < AlumniNetwork::BaseController
before_action :require_portal_access
private
def require_portal_access
unless feature_enabled?(:champion_portal)
redirect_to root_path, alert: "This feature is not yet available."
end
end
end
<% if feature_enabled?(:champion_portal) %>
<%= link_to "My Portal", champions_portal_path %>
<% end %>
For completely hiding routes:
# config/routes.rb
if Rails.application.config.x.features.champion_portal
namespace :alumni_network do
resource :portal, only: [:show]
end
end
⚠️ Note: Route-level flags require app restart. Controller-level checks are preferred for flexibility.
config.x.features, feature_enabled?)| Flag | Description | Dev | Staging | Production |
|---|---|---|---|---|
champion_portal |
New Alumni Portal (replaces signup flow) | ✅ | ✅ | ❌ |
Cp::FeatureFlag, cp_feature_enabled?)Staff-toggleable at /alumni_network/feature_flags (portal_admin) — instant, no deploy/restart. Govern the belmontalum.com portal only (nothing touches the alumnichampions.com signup). Flags hide champion-facing surfaces; all data, jobs, and Champion Admin management stay live, so a feature re-enables with no rebuild.
Flag (key) |
Gates | Default: Dev/Staging | Default: Prod |
|---|---|---|---|
news |
Community news blocks, news index/detail, likes, news submission flow | ✅ | ❌ |
photos |
Community album carousels, album index/detail | ✅ | ❌ |
events |
Dashboard events sidebar, community upcoming-events, event index/detail, RSVP, event submission flow | ✅ | ❌ |
champion_role |
Roles microsite + Champion Info, role cards, dashboard role ideas, role-quiz wizard actions, champion nudge, and role badges portal-wide | ✅ | ❌ |
your_impact |
Your Impact dashboard sidebar card | ✅ | ❌ |
Resolution order (Cp::FeatureFlag.enabled?(:news)): ENV["FEATURE_NEWS"] ("true"/"false") → cached DB enabled column → false. The ENV override is an emergency kill-switch; day-to-day toggling is via the admin UI.
Usage:
# Controller (Cp:: namespace)
before_action -> { require_feature!(:news) }, only: [:index, :show] # OFF → redirect to dashboard
# View / controller
cp_feature_enabled?(:news) # helper
Cp::FeatureFlag.enabled?(:news) # model-level
Rows arrive via the migration data-insert (or db/seeds.rb for schema:load), enabled: !Rails.env.production?. Each environment has its own DB, so the boolean is naturally per-environment. Tests: fixtures test/fixtures/cp/feature_flags.yml (all on); disable one explicitly to exercise an OFF path.
All feature flags support ENV variable overrides for quick enable/disable without deploying:
# Pattern used in all environments:
config.x.features.champion_portal = ENV.fetch('FEATURE_CHAMPION_PORTAL', '<default>') == 'true'
# Enable on staging (instant, no deploy needed)
heroku config:set FEATURE_CHAMPION_PORTAL=true -a alumni-lookup-staging
# Enable on production
heroku config:set FEATURE_CHAMPION_PORTAL=true -a alumni-lookup
# Disable (emergency rollback)
heroku config:set FEATURE_CHAMPION_PORTAL=false -a alumni-lookup
# Check current value
heroku config:get FEATURE_CHAMPION_PORTAL -a alumni-lookup
# Remove override (use code default)
heroku config:unset FEATURE_CHAMPION_PORTAL -a alumni-lookup
| Environment | Default | Override With |
|---|---|---|
| Development | true |
FEATURE_CHAMPION_PORTAL=false |
| Test | true |
(in test setup) |
| Staging | false |
FEATURE_CHAMPION_PORTAL=true |
| Production | false |
FEATURE_CHAMPION_PORTAL=true |
When removing a feature flag:
champion_portal, not new_featureif flag_a && flag_b complexity