Skip to content

Commit 03e7533

Browse files
committed
Initial Feature Flag system
Why are these changes being introduced: * FlipFlip has not been maintained in years and we believe it is causing more problems than it is solving. We need a simpler, more maintainable feature flag system. * Our needs are simple: we need to be able to turn features on and off based on environment variables. Relevant ticket(s): * https://mitlibraries.atlassian.net/browse/USE-77 How does this address that need: * Created a simple Feature model that reads from environment variables to determine if a feature is enabled or not. * Introduces consistent naming patterns for feature ENV variables. Document any side effects to this change: * This only introduces the new feature flag system. No existing feature flags have been migrated yet and FlipFlip is still present.
1 parent 81c6bd8 commit 03e7533

File tree

2 files changed

+128
-0
lines changed

2 files changed

+128
-0
lines changed

app/models/feature.rb

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Central feature flag management class for the application. This class provides a simple,
2+
# centralized way to manage feature flags throughout the codebase.
3+
#
4+
# Feature flags are controlled through environment variables with the prefix 'FEATURE_'
5+
# followed by the uppercase feature name. Each flag defaults to false unless explicitly
6+
# enabled via environment variable to ensure consistency in how our features work.
7+
#
8+
# @example Basic Usage
9+
# Feature.enabled?(:geodata) # true if FEATURE_GEODATA=true in ENV
10+
# Feature.enabled?(:unknown) # Returns false for undefined features
11+
#
12+
# @example Setting Flags in Environment
13+
# # In local ENV or Heroku config:
14+
# FEATURE_GEODATA=true # Enables the GDT feature
15+
# FEATURE_BOOLEAN_PICKER=true # Enables the boolean picker feature
16+
#
17+
# # Any non-true value or not setting ENV does not enable the feature
18+
# FEATURE_GEODATA=false # Does not enable the GDT feature
19+
# FEATURE_GEODATA=1 # Does not enable the GDT feature
20+
# FEATURE_GEODATA=on # Does not enable the GDT feature
21+
#
22+
# @example Usage in Different Contexts
23+
# # In models/controllers:
24+
# return unless Feature.enabled?(:geodata)
25+
#
26+
# # In views:
27+
# <% if Feature.enabled?(:geodata) %>
28+
#
29+
# # In tests:
30+
# ClimateControl.modify FEATURE_GEODATA: 'true' do
31+
# assert Feature.enabled?(:geodata)
32+
# end
33+
#
34+
class Feature
35+
# List of all valid features in the application
36+
VALID_FEATURES = %i[geodata boolean_picker].freeze
37+
38+
# Check if a feature is enabled by name
39+
#
40+
# @param feature_name [Symbol] The name of the feature to check
41+
# @return [Boolean] true if the feature is enabled, false otherwise
42+
# @example Check if a feature is enabled
43+
# Feature.enabled?(:geodata)
44+
def self.enabled?(feature_name)
45+
return false unless VALID_FEATURES.include?(feature_name)
46+
47+
ENV.fetch("FEATURE_#{feature_name.to_s.upcase}", false).to_s.downcase == 'true'
48+
end
49+
end

test/models/feature_test.rb

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
require 'test_helper'
2+
3+
class FeatureTest < ActiveSupport::TestCase
4+
test 'defined features default to false' do
5+
refute Feature.enabled?(:geodata)
6+
refute Feature.enabled?(:boolean_picker)
7+
end
8+
9+
test 'undefined features return false' do
10+
refute Feature.enabled?(:undefined_feature)
11+
end
12+
13+
test 'features can be enabled via environment variables' do
14+
ClimateControl.modify FEATURE_GEODATA: 'true' do
15+
assert Feature.enabled?(:geodata)
16+
end
17+
end
18+
19+
test 'features can be disabled via environment variables' do
20+
ClimateControl.modify FEATURE_GEODATA: 'false' do
21+
refute Feature.enabled?(:geodata)
22+
end
23+
end
24+
25+
test 'environment variables are case insensitive' do
26+
ClimateControl.modify FEATURE_GEODATA: 'TRUE' do
27+
assert Feature.enabled?(:geodata)
28+
end
29+
30+
ClimateControl.modify FEATURE_GEODATA: 'True' do
31+
assert Feature.enabled?(:geodata)
32+
end
33+
34+
ClimateControl.modify FEATURE_GEODATA: 'false' do
35+
refute Feature.enabled?(:geodata)
36+
end
37+
38+
ClimateControl.modify FEATURE_GEODATA: 'FALSE' do
39+
refute Feature.enabled?(:geodata)
40+
end
41+
end
42+
43+
test 'non true boolean values default to false' do
44+
ClimateControl.modify(
45+
FEATURE_GEODATA: 'invalid'
46+
) do
47+
refute Feature.enabled?(:geodata)
48+
end
49+
50+
ClimateControl.modify(
51+
FEATURE_GEODATA: '1'
52+
) do
53+
refute Feature.enabled?(:geodata)
54+
end
55+
56+
ClimateControl.modify(
57+
FEATURE_GEODATA: 'yes'
58+
) do
59+
refute Feature.enabled?(:geodata)
60+
end
61+
end
62+
63+
test 'feature names are case sensitive' do
64+
ClimateControl.modify FEATURE_GEODATA: 'true' do
65+
assert Feature.enabled?(:geodata)
66+
refute Feature.enabled?(:GDT)
67+
end
68+
end
69+
70+
test 'multiple features can be controlled independently' do
71+
ClimateControl.modify(
72+
FEATURE_GEODATA: 'true',
73+
FEATURE_BOOLEAN_PICKER: 'false'
74+
) do
75+
assert Feature.enabled?(:geodata)
76+
refute Feature.enabled?(:boolean_picker)
77+
end
78+
end
79+
end

0 commit comments

Comments
 (0)