flipper 0.28.3 → 1.4.2

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.
Files changed (204) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/ci.yml +56 -10
  4. data/.github/workflows/examples.yml +54 -11
  5. data/.github/workflows/release.yml +54 -0
  6. data/.superset/config.json +4 -0
  7. data/CLAUDE.md +93 -0
  8. data/Changelog.md +1 -559
  9. data/Gemfile +17 -8
  10. data/README.md +34 -29
  11. data/Rakefile +2 -2
  12. data/benchmark/typecast_ips.rb +8 -0
  13. data/docs/images/banner.jpg +0 -0
  14. data/docs/images/flipper_cloud.png +0 -0
  15. data/examples/cloud/app.ru +12 -0
  16. data/examples/cloud/backoff_policy.rb +13 -0
  17. data/examples/cloud/basic.rb +22 -0
  18. data/examples/cloud/cloud_setup.rb +20 -0
  19. data/examples/cloud/forked.rb +36 -0
  20. data/examples/cloud/import.rb +17 -0
  21. data/examples/cloud/poll_interval/README.md +111 -0
  22. data/examples/cloud/poll_interval/client.rb +108 -0
  23. data/examples/cloud/poll_interval/server.rb +98 -0
  24. data/examples/cloud/threaded.rb +33 -0
  25. data/examples/dsl.rb +0 -14
  26. data/examples/expressions.rb +237 -0
  27. data/examples/strict.rb +18 -0
  28. data/exe/flipper +5 -0
  29. data/flipper-cloud.gemspec +19 -0
  30. data/flipper.gemspec +8 -4
  31. data/lib/flipper/actor.rb +6 -3
  32. data/lib/flipper/adapter.rb +27 -1
  33. data/lib/flipper/adapter_builder.rb +44 -0
  34. data/lib/flipper/adapters/actor_limit.rb +54 -0
  35. data/lib/flipper/adapters/cache_base.rb +161 -0
  36. data/lib/flipper/adapters/dual_write.rb +7 -5
  37. data/lib/flipper/adapters/failover.rb +9 -7
  38. data/lib/flipper/adapters/failsafe.rb +2 -6
  39. data/lib/flipper/adapters/http/client.rb +53 -14
  40. data/lib/flipper/adapters/http/error.rb +2 -2
  41. data/lib/flipper/adapters/http.rb +56 -16
  42. data/lib/flipper/adapters/instrumented.rb +2 -6
  43. data/lib/flipper/adapters/memoizable.rb +17 -22
  44. data/lib/flipper/adapters/memory.rb +5 -7
  45. data/lib/flipper/adapters/operation_logger.rb +18 -92
  46. data/lib/flipper/adapters/poll.rb +16 -3
  47. data/lib/flipper/adapters/pstore.rb +18 -12
  48. data/lib/flipper/adapters/read_only.rb +8 -41
  49. data/lib/flipper/adapters/strict.rb +75 -0
  50. data/lib/flipper/adapters/sync/feature_synchronizer.rb +14 -1
  51. data/lib/flipper/adapters/sync/synchronizer.rb +13 -5
  52. data/lib/flipper/adapters/sync.rb +7 -7
  53. data/lib/flipper/adapters/wrapper.rb +54 -0
  54. data/lib/flipper/cli.rb +314 -0
  55. data/lib/flipper/cloud/configuration.rb +267 -0
  56. data/lib/flipper/cloud/dsl.rb +27 -0
  57. data/lib/flipper/cloud/message_verifier.rb +95 -0
  58. data/lib/flipper/cloud/middleware.rb +63 -0
  59. data/lib/flipper/cloud/migrate.rb +71 -0
  60. data/lib/flipper/cloud/routes.rb +14 -0
  61. data/lib/flipper/cloud/telemetry/backoff_policy.rb +96 -0
  62. data/lib/flipper/cloud/telemetry/instrumenter.rb +22 -0
  63. data/lib/flipper/cloud/telemetry/metric.rb +39 -0
  64. data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
  65. data/lib/flipper/cloud/telemetry/submitter.rb +100 -0
  66. data/lib/flipper/cloud/telemetry.rb +191 -0
  67. data/lib/flipper/cloud.rb +54 -0
  68. data/lib/flipper/configuration.rb +25 -4
  69. data/lib/flipper/dsl.rb +48 -43
  70. data/lib/flipper/engine.rb +102 -0
  71. data/lib/flipper/export.rb +0 -2
  72. data/lib/flipper/exporters/json/export.rb +1 -1
  73. data/lib/flipper/exporters/json/v1.rb +1 -1
  74. data/lib/flipper/expression/builder.rb +73 -0
  75. data/lib/flipper/expression/constant.rb +25 -0
  76. data/lib/flipper/expression.rb +71 -0
  77. data/lib/flipper/expressions/all.rb +9 -0
  78. data/lib/flipper/expressions/any.rb +9 -0
  79. data/lib/flipper/expressions/boolean.rb +9 -0
  80. data/lib/flipper/expressions/comparable.rb +13 -0
  81. data/lib/flipper/expressions/equal.rb +9 -0
  82. data/lib/flipper/expressions/feature_enabled.rb +34 -0
  83. data/lib/flipper/expressions/greater_than.rb +9 -0
  84. data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
  85. data/lib/flipper/expressions/less_than.rb +9 -0
  86. data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
  87. data/lib/flipper/expressions/not_equal.rb +9 -0
  88. data/lib/flipper/expressions/now.rb +9 -0
  89. data/lib/flipper/expressions/number.rb +9 -0
  90. data/lib/flipper/expressions/percentage.rb +9 -0
  91. data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
  92. data/lib/flipper/expressions/property.rb +9 -0
  93. data/lib/flipper/expressions/random.rb +9 -0
  94. data/lib/flipper/expressions/string.rb +9 -0
  95. data/lib/flipper/expressions/time.rb +16 -0
  96. data/lib/flipper/feature.rb +63 -1
  97. data/lib/flipper/gate.rb +2 -1
  98. data/lib/flipper/gate_values.rb +5 -2
  99. data/lib/flipper/gates/expression.rb +75 -0
  100. data/lib/flipper/instrumentation/log_subscriber.rb +13 -5
  101. data/lib/flipper/instrumentation/statsd.rb +4 -2
  102. data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
  103. data/lib/flipper/instrumentation/subscriber.rb +0 -4
  104. data/lib/flipper/metadata.rb +8 -1
  105. data/lib/flipper/middleware/memoizer.rb +30 -14
  106. data/lib/flipper/model/active_record.rb +23 -0
  107. data/lib/flipper/poller.rb +54 -12
  108. data/lib/flipper/serializers/gzip.rb +22 -0
  109. data/lib/flipper/serializers/json.rb +17 -0
  110. data/lib/flipper/spec/shared_adapter_specs.rb +82 -63
  111. data/lib/flipper/test/shared_adapter_test.rb +77 -58
  112. data/lib/flipper/test_help.rb +43 -0
  113. data/lib/flipper/typecast.rb +37 -9
  114. data/lib/flipper/types/percentage.rb +1 -1
  115. data/lib/flipper/version.rb +11 -1
  116. data/lib/flipper.rb +61 -8
  117. data/lib/generators/flipper/setup_generator.rb +68 -0
  118. data/lib/generators/flipper/templates/initializer.rb +45 -0
  119. data/lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb +22 -0
  120. data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb +18 -0
  121. data/lib/generators/flipper/update_generator.rb +35 -0
  122. data/package-lock.json +41 -0
  123. data/package.json +10 -0
  124. data/spec/fixtures/environment.rb +1 -0
  125. data/spec/flipper/adapter_builder_spec.rb +72 -0
  126. data/spec/flipper/adapter_spec.rb +21 -0
  127. data/spec/flipper/adapters/actor_limit_spec.rb +75 -0
  128. data/spec/flipper/adapters/dual_write_spec.rb +15 -2
  129. data/spec/flipper/adapters/failover_spec.rb +12 -0
  130. data/spec/flipper/adapters/http/client_spec.rb +61 -0
  131. data/spec/flipper/adapters/http_spec.rb +375 -74
  132. data/spec/flipper/adapters/instrumented_spec.rb +1 -1
  133. data/spec/flipper/adapters/memoizable_spec.rb +21 -21
  134. data/spec/flipper/adapters/operation_logger_spec.rb +2 -2
  135. data/spec/flipper/adapters/poll_spec.rb +41 -0
  136. data/spec/flipper/adapters/read_only_spec.rb +32 -17
  137. data/spec/flipper/adapters/strict_spec.rb +122 -0
  138. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +39 -0
  139. data/spec/flipper/adapters/sync/synchronizer_spec.rb +87 -0
  140. data/spec/flipper/adapters/sync_spec.rb +13 -0
  141. data/spec/flipper/cli_spec.rb +217 -0
  142. data/spec/flipper/cloud/configuration_spec.rb +251 -0
  143. data/spec/flipper/cloud/dsl_spec.rb +82 -0
  144. data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
  145. data/spec/flipper/cloud/middleware_spec.rb +289 -0
  146. data/spec/flipper/cloud/migrate_spec.rb +160 -0
  147. data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +107 -0
  148. data/spec/flipper/cloud/telemetry/metric_spec.rb +87 -0
  149. data/spec/flipper/cloud/telemetry/metric_storage_spec.rb +58 -0
  150. data/spec/flipper/cloud/telemetry/submitter_spec.rb +145 -0
  151. data/spec/flipper/cloud/telemetry_spec.rb +208 -0
  152. data/spec/flipper/cloud_spec.rb +186 -0
  153. data/spec/flipper/configuration_spec.rb +17 -0
  154. data/spec/flipper/dsl_spec.rb +34 -73
  155. data/spec/flipper/engine_spec.rb +374 -0
  156. data/spec/flipper/exporters/json/v1_spec.rb +3 -3
  157. data/spec/flipper/expression/builder_spec.rb +248 -0
  158. data/spec/flipper/expression_spec.rb +188 -0
  159. data/spec/flipper/expressions/all_spec.rb +15 -0
  160. data/spec/flipper/expressions/any_spec.rb +15 -0
  161. data/spec/flipper/expressions/boolean_spec.rb +15 -0
  162. data/spec/flipper/expressions/equal_spec.rb +24 -0
  163. data/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +28 -0
  164. data/spec/flipper/expressions/greater_than_spec.rb +28 -0
  165. data/spec/flipper/expressions/less_than_or_equal_to_spec.rb +28 -0
  166. data/spec/flipper/expressions/less_than_spec.rb +32 -0
  167. data/spec/flipper/expressions/not_equal_spec.rb +15 -0
  168. data/spec/flipper/expressions/now_spec.rb +11 -0
  169. data/spec/flipper/expressions/number_spec.rb +21 -0
  170. data/spec/flipper/expressions/percentage_of_actors_spec.rb +20 -0
  171. data/spec/flipper/expressions/percentage_spec.rb +15 -0
  172. data/spec/flipper/expressions/property_spec.rb +13 -0
  173. data/spec/flipper/expressions/random_spec.rb +9 -0
  174. data/spec/flipper/expressions/string_spec.rb +11 -0
  175. data/spec/flipper/expressions/time_spec.rb +29 -0
  176. data/spec/flipper/feature_spec.rb +380 -10
  177. data/spec/flipper/gate_values_spec.rb +2 -2
  178. data/spec/flipper/gates/expression_spec.rb +190 -0
  179. data/spec/flipper/identifier_spec.rb +4 -5
  180. data/spec/flipper/instrumentation/log_subscriber_spec.rb +10 -2
  181. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +16 -2
  182. data/spec/flipper/middleware/memoizer_spec.rb +107 -7
  183. data/spec/flipper/model/active_record_spec.rb +72 -0
  184. data/spec/flipper/poller_spec.rb +347 -4
  185. data/spec/flipper/serializers/gzip_spec.rb +13 -0
  186. data/spec/flipper/serializers/json_spec.rb +13 -0
  187. data/spec/flipper/typecast_spec.rb +43 -7
  188. data/spec/flipper/types/actor_spec.rb +18 -1
  189. data/spec/flipper_integration_spec.rb +247 -16
  190. data/spec/flipper_spec.rb +92 -29
  191. data/spec/spec_helper.rb +24 -17
  192. data/spec/support/actor_names.yml +1 -0
  193. data/spec/support/fail_on_output.rb +8 -0
  194. data/spec/support/fake_backoff_policy.rb +15 -0
  195. data/spec/support/spec_helpers.rb +34 -8
  196. data/test/adapters/actor_limit_test.rb +20 -0
  197. data/test_rails/generators/flipper/setup_generator_test.rb +69 -0
  198. data/test_rails/generators/flipper/update_generator_test.rb +96 -0
  199. data/test_rails/helper.rb +22 -2
  200. data/test_rails/system/test_help_test.rb +52 -0
  201. metadata +139 -75
  202. data/.tool-versions +0 -1
  203. data/lib/flipper/railtie.rb +0 -47
  204. data/spec/flipper/railtie_spec.rb +0 -109
@@ -10,16 +10,29 @@ module Flipper
10
10
  # Deprecated
11
11
  Poller = ::Flipper::Poller
12
12
 
13
- # Public: The name of the adapter.
14
- attr_reader :name, :adapter, :poller
13
+ attr_reader :adapter, :poller
15
14
 
16
15
  def_delegators :synced_adapter, :features, :get, :get_multi, :get_all, :add, :remove, :clear, :enable, :disable
17
16
 
18
17
  def initialize(poller, adapter)
19
- @name = :poll
20
18
  @adapter = adapter
21
19
  @poller = poller
22
20
  @last_synced_at = 0
21
+
22
+ # If the adapter is empty, we need to sync before starting the poller.
23
+ # Yes, this will block the main thread, but that's better than thinking
24
+ # nothing is enabled.
25
+ if adapter.features.empty?
26
+ begin
27
+ @poller.sync
28
+ rescue
29
+ # TODO: Warn here that it's possible that no data has been synced
30
+ # and flags are being evaluated without flag data being present
31
+ # until a sync completes. We rescue to avoid flipper being down
32
+ # causing your processes to crash.
33
+ end
34
+ end
35
+
23
36
  @poller.start
24
37
  end
25
38
 
@@ -1,3 +1,4 @@
1
+ require 'json'
1
2
  require 'pstore'
2
3
  require 'set'
3
4
  require 'flipper'
@@ -9,19 +10,14 @@ module Flipper
9
10
  class PStore
10
11
  include ::Flipper::Adapter
11
12
 
12
- FeaturesKey = :flipper_features
13
-
14
- # Public: The name of the adapter.
15
- attr_reader :name
16
-
17
13
  # Public: The path to where the file is stored.
18
14
  attr_reader :path
19
15
 
20
16
  # Public
21
17
  def initialize(path = 'flipper.pstore', thread_safe = true)
22
- @name = :pstore
23
18
  @path = path
24
19
  @store = ::PStore.new(path, thread_safe)
20
+ @features_key = :flipper_features
25
21
  end
26
22
 
27
23
  # Public: The set of known features.
@@ -34,7 +30,7 @@ module Flipper
34
30
  # Public: Adds a feature to the set of known features.
35
31
  def add(feature)
36
32
  @store.transaction do
37
- set_add FeaturesKey, feature.key
33
+ set_add @features_key, feature.key
38
34
  end
39
35
  true
40
36
  end
@@ -43,7 +39,7 @@ module Flipper
43
39
  # all the values for the feature.
44
40
  def remove(feature)
45
41
  @store.transaction do
46
- set_delete FeaturesKey, feature.key
42
+ set_delete @features_key, feature.key
47
43
  clear_gates(feature)
48
44
  end
49
45
  true
@@ -70,7 +66,7 @@ module Flipper
70
66
  end
71
67
  end
72
68
 
73
- def get_all
69
+ def get_all(**kwargs)
74
70
  @store.transaction do
75
71
  features = read_feature_keys.map { |key| Flipper::Feature.new(key, self) }
76
72
  read_many_features(features)
@@ -88,6 +84,8 @@ module Flipper
88
84
  write key(feature, gate), thing.value.to_s
89
85
  when :set
90
86
  set_add key(feature, gate), thing.value.to_s
87
+ when :json
88
+ write key(feature, gate), Typecast.to_json(thing.value)
91
89
  else
92
90
  raise "#{gate} is not supported by this adapter yet"
93
91
  end
@@ -109,6 +107,10 @@ module Flipper
109
107
  @store.transaction do
110
108
  set_delete key(feature, gate), thing.value.to_s
111
109
  end
110
+ when :json
111
+ @store.transaction do
112
+ delete key(feature, gate)
113
+ end
112
114
  else
113
115
  raise "#{gate} is not supported by this adapter yet"
114
116
  end
@@ -135,7 +137,7 @@ module Flipper
135
137
  end
136
138
 
137
139
  def read_feature_keys
138
- set_members FeaturesKey
140
+ set_members @features_key
139
141
  end
140
142
 
141
143
  def read_many_features(features)
@@ -150,12 +152,16 @@ module Flipper
150
152
  result = {}
151
153
 
152
154
  feature.gates.each do |gate|
155
+ key = key(feature, gate)
153
156
  result[gate.key] =
154
157
  case gate.data_type
155
158
  when :boolean, :integer
156
- read key(feature, gate)
159
+ read key
157
160
  when :set
158
- set_members key(feature, gate)
161
+ set_members key
162
+ when :json
163
+ value = read(key)
164
+ Typecast.from_json(value)
159
165
  else
160
166
  raise "#{gate} is not supported by this adapter yet"
161
167
  end
@@ -3,8 +3,8 @@ require 'flipper'
3
3
  module Flipper
4
4
  module Adapters
5
5
  # Public: Adapter that wraps another adapter and raises for any writes.
6
- class ReadOnly
7
- include ::Flipper::Adapter
6
+ class ReadOnly < Wrapper
7
+ WRITE_METHODS = %i[add remove clear enable disable]
8
8
 
9
9
  class WriteAttempted < Error
10
10
  def initialize(message = nil)
@@ -12,49 +12,16 @@ module Flipper
12
12
  end
13
13
  end
14
14
 
15
- # Internal: The name of the adapter.
16
- attr_reader :name
17
-
18
- # Public
19
- def initialize(adapter)
20
- @adapter = adapter
21
- @name = :read_only
22
- end
23
-
24
- def features
25
- @adapter.features
26
- end
27
-
28
- def get(feature)
29
- @adapter.get(feature)
30
- end
31
-
32
- def get_multi(features)
33
- @adapter.get_multi(features)
15
+ def read_only?
16
+ true
34
17
  end
35
18
 
36
- def get_all
37
- @adapter.get_all
38
- end
19
+ private
39
20
 
40
- def add(_feature)
41
- raise WriteAttempted
42
- end
43
-
44
- def remove(_feature)
45
- raise WriteAttempted
46
- end
47
-
48
- def clear(_feature)
49
- raise WriteAttempted
50
- end
51
-
52
- def enable(_feature, _gate, _thing)
53
- raise WriteAttempted
54
- end
21
+ def wrap(method, *args, **kwargs)
22
+ raise WriteAttempted if WRITE_METHODS.include?(method)
55
23
 
56
- def disable(_feature, _gate, _thing)
57
- raise WriteAttempted
24
+ yield
58
25
  end
59
26
  end
60
27
  end
@@ -0,0 +1,75 @@
1
+ module Flipper
2
+ module Adapters
3
+ # An adapter that ensures a feature exists before checking it.
4
+ class Strict < Wrapper
5
+ attr_reader :handler
6
+
7
+ class NotFound < ::Flipper::Error
8
+ def initialize(name)
9
+ super "Could not find feature #{name.inspect}. Call `Flipper.add(#{name.inspect})` to create it."
10
+ end
11
+ end
12
+
13
+ class << self
14
+ # Returns whether sync mode is enabled for the current thread.
15
+ # When sync mode is enabled, strict checks are not enforced,
16
+ # allowing sync operations to add features and bring local state
17
+ # in line with remote state.
18
+ def sync_mode
19
+ Thread.current[:flipper_strict_sync_mode]
20
+ end
21
+
22
+ def sync_mode=(value)
23
+ Thread.current[:flipper_strict_sync_mode] = value
24
+ end
25
+
26
+ # Executes a block with sync mode enabled. Strict checks will
27
+ # not be enforced within the block.
28
+ def with_sync_mode
29
+ old_value = sync_mode
30
+ self.sync_mode = true
31
+ yield
32
+ ensure
33
+ self.sync_mode = old_value
34
+ end
35
+ end
36
+
37
+ def initialize(adapter, handler = nil, &block)
38
+ super(adapter)
39
+ @handler = block || handler
40
+ end
41
+
42
+ def add(feature)
43
+ assert_feature_exists(feature) unless self.class.sync_mode
44
+ super
45
+ end
46
+
47
+ def get(feature)
48
+ assert_feature_exists(feature)
49
+ super
50
+ end
51
+
52
+ def get_multi(features)
53
+ features.each { |feature| assert_feature_exists(feature) }
54
+ super
55
+ end
56
+
57
+ private
58
+
59
+ def assert_feature_exists(feature)
60
+ return if self.class.sync_mode
61
+ return if @adapter.features.include?(feature.key)
62
+
63
+ case handler
64
+ when Proc then handler.call(feature)
65
+ when :warn then warn NotFound.new(feature.key).message
66
+ when :noop, false, nil
67
+ # noop
68
+ else # truthy or :raise
69
+ raise NotFound.new(feature.key)
70
+ end
71
+ end
72
+
73
+ end
74
+ end
75
+ end
@@ -9,6 +9,7 @@ module Flipper
9
9
  class FeatureSynchronizer
10
10
  extend Forwardable
11
11
 
12
+ def_delegator :@local_gate_values, :expression, :local_expression
12
13
  def_delegator :@local_gate_values, :boolean, :local_boolean
13
14
  def_delegator :@local_gate_values, :actors, :local_actors
14
15
  def_delegator :@local_gate_values, :groups, :local_groups
@@ -17,6 +18,7 @@ module Flipper
17
18
  def_delegator :@local_gate_values, :percentage_of_time,
18
19
  :local_percentage_of_time
19
20
 
21
+ def_delegator :@remote_gate_values, :expression, :remote_expression
20
22
  def_delegator :@remote_gate_values, :boolean, :remote_boolean
21
23
  def_delegator :@remote_gate_values, :actors, :remote_actors
22
24
  def_delegator :@remote_gate_values, :groups, :remote_groups
@@ -40,8 +42,9 @@ module Flipper
40
42
  @feature.enable
41
43
  else
42
44
  @feature.disable if local_boolean_enabled?
43
- sync_actors
44
45
  sync_groups
46
+ sync_actors
47
+ sync_expression
45
48
  sync_percentage_of_actors
46
49
  sync_percentage_of_time
47
50
  end
@@ -49,6 +52,16 @@ module Flipper
49
52
 
50
53
  private
51
54
 
55
+ def sync_expression
56
+ return if local_expression == remote_expression
57
+
58
+ if remote_expression.nil?
59
+ @feature.disable_expression
60
+ else
61
+ @feature.enable_expression remote_expression
62
+ end
63
+ end
64
+
52
65
  def sync_actors
53
66
  remote_actors_added = remote_actors - local_actors
54
67
  remote_actors_added.each do |flipper_id|
@@ -1,5 +1,7 @@
1
1
  require "flipper/feature"
2
2
  require "flipper/gate_values"
3
+ require "flipper/adapters/actor_limit"
4
+ require "flipper/adapters/strict"
3
5
  require "flipper/adapters/sync/feature_synchronizer"
4
6
 
5
7
  module Flipper
@@ -16,27 +18,33 @@ module Flipper
16
18
  # options - The Hash of options.
17
19
  # :instrumenter - The instrumenter used to instrument.
18
20
  # :raise - Should errors be raised (default: true).
21
+ # :cache_bust - Should cache busting be used for remote get_all (default: false).
19
22
  def initialize(local, remote, options = {})
20
23
  @local = local
21
24
  @remote = remote
22
25
  @instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
23
26
  @raise = options.fetch(:raise, true)
27
+ @cache_bust = options.fetch(:cache_bust, false)
24
28
  end
25
29
 
26
30
  # Public: Forces a sync.
27
31
  def call
28
- @instrumenter.instrument("synchronizer_call.flipper") { sync }
32
+ @instrumenter.instrument("synchronizer_call.flipper") do
33
+ Flipper::Adapters::Strict.with_sync_mode do
34
+ Flipper::Adapters::ActorLimit.with_sync_mode { sync }
35
+ end
36
+ end
29
37
  end
30
38
 
31
39
  private
32
40
 
33
41
  def sync
34
42
  local_get_all = @local.get_all
35
- remote_get_all = @remote.get_all
43
+ remote_get_all = @remote.get_all(cache_bust: @cache_bust)
36
44
 
37
45
  # Sync all the gate values.
38
46
  remote_get_all.each do |feature_key, remote_gates_hash|
39
- feature = Feature.new(feature_key, @local)
47
+ feature = Feature.new(feature_key, @local, instrumenter: @instrumenter)
40
48
  # Check if feature_key is in hash before accessing to prevent unintended hash modification
41
49
  local_gates_hash = local_get_all.key?(feature_key) ? local_get_all[feature_key] : @local.default_config
42
50
  local_gate_values = GateValues.new(local_gates_hash)
@@ -46,11 +54,11 @@ module Flipper
46
54
 
47
55
  # Add features that are missing in local and present in remote.
48
56
  features_to_add = remote_get_all.keys - local_get_all.keys
49
- features_to_add.each { |key| Feature.new(key, @local).add }
57
+ features_to_add.each { |key| Feature.new(key, @local, instrumenter: @instrumenter).add }
50
58
 
51
59
  # Remove features that are present in local and missing in remote.
52
60
  features_to_remove = local_get_all.keys - remote_get_all.keys
53
- features_to_remove.each { |key| Feature.new(key, @local).remove }
61
+ features_to_remove.each { |key| Feature.new(key, @local, instrumenter: @instrumenter).remove }
54
62
 
55
63
  nil
56
64
  rescue => exception
@@ -8,11 +8,8 @@ module Flipper
8
8
  class Sync
9
9
  include ::Flipper::Adapter
10
10
 
11
- # Public: The name of the adapter.
12
- attr_reader :name
13
-
14
11
  # Public: The synchronizer that will keep the local and remote in sync.
15
- attr_reader :synchronizer
12
+ attr_reader :synchronizer, :local, :remote
16
13
 
17
14
  # Public: Build a new sync instance.
18
15
  #
@@ -22,7 +19,6 @@ module Flipper
22
19
  # interval - The Float or Integer number of seconds between syncs from
23
20
  # remote to local. Default value is set in IntervalSynchronizer.
24
21
  def initialize(local, remote, options = {})
25
- @name = :sync
26
22
  @local = local
27
23
  @remote = remote
28
24
  @synchronizer = options.fetch(:synchronizer) do
@@ -37,6 +33,10 @@ module Flipper
37
33
  synchronize
38
34
  end
39
35
 
36
+ def adapter_stack
37
+ "#{name}(local: #{@local.adapter_stack}, remote: #{@remote.adapter_stack})"
38
+ end
39
+
40
40
  def features
41
41
  synchronize
42
42
  @local.features
@@ -52,9 +52,9 @@ module Flipper
52
52
  @local.get_multi(features)
53
53
  end
54
54
 
55
- def get_all
55
+ def get_all(**kwargs)
56
56
  synchronize
57
- @local.get_all
57
+ @local.get_all(**kwargs)
58
58
  end
59
59
 
60
60
  def add(feature)
@@ -0,0 +1,54 @@
1
+ module Flipper
2
+ module Adapters
3
+ # A base class for any adapter that wraps another adapter. By default, all methods
4
+ # delegate to the wrapped adapter. Implement `#wrap` to customize the behavior of
5
+ # all delegated methods, or override individual methods as needed.
6
+ class Wrapper
7
+ include Flipper::Adapter
8
+
9
+ METHODS = [
10
+ :import,
11
+ :export,
12
+ :features,
13
+ :add,
14
+ :remove,
15
+ :clear,
16
+ :get,
17
+ :get_multi,
18
+ :get_all,
19
+ :enable,
20
+ :disable,
21
+ ].freeze
22
+
23
+ attr_reader :adapter
24
+
25
+ def initialize(adapter)
26
+ @adapter = adapter
27
+ end
28
+
29
+ METHODS.each do |method|
30
+ if RUBY_VERSION >= '3.0'
31
+ define_method(method) do |*args, **kwargs|
32
+ wrap(method, *args, **kwargs) { @adapter.public_send(method, *args, **kwargs) }
33
+ end
34
+ else
35
+ define_method(method) do |*args|
36
+ wrap(method, *args) { @adapter.public_send(method, *args) }
37
+ end
38
+ end
39
+ end
40
+
41
+ # Override this method to customize the behavior of all delegated methods, and just yield to
42
+ # the block to call the wrapped adapter.
43
+ if RUBY_VERSION >= '3.0'
44
+ def wrap(method, *args, **kwargs, &block)
45
+ block.call
46
+ end
47
+ else
48
+ def wrap(method, *args, &block)
49
+ block.call
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end