magick-feature-flags 1.3.0 → 1.3.1

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: 046dc5815e415705073eff7b3619c90bfd09a4af59b74d4116a3b2c206d5572e
4
- data.tar.gz: 97bd2be3b303a73fa464c87ebbc6456098003601d236072c10e9a483e52c50b2
3
+ metadata.gz: a34f03026e8631bc2d29c0935cb98e472e7ed387fc4628edc357776423bfc2c5
4
+ data.tar.gz: d87af21843efb22d5dba60d342f3beb5fd807eb42f1b2948c82ae8dc3d6b67a2
5
5
  SHA512:
6
- metadata.gz: 85d499e307729dfd4742ec0535f5edde63fb5dc2706ee8b1189dd63f8ed7f9c8c6d7a784d9a7f59e988f394fb92f41acc84c176a9544fb417c892586dd6444f2
7
- data.tar.gz: 589515a83297e0d3ce02332afa309741533f1cf91fa556044b9a1f4f63a2d327248eba80c1727baa08ed5ed476db401530ddd21645eab3d7028624f97f6ee204
6
+ metadata.gz: '0296397245455dbecd448a2dab448b2209579f4c9c39a1e93b424e8f6982fe8a3f498140ac7af5f1127c70fc2cdaa3378194135afe2e55fe9254646a4c761e64'
7
+ data.tar.gz: 43e1ce6dcf51815f83ccf4440845d415f8d351e6a6038c8f7bcd0a294dfc0def6531a04a118119d66e71a64d7cfcc1607273eda0c181dfdf951c9ee61d4cfae7
data/README.md CHANGED
@@ -467,6 +467,32 @@ class CheckoutController < ApplicationController
467
467
  end
468
468
  ```
469
469
 
470
+ **Experiments without a user (anonymous visitors):**
471
+
472
+ For flows where there's no authenticated user yet (e.g., registration, landing pages), use any stable identifier as `user_id` — a session ID or a tracking cookie:
473
+
474
+ ```ruby
475
+ class RegistrationController < ApplicationController
476
+ def new
477
+ cookies[:visitor_id] ||= SecureRandom.uuid
478
+ @variant = Magick.variant(:registration_flow, user_id: cookies[:visitor_id])
479
+ end
480
+ end
481
+ ```
482
+
483
+ The hashing just needs a consistent string. As long as the same visitor sends the same identifier, they get the same variant every time.
484
+
485
+ **Safe to call on non-existent experiments:**
486
+
487
+ ```ruby
488
+ Magick.variant(:nonexistent, user_id: 123) # => nil
489
+ Magick.variant_value(:nonexistent, user_id: 123) # => nil
490
+ ```
491
+
492
+ **Important — changing weights may shift users:**
493
+
494
+ You can change variant weights at any time via the Admin UI or code, and changes take effect immediately across all adapters. However, changing weights alters the bucket boundaries, which means some users may be reassigned to a different variant after the update. Magick does not persist individual user-to-variant assignments — assignment is computed on the fly from the hash. If your experiment requires that users never shift variants mid-experiment, you should persist the assignment externally (e.g., store `user_id → variant` in a database table on first exposure).
495
+
470
496
  **How it works:**
471
497
  - Variants are assigned using a deterministic MD5 hash of `feature_name + user_id`
472
498
  - The same user always gets the same variant across sessions and requests
@@ -80,8 +80,11 @@ module Magick
80
80
  end
81
81
 
82
82
  def enable
83
- @feature.enable
84
- redirect_to magick_admin_ui.features_path, notice: 'Feature enabled'
83
+ if @feature.enable
84
+ redirect_to magick_admin_ui.features_path, notice: 'Feature enabled'
85
+ else
86
+ redirect_to magick_admin_ui.feature_path(@feature.name), alert: 'Cannot enable feature — its dependencies must be enabled first'
87
+ end
85
88
  end
86
89
 
87
90
  def disable
@@ -516,20 +516,16 @@ module Magick
516
516
  end
517
517
 
518
518
  def enable(user_id: nil)
519
- # Check if this feature is a dependency of any disabled features
520
- # If a main feature that depends on this feature is disabled, prevent enabling this dependency
521
- # Dependencies cannot be enabled until the main feature is enabled
522
- dependent_features = find_dependent_features
523
- disabled_dependents = dependent_features.select do |dep_feature_name|
524
- dep_feature = Magick.features[dep_feature_name.to_s] || Magick[dep_feature_name]
525
- # Check if the dependent feature (main feature) is disabled
526
- dep_feature && !dep_feature.enabled?
527
- end
519
+ # Check that all of this feature's own dependencies are enabled
520
+ # e.g. if checkout depends on payments, checkout can't be enabled until payments is
521
+ deps = @dependencies || []
522
+ unless deps.empty?
523
+ disabled_deps = deps.select do |dep_name|
524
+ dep_feature = Magick.features[dep_name.to_s] || Magick[dep_name]
525
+ dep_feature && !dep_feature.enabled?
526
+ end
528
527
 
529
- unless disabled_dependents.empty?
530
- # Return false if any main feature that depends on this feature is disabled
531
- # This prevents enabling a dependency when the main feature is disabled
532
- return false
528
+ return false unless disabled_deps.empty?
533
529
  end
534
530
 
535
531
  # Clear all targeting to enable globally
@@ -988,13 +984,12 @@ module Magick
988
984
  end
989
985
 
990
986
  def disable_dependent_features(user_id: nil)
991
- # Cascade-disable this feature's own dependencies (downstream sub-features).
992
- # e.g. if A depends on B, disabling A also disables B.
993
- # But disabling B does NOT cascade up to A.
994
- deps = @dependencies || []
995
- return if deps.empty?
987
+ # Cascade-disable features that depend ON this feature.
988
+ # e.g. if checkout depends on payments, disabling payments also disables checkout.
989
+ dependents = find_dependent_features
990
+ return if dependents.empty?
996
991
 
997
- deps.each do |dep_name|
992
+ dependents.each do |dep_name|
998
993
  dep_feature = Magick.features[dep_name.to_s] || Magick[dep_name]
999
994
  next unless dep_feature
1000
995
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Magick
4
- VERSION = '1.3.0'
4
+ VERSION = '1.3.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: magick-feature-flags
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Lobanov