My opinionated ruby on rails template
1# frozen_string_literal: true
2
3say 'Setting up Flipper for feature flags...', :green
4
5gem 'flipper'
6gem 'flipper-active_record'
7gem 'flipper-ui'
8gem 'flipper-active_support_cache_store'
9
10after_bundle do
11 say ' Running Flipper setup...', :cyan
12 rails_command 'generate flipper:setup'
13end
14
15say ' Creating Flipper initializer...', :cyan
16file 'config/initializers/flipper.rb', <<~RUBY
17 # frozen_string_literal: true
18
19 require 'flipper'
20 require 'flipper/adapters/active_record'
21 require 'flipper/adapters/active_support_cache_store'
22
23 Flipper.configure do |config|
24 config.default do
25 adapter = Flipper::Adapters::ActiveRecord.new
26 cached_adapter = Flipper::Adapters::ActiveSupportCacheStore.new(
27 adapter,
28 Rails.cache,
29 expires_in: 5.minutes
30 )
31 Flipper.new(cached_adapter)
32 end
33 end
34
35 # Register groups
36 Flipper.register(:staff) do |actor, _context|
37 actor.respond_to?(:admin_or_above?) && actor.admin_or_above?
38 end
39
40 Flipper.register(:admins) do |actor, _context|
41 actor.respond_to?(:admin?) && actor.admin?
42 end
43
44 Flipper.register(:super_admins) do |actor, _context|
45 actor.respond_to?(:super_admin?) && actor.super_admin?
46 end
47
48 # Configure Flipper UI
49 Flipper::UI.configure do |config|
50 config.application_breadcrumb_href = '/'
51 config.feature_creation_enabled = true
52 config.feature_removal_enabled = true
53
54 if Rails.env.production?
55 config.banner_text = 'Production Environment'
56 config.banner_class = 'danger'
57 elsif Rails.env.staging?
58 config.banner_text = 'Staging Environment'
59 config.banner_class = 'warning'
60 end
61 end
62RUBY
63
64say ' Creating Featureable concern...', :cyan
65file 'app/models/concerns/featureable.rb', <<~RUBY
66 # frozen_string_literal: true
67
68 # Include in models that can be used as Flipper actors
69 #
70 # Usage:
71 # class User < ApplicationRecord
72 # include Featureable
73 # end
74 #
75 # Flipper.enable(:new_dashboard, user)
76 # Flipper.enabled?(:new_dashboard, user)
77 #
78 module Featureable
79 extend ActiveSupport::Concern
80
81 # Uses public_id if available (e.g., "user_abc123"), otherwise falls back to "ClassName;id"
82 def flipper_id
83 respond_to?(:public_id) ? public_id : "\#{self.class.name};\#{id}"
84 end
85 end
86RUBY
87
88say ' Adding Featureable to User model...', :cyan
89inject_into_file 'app/models/user.rb', after: "class User < ApplicationRecord\n" do
90 " include Featureable\n"
91end
92
93say ' Creating Feature helper...', :cyan
94file 'app/helpers/feature_helper.rb', <<~RUBY
95 # frozen_string_literal: true
96
97 module FeatureHelper
98 # Check if a feature is enabled for the current user
99 #
100 # Usage in views:
101 # <% if feature_enabled?(:new_dashboard) %>
102 # <%= render 'new_dashboard' %>
103 # <% end %>
104 #
105 def feature_enabled?(feature, actor = current_user)
106 Flipper.enabled?(feature, actor)
107 end
108 end
109RUBY
110
111say ' Adding Feature helper to ApplicationController...', :cyan
112inject_into_class 'app/controllers/application_controller.rb', 'ApplicationController', <<~RUBY
113 helper FeatureHelper
114RUBY
115
116say 'Flipper feature flags configured!', :green
117say ' Dashboard at /admin/flipper (super_admin only)', :cyan
118say ' Run migrations: rails db:migrate', :yellow
119say ' Usage: Flipper.enable(:feature), Flipper.enabled?(:feature, user)', :cyan