subflag-rails 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2d5585ee53dcf0c1864f3d9bf5845a41cd8e3c884fe11ca457bc51b3ded8cfcd
4
- data.tar.gz: 1014353a80ee041027dd501aea4035f7043b873dbf75506678e2852b948f436a
3
+ metadata.gz: bd3dfb04635e013ed8a9da2282150acf511c8704aace3c439a06f7e5dd0ac24a
4
+ data.tar.gz: adc804044699b6cea75d8b44fc742d08f2a96b24092a8e66d19b419f41c45fa1
5
5
  SHA512:
6
- metadata.gz: 7a0b5adb5b8df94ed4e07ba41e47b4430b719b2cf741695b6f9029239aefa6437535d36fb08d9b2f12d84911126cdfa45d15429b2c024f0d93c694f6ee5e77e8
7
- data.tar.gz: 582032e633830bb9e5867ae6883545a4ba5fd0ece16556a542f9de3513a5c0853f4ce7a121ad4af3d3ea6bc901ff136abc9f77f12eaa37789d64fa77bd11a750
6
+ metadata.gz: eb94613ee232272a8799ff0f7623c02d2d6e398ed0b7fbb231ff48bdf1e160ff99cb2110622567374012de4418e9b40129f7b14b0fabe7624904c399696f6dfc
7
+ data.tar.gz: 6327d0185659e82b95a922380cfbae030a42f91676a5a00d1a5fb43120c46eedd8d88432c0ec5cc7d20724494abd57be11ac6c47e2da26b536daa159174bf9ac
data/README.md CHANGED
@@ -1,6 +1,25 @@
1
1
  # Subflag Rails
2
2
 
3
- Typed feature flags for Rails. Booleans, strings, numbers, and JSON — all targetable by user.
3
+ Typed feature flags for Rails. Booleans, strings, numbers, and JSON — with pluggable backends.
4
+
5
+ [Subflag](https://subflag.com)
6
+
7
+ ## Backends
8
+
9
+ Choose where your flags live:
10
+
11
+ | Backend | Use Case | Flags Stored In |
12
+ |---------|----------|-----------------|
13
+ | `:subflag` | Production with dashboard, environments, targeting | Subflag Cloud |
14
+ | `:active_record` | Self-hosted, no external dependencies, [built-in admin UI](#admin-ui-activerecord) | Your database |
15
+ | `:memory` | Testing and development | In-memory hash |
16
+
17
+ **Same API regardless of backend:**
18
+
19
+ ```ruby
20
+ subflag_enabled?(:new_checkout) # Works with any backend
21
+ subflag_value(:max_projects, default: 3) # Works with any backend
22
+ ```
4
23
 
5
24
  ## Installation
6
25
 
@@ -8,9 +27,14 @@ Add to your Gemfile:
8
27
 
9
28
  ```ruby
10
29
  gem 'subflag-rails'
30
+
31
+ # If using Subflag Cloud (backend: :subflag), also add:
32
+ gem 'subflag-openfeature-provider'
11
33
  ```
12
34
 
13
- Run the generator:
35
+ ### Option 1: Subflag Cloud (Default)
36
+
37
+ Dashboard, environments, percentage rollouts, and user targeting.
14
38
 
15
39
  ```bash
16
40
  rails generate subflag:install
@@ -29,6 +53,38 @@ subflag:
29
53
 
30
54
  Or set the `SUBFLAG_API_KEY` environment variable.
31
55
 
56
+ ### Option 2: ActiveRecord (Self-Hosted)
57
+
58
+ Flags stored in your database. No external dependencies.
59
+
60
+ ```bash
61
+ rails generate subflag:install --backend=active_record
62
+ rails db:migrate
63
+ ```
64
+
65
+ Create flags directly:
66
+
67
+ ```ruby
68
+ Subflag::Rails::Flag.create!(key: "new-checkout", value: "true", value_type: "boolean")
69
+ Subflag::Rails::Flag.create!(key: "max-projects", value: "100", value_type: "integer")
70
+ Subflag::Rails::Flag.create!(key: "welcome-message", value: "Hello!", value_type: "string")
71
+ ```
72
+
73
+ ### Option 3: Memory (Testing)
74
+
75
+ In-memory flags for tests and local development.
76
+
77
+ ```bash
78
+ rails generate subflag:install --backend=memory
79
+ ```
80
+
81
+ Set flags programmatically:
82
+
83
+ ```ruby
84
+ Subflag::Rails.provider.set(:new_checkout, true)
85
+ Subflag::Rails.provider.set(:max_projects, 100)
86
+ ```
87
+
32
88
  ## Usage
33
89
 
34
90
  ### Controllers & Views
@@ -159,42 +215,6 @@ In Ruby, use underscores — they're automatically converted to dashes:
159
215
  subflag_enabled?(:new_checkout) # looks up "new-checkout"
160
216
  ```
161
217
 
162
- ## Testing
163
-
164
- Stub flags in your tests:
165
-
166
- ```ruby
167
- # spec/rails_helper.rb (RSpec)
168
- require "subflag/rails/test_helpers"
169
- RSpec.configure do |config|
170
- config.include Subflag::Rails::TestHelpers
171
- end
172
-
173
- # test/test_helper.rb (Minitest)
174
- require "subflag/rails/test_helpers"
175
- class ActiveSupport::TestCase
176
- include Subflag::Rails::TestHelpers
177
- end
178
- ```
179
-
180
- ```ruby
181
- # In your specs/tests
182
- it "shows new checkout when enabled" do
183
- stub_subflag(:new_checkout, true)
184
- stub_subflag(:max_projects, 100)
185
-
186
- visit checkout_path
187
- expect(page).to have_content("New Checkout")
188
- end
189
-
190
- # Stub multiple at once
191
- stub_subflags(
192
- new_checkout: true,
193
- max_projects: 100,
194
- headline: "Welcome!"
195
- )
196
- ```
197
-
198
218
  ## Request Caching
199
219
 
200
220
  Enable per-request caching to avoid multiple API calls for the same flag:
@@ -214,6 +234,25 @@ subflag_enabled?(:new_checkout) # Cache hit
214
234
  subflag_enabled?(:new_checkout) # Cache hit
215
235
  ```
216
236
 
237
+ ## Cross-Request Caching
238
+
239
+ By default, prefetched flags are only cached for the current request. To cache across multiple requests using `Rails.cache`, set a TTL:
240
+
241
+ ```ruby
242
+ # config/initializers/subflag.rb
243
+ Subflag::Rails.configure do |config|
244
+ config.api_key = Rails.application.credentials.subflag_api_key
245
+ config.cache_ttl = 30.seconds # Cache flags in Rails.cache for 30 seconds
246
+ end
247
+ ```
248
+
249
+ With `cache_ttl` set:
250
+ - First request fetches from API and stores in `Rails.cache`
251
+ - Subsequent requests (within TTL) read from `Rails.cache` — no API call
252
+ - After TTL expires, next request fetches fresh data
253
+
254
+ This significantly reduces API load for high-traffic applications. Choose a TTL that balances freshness with performance — 30 seconds is a good starting point.
255
+
217
256
  ## Bulk Flag Evaluation (Prefetch)
218
257
 
219
258
  For optimal performance, prefetch all flags for a user in a single API call. This is especially useful when your page checks multiple flags:
@@ -263,25 +302,6 @@ subflag_prefetch(admin_user)
263
302
  subflag_prefetch(current_user, context: { device: "mobile" })
264
303
  ```
265
304
 
266
- ### Cross-Request Caching
267
-
268
- By default, prefetched flags are only cached for the current request. To cache across multiple requests using `Rails.cache`, set a TTL:
269
-
270
- ```ruby
271
- # config/initializers/subflag.rb
272
- Subflag::Rails.configure do |config|
273
- config.api_key = Rails.application.credentials.subflag_api_key
274
- config.cache_ttl = 30.seconds # Cache flags in Rails.cache for 30 seconds
275
- end
276
- ```
277
-
278
- With `cache_ttl` set:
279
- - First request fetches from API and stores in `Rails.cache`
280
- - Subsequent requests (within TTL) read from `Rails.cache` — no API call
281
- - After TTL expires, next request fetches fresh data
282
-
283
- This significantly reduces API load for high-traffic applications. Choose a TTL that balances freshness with performance — 30 seconds is a good starting point.
284
-
285
305
  ### Direct API
286
306
 
287
307
  You can also use the module method directly:
@@ -296,13 +316,16 @@ Subflag::Rails.prefetch_flags(user: current_user)
296
316
 
297
317
  ```ruby
298
318
  Subflag::Rails.configure do |config|
299
- # API key (auto-loaded from credentials/ENV)
319
+ # Backend: :subflag (cloud), :active_record (self-hosted), :memory (testing)
320
+ config.backend = :subflag
321
+
322
+ # API key - required for :subflag backend
300
323
  config.api_key = "sdk-production-..."
301
324
 
302
325
  # API URL (default: https://api.subflag.com)
303
326
  config.api_url = "https://api.subflag.com"
304
327
 
305
- # Cross-request caching via Rails.cache (optional)
328
+ # Cross-request caching via Rails.cache (optional, :subflag backend only)
306
329
  # When set, prefetched flags are cached for this duration
307
330
  config.cache_ttl = 30.seconds
308
331
 
@@ -310,13 +333,217 @@ Subflag::Rails.configure do |config|
310
333
  config.logging_enabled = Rails.env.development?
311
334
  config.log_level = :debug # :debug, :info, :warn
312
335
 
313
- # User context
336
+ # User context - works with all backends
314
337
  config.user_context do |user|
315
338
  { targeting_key: user.id.to_s, plan: user.plan }
316
339
  end
317
340
  end
318
341
  ```
319
342
 
343
+ ### ActiveRecord Flag Model
344
+
345
+ When using `backend: :active_record`, flags are stored in the `subflag_flags` table:
346
+
347
+ | Column | Type | Description |
348
+ |--------|------|-------------|
349
+ | `key` | string | Flag name (lowercase, dashes, e.g., `new-checkout`) |
350
+ | `value` | text | Default value (what everyone gets) |
351
+ | `value_type` | string | Type: `boolean`, `string`, `integer`, `float`, `object` |
352
+ | `enabled` | boolean | Whether the flag is active (default: true) |
353
+ | `description` | text | Optional description |
354
+ | `targeting_rules` | json | Optional rules for showing different values to different users |
355
+
356
+ ```ruby
357
+ # Create flags
358
+ Subflag::Rails::Flag.create!(key: "max-projects", value: "100", value_type: "integer")
359
+
360
+ # Query flags
361
+ Subflag::Rails::Flag.enabled.find_each { |f| puts "#{f.key}: #{f.typed_value}" }
362
+
363
+ # Disable a flag
364
+ Subflag::Rails::Flag.find_by(key: "new-checkout")&.update!(enabled: false)
365
+ ```
366
+
367
+ ### Targeting Rules (ActiveRecord)
368
+
369
+ Show different flag values to different users based on their attributes. Perfect for internal testing before wider rollout.
370
+
371
+ > **Tip:** Use the [Admin UI](#admin-ui-activerecord) to manage targeting rules visually instead of editing JSON.
372
+
373
+ **First, configure user context:**
374
+
375
+ ```ruby
376
+ # config/initializers/subflag.rb
377
+ Subflag::Rails.configure do |config|
378
+ config.backend = :active_record
379
+
380
+ config.user_context do |user|
381
+ {
382
+ targeting_key: user.id.to_s,
383
+ email: user.email,
384
+ role: user.role, # e.g., "admin", "developer", "qa"
385
+ plan: user.plan # e.g., "free", "pro", "enterprise"
386
+ }
387
+ end
388
+ end
389
+ ```
390
+
391
+ **Create flags with targeting rules:**
392
+
393
+ ```ruby
394
+ # Internal team sees new feature, everyone else sees old
395
+ Subflag::Rails::Flag.create!(
396
+ key: "new-dashboard",
397
+ value: "false", # Default: everyone gets false
398
+ value_type: "boolean",
399
+ targeting_rules: [
400
+ {
401
+ "value" => "true", # Internal team gets true
402
+ "conditions" => {
403
+ "type" => "OR",
404
+ "conditions" => [
405
+ { "attribute" => "email", "operator" => "ENDS_WITH", "value" => "@yourcompany.com" },
406
+ { "attribute" => "role", "operator" => "IN", "value" => ["admin", "developer", "qa"] }
407
+ ]
408
+ }
409
+ }
410
+ ]
411
+ )
412
+ ```
413
+
414
+ **Progressive rollout with multiple rules (first match wins):**
415
+
416
+ ```ruby
417
+ Subflag::Rails::Flag.create!(
418
+ key: "max-projects",
419
+ value: "5", # Default: everyone gets 5
420
+ value_type: "integer",
421
+ targeting_rules: [
422
+ { "value" => "1000", "conditions" => { "type" => "AND", "conditions" => [{ "attribute" => "role", "operator" => "EQUALS", "value" => "admin" }] } },
423
+ { "value" => "100", "conditions" => { "type" => "AND", "conditions" => [{ "attribute" => "email", "operator" => "ENDS_WITH", "value" => "@yourcompany.com" }] } },
424
+ { "value" => "25", "conditions" => { "type" => "AND", "conditions" => [{ "attribute" => "plan", "operator" => "EQUALS", "value" => "pro" }] } }
425
+ ]
426
+ )
427
+ ```
428
+
429
+ **Supported operators:**
430
+
431
+ | Operator | Example | Description |
432
+ |----------|---------|-------------|
433
+ | `EQUALS` | `{ "attribute" => "role", "operator" => "EQUALS", "value" => "admin" }` | Exact match |
434
+ | `NOT_EQUALS` | `{ "attribute" => "env", "operator" => "NOT_EQUALS", "value" => "prod" }` | Not equal |
435
+ | `IN` | `{ "attribute" => "role", "operator" => "IN", "value" => ["admin", "qa"] }` | Value in list |
436
+ | `NOT_IN` | `{ "attribute" => "country", "operator" => "NOT_IN", "value" => ["RU", "CN"] }` | Value not in list |
437
+ | `CONTAINS` | `{ "attribute" => "email", "operator" => "CONTAINS", "value" => "test" }` | String contains |
438
+ | `NOT_CONTAINS` | `{ "attribute" => "email", "operator" => "NOT_CONTAINS", "value" => "spam" }` | String doesn't contain |
439
+ | `STARTS_WITH` | `{ "attribute" => "user_id", "operator" => "STARTS_WITH", "value" => "test-" }` | String prefix |
440
+ | `ENDS_WITH` | `{ "attribute" => "email", "operator" => "ENDS_WITH", "value" => "@company.com" }` | String suffix |
441
+ | `GREATER_THAN` | `{ "attribute" => "age", "operator" => "GREATER_THAN", "value" => 18 }` | Numeric greater than |
442
+ | `LESS_THAN` | `{ "attribute" => "items", "operator" => "LESS_THAN", "value" => 100 }` | Numeric less than |
443
+ | `GREATER_THAN_OR_EQUAL` | `{ "attribute" => "score", "operator" => "GREATER_THAN_OR_EQUAL", "value" => 80 }` | Numeric greater or equal |
444
+ | `LESS_THAN_OR_EQUAL` | `{ "attribute" => "score", "operator" => "LESS_THAN_OR_EQUAL", "value" => 50 }` | Numeric less or equal |
445
+ | `MATCHES` | `{ "attribute" => "email", "operator" => "MATCHES", "value" => ".*@company\\.com$" }` | Regex match |
446
+
447
+ **Combining conditions:**
448
+
449
+ ```ruby
450
+ # OR: any condition matches
451
+ {
452
+ "type" => "OR",
453
+ "conditions" => [
454
+ { "attribute" => "email", "operator" => "ENDS_WITH", "value" => "@company.com" },
455
+ { "attribute" => "role", "operator" => "EQUALS", "value" => "admin" }
456
+ ]
457
+ }
458
+
459
+ # AND: all conditions must match
460
+ {
461
+ "type" => "AND",
462
+ "conditions" => [
463
+ { "attribute" => "plan", "operator" => "EQUALS", "value" => "enterprise" },
464
+ { "attribute" => "country", "operator" => "IN", "value" => ["US", "CA"] }
465
+ ]
466
+ }
467
+ ```
468
+
469
+ **How evaluation works:**
470
+
471
+ 1. Flag disabled? → return code default
472
+ 2. For each rule (in order): if context matches → return rule's value
473
+ 3. No rules matched? → return flag's default `value`
474
+
475
+ This lets you progressively widen access by adding rules, without changing existing ones.
476
+
477
+ ### Admin UI (ActiveRecord)
478
+
479
+ Mount the admin UI to manage flags and targeting rules visually:
480
+
481
+ ```ruby
482
+ # config/routes.rb
483
+ Rails.application.routes.draw do
484
+ mount Subflag::Rails::Engine => "/subflag"
485
+ # ... your other routes
486
+ end
487
+ ```
488
+
489
+ The admin UI provides:
490
+ - List, create, edit, and delete flags
491
+ - Toggle flags enabled/disabled
492
+ - Visual targeting rule builder (no JSON editing)
493
+ - Test rules against sample contexts
494
+
495
+ **Secure the admin UI:**
496
+
497
+ ```ruby
498
+ # config/initializers/subflag.rb
499
+ Subflag::Rails.configure do |config|
500
+ config.backend = :active_record
501
+
502
+ # Require authentication for admin UI
503
+ config.admin_auth do
504
+ redirect_to main_app.root_path unless current_user&.admin?
505
+ end
506
+ end
507
+ ```
508
+
509
+ Visit `/subflag` in your browser to access the admin UI.
510
+
511
+ ## Testing
512
+
513
+ Stub flags in your tests:
514
+
515
+ ```ruby
516
+ # spec/rails_helper.rb (RSpec)
517
+ require "subflag/rails/test_helpers"
518
+ RSpec.configure do |config|
519
+ config.include Subflag::Rails::TestHelpers
520
+ end
521
+
522
+ # test/test_helper.rb (Minitest)
523
+ require "subflag/rails/test_helpers"
524
+ class ActiveSupport::TestCase
525
+ include Subflag::Rails::TestHelpers
526
+ end
527
+ ```
528
+
529
+ ```ruby
530
+ # In your specs/tests
531
+ it "shows new checkout when enabled" do
532
+ stub_subflag(:new_checkout, true)
533
+ stub_subflag(:max_projects, 100)
534
+
535
+ visit checkout_path
536
+ expect(page).to have_content("New Checkout")
537
+ end
538
+
539
+ # Stub multiple at once
540
+ stub_subflags(
541
+ new_checkout: true,
542
+ max_projects: 100,
543
+ headline: "Welcome!"
544
+ )
545
+ ```
546
+
320
547
  ## Documentation
321
548
 
322
549
  - [Subflag Docs](https://docs.subflag.com)
@@ -7,49 +7,121 @@ module Subflag
7
7
  # Generator for setting up Subflag in a Rails application
8
8
  #
9
9
  # Usage:
10
- # rails generate subflag:install
10
+ # rails generate subflag:install # Default: Subflag Cloud
11
+ # rails generate subflag:install --backend=subflag # Explicit: Subflag Cloud
12
+ # rails generate subflag:install --backend=active_record # Self-hosted DB
13
+ # rails generate subflag:install --backend=memory # Testing only
11
14
  #
12
15
  class InstallGenerator < ::Rails::Generators::Base
16
+ include ::Rails::Generators::Migration if defined?(::Rails::Generators::Migration)
17
+
13
18
  source_root File.expand_path("templates", __dir__)
14
19
 
15
- desc "Creates a Subflag initializer and provides setup instructions"
20
+ desc "Creates a Subflag initializer and optionally a migration for ActiveRecord backend"
21
+
22
+ class_option :backend, type: :string, default: "subflag",
23
+ desc: "Backend to use: subflag (cloud), active_record (self-hosted), or memory (testing)"
24
+
25
+ def self.next_migration_number(dirname)
26
+ if defined?(::ActiveRecord::Generators::Base)
27
+ ::ActiveRecord::Generators::Base.next_migration_number(dirname)
28
+ else
29
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
30
+ end
31
+ end
32
+
33
+ # Helper for templates to detect PostgreSQL adapter
34
+ def postgresql?
35
+ return false unless defined?(::ActiveRecord::Base)
36
+
37
+ adapter = ::ActiveRecord::Base.connection_db_config.adapter.to_s rescue nil
38
+ adapter&.include?("postgresql") || adapter&.include?("postgis")
39
+ end
16
40
 
17
41
  def create_initializer
18
- template "initializer.rb", "config/initializers/subflag.rb"
42
+ template "initializer.rb.tt", "config/initializers/subflag.rb"
43
+ end
44
+
45
+ def create_flags_migration
46
+ return unless options[:backend] == "active_record"
47
+
48
+ migration_template "create_subflag_flags.rb.tt",
49
+ "db/migrate/create_subflag_flags.rb"
19
50
  end
20
51
 
21
52
  def show_instructions
22
53
  say ""
23
- say "Subflag installed!", :green
24
- say ""
25
- say "Next steps:"
26
- say ""
27
- say "1. Add your API key to Rails credentials:"
28
- say " $ rails credentials:edit"
29
- say ""
30
- say " subflag:"
31
- say " api_key: sdk-production-your-key-here"
32
- say ""
33
- say " Or set SUBFLAG_API_KEY environment variable."
34
- say ""
35
- say "2. Configure user context in config/initializers/subflag.rb"
36
- say ""
37
- say "3. Use flags in your code:"
38
- say ""
39
- say " # Controller (auto-scoped to current_user)"
40
- say " if subflag_enabled?(:new_checkout)"
41
- say " # ..."
42
- say " end"
43
- say ""
44
- say " max = subflag_value(:max_projects, default: 3)"
45
- say ""
46
- say " # View"
47
- say " <% if subflag_enabled?(:new_checkout) %>"
48
- say " <%= render 'new_checkout' %>"
49
- say " <% end %>"
50
- say ""
51
- say "Docs: https://docs.subflag.com/rails"
52
- say ""
54
+
55
+ case options[:backend]
56
+ when "active_record"
57
+ say "Subflag installed with ActiveRecord backend!", :green
58
+ say ""
59
+ say "Next steps:"
60
+ say ""
61
+ say "1. Run the migration:"
62
+ say " $ rails db:migrate"
63
+ say ""
64
+ say "2. Create your first flag:"
65
+ say ""
66
+ say " Subflag::Rails::Flag.create!("
67
+ say " key: 'new-checkout',"
68
+ say " value: 'true',"
69
+ say " value_type: 'boolean'"
70
+ say " )"
71
+ say ""
72
+ say "3. Use flags in your code:"
73
+ say ""
74
+ say " if subflag_enabled?(:new_checkout)"
75
+ say " # ..."
76
+ say " end"
77
+ say ""
78
+ say "When you're ready for a dashboard, environments, and user targeting:"
79
+ say " https://subflag.com", :yellow
80
+ say ""
81
+
82
+ when "memory"
83
+ say "Subflag installed with Memory backend!", :green
84
+ say ""
85
+ say "Note: Memory backend is for testing only. Flags reset on restart."
86
+ say ""
87
+ say "Set flags in your tests or initializer:"
88
+ say ""
89
+ say " Subflag::Rails.provider.set(:new_checkout, true)"
90
+ say " Subflag::Rails.provider.set(:max_projects, 100)"
91
+ say ""
92
+ say "Use flags:"
93
+ say ""
94
+ say " subflag_enabled?(:new_checkout) # => true"
95
+ say " subflag_value(:max_projects, default: 3) # => 100"
96
+ say ""
97
+
98
+ else # subflag (cloud)
99
+ say "Subflag installed!", :green
100
+ say ""
101
+ say "Next steps:"
102
+ say ""
103
+ say "1. Add your API key to Rails credentials:"
104
+ say " $ rails credentials:edit"
105
+ say ""
106
+ say " subflag:"
107
+ say " api_key: sdk-production-your-key-here"
108
+ say ""
109
+ say " Or set SUBFLAG_API_KEY environment variable."
110
+ say ""
111
+ say "2. Configure user context in config/initializers/subflag.rb"
112
+ say ""
113
+ say "3. Use flags in your code:"
114
+ say ""
115
+ say " # Controller (auto-scoped to current_user)"
116
+ say " if subflag_enabled?(:new_checkout)"
117
+ say " # ..."
118
+ say " end"
119
+ say ""
120
+ say " max = subflag_value(:max_projects, default: 3)"
121
+ say ""
122
+ say "Docs: https://docs.subflag.com/rails"
123
+ say ""
124
+ end
53
125
  end
54
126
  end
55
127
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateSubflagFlags < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
4
+ def change
5
+ create_table :subflag_flags do |t|
6
+ t.string :key, null: false
7
+ t.string :value_type, null: false, default: "boolean"
8
+ t.text :value, null: false
9
+ t.boolean :enabled, null: false, default: true
10
+ t.text :description
11
+
12
+ # Targeting rules for showing different values to different users
13
+ # Stores an array of { value, conditions } rules as JSON
14
+ # First matching rule wins; falls back to `value` if no match
15
+ t.<%= postgresql? ? 'jsonb' : 'json' %> :targeting_rules
16
+
17
+ t.timestamps
18
+ end
19
+
20
+ add_index :subflag_flags, :key, unique: true
21
+ end
22
+ end
@@ -2,17 +2,31 @@
2
2
 
3
3
  # Subflag configuration
4
4
  #
5
+ <% if options[:backend] == "subflag" -%>
5
6
  # API key is automatically loaded from:
6
7
  # 1. Rails credentials (subflag.api_key or subflag_api_key)
7
8
  # 2. SUBFLAG_API_KEY environment variable
9
+ <% elsif options[:backend] == "active_record" -%>
10
+ # Using ActiveRecord backend - flags stored in subflag_flags table
11
+ <% else -%>
12
+ # Using Memory backend - flags stored in memory (testing only)
13
+ <% end -%>
8
14
 
9
15
  Subflag::Rails.configure do |config|
10
- # Uncomment to manually set API key
11
- # config.api_key = Rails.application.credentials.dig(:subflag, :api_key)
16
+ # Backend: :subflag (cloud), :active_record (self-hosted), :memory (testing)
17
+ config.backend = :<%= options[:backend] %>
18
+ <% if options[:backend] == "subflag" -%>
19
+
20
+ # Your Subflag API key
21
+ # Get one at https://subflag.com
22
+ config.api_key = ENV["SUBFLAG_API_KEY"] || Rails.application.credentials.dig(:subflag, :api_key)
23
+ <% end -%>
24
+ <% if options[:backend] != "memory" -%>
12
25
 
13
26
  # Enable logging in development
14
27
  config.logging_enabled = Rails.env.development?
15
28
  config.log_level = :debug
29
+ <% end -%>
16
30
 
17
31
  # Configure user context for targeting
18
32
  # This enables per-user flag values (e.g., different limits by plan)