flipper 0.28.0 → 1.3.6

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 (195) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/ci.yml +50 -7
  4. data/.github/workflows/examples.yml +52 -10
  5. data/CLAUDE.md +74 -0
  6. data/Changelog.md +1 -526
  7. data/Gemfile +17 -8
  8. data/README.md +31 -27
  9. data/Rakefile +2 -2
  10. data/benchmark/typecast_ips.rb +8 -0
  11. data/docs/images/banner.jpg +0 -0
  12. data/docs/images/flipper_cloud.png +0 -0
  13. data/examples/cloud/app.ru +12 -0
  14. data/examples/cloud/backoff_policy.rb +13 -0
  15. data/examples/cloud/basic.rb +22 -0
  16. data/examples/cloud/cloud_setup.rb +20 -0
  17. data/examples/cloud/forked.rb +36 -0
  18. data/examples/cloud/import.rb +17 -0
  19. data/examples/cloud/threaded.rb +33 -0
  20. data/examples/dsl.rb +0 -14
  21. data/examples/expressions.rb +213 -0
  22. data/examples/mirroring.rb +59 -0
  23. data/examples/strict.rb +18 -0
  24. data/exe/flipper +5 -0
  25. data/flipper-cloud.gemspec +19 -0
  26. data/flipper.gemspec +8 -4
  27. data/lib/flipper/actor.rb +6 -3
  28. data/lib/flipper/adapter.rb +10 -0
  29. data/lib/flipper/adapter_builder.rb +44 -0
  30. data/lib/flipper/adapters/actor_limit.rb +28 -0
  31. data/lib/flipper/adapters/cache_base.rb +143 -0
  32. data/lib/flipper/adapters/dual_write.rb +1 -3
  33. data/lib/flipper/adapters/failover.rb +0 -4
  34. data/lib/flipper/adapters/failsafe.rb +0 -4
  35. data/lib/flipper/adapters/http/client.rb +40 -12
  36. data/lib/flipper/adapters/http/error.rb +2 -2
  37. data/lib/flipper/adapters/http.rb +19 -14
  38. data/lib/flipper/adapters/instrumented.rb +0 -4
  39. data/lib/flipper/adapters/memoizable.rb +14 -19
  40. data/lib/flipper/adapters/memory.rb +37 -19
  41. data/lib/flipper/adapters/operation_logger.rb +18 -92
  42. data/lib/flipper/adapters/poll.rb +16 -3
  43. data/lib/flipper/adapters/pstore.rb +17 -11
  44. data/lib/flipper/adapters/read_only.rb +8 -41
  45. data/lib/flipper/adapters/strict.rb +45 -0
  46. data/lib/flipper/adapters/sync/feature_synchronizer.rb +10 -1
  47. data/lib/flipper/adapters/sync.rb +0 -4
  48. data/lib/flipper/adapters/wrapper.rb +54 -0
  49. data/lib/flipper/cli.rb +263 -0
  50. data/lib/flipper/cloud/configuration.rb +266 -0
  51. data/lib/flipper/cloud/dsl.rb +27 -0
  52. data/lib/flipper/cloud/message_verifier.rb +95 -0
  53. data/lib/flipper/cloud/middleware.rb +63 -0
  54. data/lib/flipper/cloud/routes.rb +14 -0
  55. data/lib/flipper/cloud/telemetry/backoff_policy.rb +96 -0
  56. data/lib/flipper/cloud/telemetry/instrumenter.rb +22 -0
  57. data/lib/flipper/cloud/telemetry/metric.rb +39 -0
  58. data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
  59. data/lib/flipper/cloud/telemetry/submitter.rb +100 -0
  60. data/lib/flipper/cloud/telemetry.rb +191 -0
  61. data/lib/flipper/cloud.rb +53 -0
  62. data/lib/flipper/configuration.rb +25 -4
  63. data/lib/flipper/dsl.rb +47 -42
  64. data/lib/flipper/engine.rb +102 -0
  65. data/lib/flipper/export.rb +0 -2
  66. data/lib/flipper/exporters/json/export.rb +1 -1
  67. data/lib/flipper/exporters/json/v1.rb +1 -1
  68. data/lib/flipper/expression/builder.rb +73 -0
  69. data/lib/flipper/expression/constant.rb +25 -0
  70. data/lib/flipper/expression.rb +71 -0
  71. data/lib/flipper/expressions/all.rb +9 -0
  72. data/lib/flipper/expressions/any.rb +9 -0
  73. data/lib/flipper/expressions/boolean.rb +9 -0
  74. data/lib/flipper/expressions/comparable.rb +13 -0
  75. data/lib/flipper/expressions/duration.rb +28 -0
  76. data/lib/flipper/expressions/equal.rb +9 -0
  77. data/lib/flipper/expressions/greater_than.rb +9 -0
  78. data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
  79. data/lib/flipper/expressions/less_than.rb +9 -0
  80. data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
  81. data/lib/flipper/expressions/not_equal.rb +9 -0
  82. data/lib/flipper/expressions/now.rb +9 -0
  83. data/lib/flipper/expressions/number.rb +9 -0
  84. data/lib/flipper/expressions/percentage.rb +9 -0
  85. data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
  86. data/lib/flipper/expressions/property.rb +9 -0
  87. data/lib/flipper/expressions/random.rb +9 -0
  88. data/lib/flipper/expressions/string.rb +9 -0
  89. data/lib/flipper/expressions/time.rb +9 -0
  90. data/lib/flipper/feature.rb +63 -1
  91. data/lib/flipper/gate.rb +2 -1
  92. data/lib/flipper/gate_values.rb +5 -2
  93. data/lib/flipper/gates/expression.rb +75 -0
  94. data/lib/flipper/instrumentation/log_subscriber.rb +28 -5
  95. data/lib/flipper/instrumentation/statsd.rb +4 -2
  96. data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
  97. data/lib/flipper/instrumentation/subscriber.rb +0 -4
  98. data/lib/flipper/metadata.rb +8 -1
  99. data/lib/flipper/middleware/memoizer.rb +30 -14
  100. data/lib/flipper/model/active_record.rb +23 -0
  101. data/lib/flipper/poller.rb +10 -9
  102. data/lib/flipper/serializers/gzip.rb +22 -0
  103. data/lib/flipper/serializers/json.rb +17 -0
  104. data/lib/flipper/spec/shared_adapter_specs.rb +82 -63
  105. data/lib/flipper/test/shared_adapter_test.rb +77 -58
  106. data/lib/flipper/test_help.rb +43 -0
  107. data/lib/flipper/typecast.rb +37 -9
  108. data/lib/flipper/types/percentage.rb +1 -1
  109. data/lib/flipper/version.rb +11 -1
  110. data/lib/flipper.rb +44 -7
  111. data/lib/generators/flipper/setup_generator.rb +68 -0
  112. data/lib/generators/flipper/templates/initializer.rb +45 -0
  113. data/lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb +22 -0
  114. data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb +18 -0
  115. data/lib/generators/flipper/update_generator.rb +35 -0
  116. data/package-lock.json +41 -0
  117. data/package.json +10 -0
  118. data/spec/fixtures/environment.rb +1 -0
  119. data/spec/flipper/adapter_builder_spec.rb +72 -0
  120. data/spec/flipper/adapter_spec.rb +1 -0
  121. data/spec/flipper/adapters/actor_limit_spec.rb +20 -0
  122. data/spec/flipper/adapters/dual_write_spec.rb +2 -2
  123. data/spec/flipper/adapters/http/client_spec.rb +61 -0
  124. data/spec/flipper/adapters/http_spec.rb +135 -74
  125. data/spec/flipper/adapters/instrumented_spec.rb +1 -1
  126. data/spec/flipper/adapters/memoizable_spec.rb +21 -21
  127. data/spec/flipper/adapters/memory_spec.rb +11 -2
  128. data/spec/flipper/adapters/operation_logger_spec.rb +2 -2
  129. data/spec/flipper/adapters/poll_spec.rb +41 -0
  130. data/spec/flipper/adapters/read_only_spec.rb +32 -17
  131. data/spec/flipper/adapters/strict_spec.rb +64 -0
  132. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +27 -0
  133. data/spec/flipper/cli_spec.rb +166 -0
  134. data/spec/flipper/cloud/configuration_spec.rb +251 -0
  135. data/spec/flipper/cloud/dsl_spec.rb +82 -0
  136. data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
  137. data/spec/flipper/cloud/middleware_spec.rb +289 -0
  138. data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +107 -0
  139. data/spec/flipper/cloud/telemetry/metric_spec.rb +87 -0
  140. data/spec/flipper/cloud/telemetry/metric_storage_spec.rb +58 -0
  141. data/spec/flipper/cloud/telemetry/submitter_spec.rb +145 -0
  142. data/spec/flipper/cloud/telemetry_spec.rb +208 -0
  143. data/spec/flipper/cloud_spec.rb +186 -0
  144. data/spec/flipper/configuration_spec.rb +17 -0
  145. data/spec/flipper/dsl_spec.rb +34 -73
  146. data/spec/flipper/engine_spec.rb +374 -0
  147. data/spec/flipper/exporters/json/v1_spec.rb +3 -3
  148. data/spec/flipper/expression/builder_spec.rb +248 -0
  149. data/spec/flipper/expression_spec.rb +188 -0
  150. data/spec/flipper/expressions/all_spec.rb +15 -0
  151. data/spec/flipper/expressions/any_spec.rb +15 -0
  152. data/spec/flipper/expressions/boolean_spec.rb +15 -0
  153. data/spec/flipper/expressions/duration_spec.rb +43 -0
  154. data/spec/flipper/expressions/equal_spec.rb +24 -0
  155. data/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +28 -0
  156. data/spec/flipper/expressions/greater_than_spec.rb +28 -0
  157. data/spec/flipper/expressions/less_than_or_equal_to_spec.rb +28 -0
  158. data/spec/flipper/expressions/less_than_spec.rb +32 -0
  159. data/spec/flipper/expressions/not_equal_spec.rb +15 -0
  160. data/spec/flipper/expressions/now_spec.rb +11 -0
  161. data/spec/flipper/expressions/number_spec.rb +21 -0
  162. data/spec/flipper/expressions/percentage_of_actors_spec.rb +20 -0
  163. data/spec/flipper/expressions/percentage_spec.rb +15 -0
  164. data/spec/flipper/expressions/property_spec.rb +13 -0
  165. data/spec/flipper/expressions/random_spec.rb +9 -0
  166. data/spec/flipper/expressions/string_spec.rb +11 -0
  167. data/spec/flipper/expressions/time_spec.rb +13 -0
  168. data/spec/flipper/feature_spec.rb +380 -10
  169. data/spec/flipper/gate_values_spec.rb +2 -2
  170. data/spec/flipper/gates/expression_spec.rb +108 -0
  171. data/spec/flipper/identifier_spec.rb +4 -5
  172. data/spec/flipper/instrumentation/log_subscriber_spec.rb +10 -2
  173. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +16 -2
  174. data/spec/flipper/middleware/memoizer_spec.rb +79 -10
  175. data/spec/flipper/model/active_record_spec.rb +72 -0
  176. data/spec/flipper/serializers/gzip_spec.rb +13 -0
  177. data/spec/flipper/serializers/json_spec.rb +13 -0
  178. data/spec/flipper/typecast_spec.rb +43 -7
  179. data/spec/flipper/types/actor_spec.rb +18 -1
  180. data/spec/flipper_integration_spec.rb +114 -16
  181. data/spec/flipper_spec.rb +87 -29
  182. data/spec/spec_helper.rb +17 -17
  183. data/spec/support/actor_names.yml +1 -0
  184. data/spec/support/fail_on_output.rb +8 -0
  185. data/spec/support/fake_backoff_policy.rb +15 -0
  186. data/spec/support/spec_helpers.rb +34 -8
  187. data/test/adapters/actor_limit_test.rb +20 -0
  188. data/test_rails/generators/flipper/setup_generator_test.rb +69 -0
  189. data/test_rails/generators/flipper/update_generator_test.rb +96 -0
  190. data/test_rails/helper.rb +22 -2
  191. data/test_rails/system/test_help_test.rb +52 -0
  192. metadata +179 -19
  193. data/.tool-versions +0 -1
  194. data/lib/flipper/railtie.rb +0 -47
  195. data/spec/flipper/railtie_spec.rb +0 -109
@@ -100,7 +100,14 @@ module Flipper
100
100
  #
101
101
  # Returns true if enabled, false if not.
102
102
  def enabled?(*actors)
103
- actors = actors.flatten.compact.map { |actor| Types::Actor.wrap(actor) }
103
+ actors = Array(actors).
104
+ # Avoids to_ary warning that happens when passing DelegateClass of an
105
+ # ActiveRecord object and using flatten here. This is tested in
106
+ # spec/flipper/model/active_record_spec.rb.
107
+ flat_map { |actor| actor.is_a?(Array) ? actor : [actor] }.
108
+ # Allows null object pattern. See PR for more. https://github.com/flippercloud/flipper/pull/887
109
+ reject(&:nil?).
110
+ map { |actor| Types::Actor.wrap(actor) }
104
111
  actors = nil if actors.empty?
105
112
 
106
113
  # thing is left for backwards compatibility
@@ -120,6 +127,28 @@ module Flipper
120
127
  end
121
128
  end
122
129
 
130
+ # Public: Enables an expression_to_add for a feature.
131
+ #
132
+ # expression - an Expression or Hash that can be converted to an expression.
133
+ #
134
+ # Returns result of enable.
135
+ def enable_expression(expression)
136
+ enable Expression.build(expression)
137
+ end
138
+
139
+ # Public: Add an expression for a feature.
140
+ #
141
+ # expression_to_add - an expression or Hash that can be converted to an expression.
142
+ #
143
+ # Returns result of enable.
144
+ def add_expression(expression_to_add)
145
+ if (current_expression = expression)
146
+ enable current_expression.add(expression_to_add)
147
+ else
148
+ enable expression_to_add
149
+ end
150
+ end
151
+
123
152
  # Public: Enables a feature for an actor.
124
153
  #
125
154
  # actor - a Flipper::Types::Actor instance or an object that responds
@@ -160,6 +189,27 @@ module Flipper
160
189
  enable Types::PercentageOfActors.wrap(percentage)
161
190
  end
162
191
 
192
+ # Public: Disables an expression for a feature.
193
+ #
194
+ # expression - an expression or Hash that can be converted to an expression.
195
+ #
196
+ # Returns result of disable.
197
+ def disable_expression
198
+ disable Flipper.all # just need an expression to clear
199
+ end
200
+
201
+ # Public: Remove an expression from a feature. Does nothing if no expression is
202
+ # currently enabled.
203
+ #
204
+ # expression - an Expression or Hash that can be converted to an expression.
205
+ #
206
+ # Returns result of enable or nil (if no expression enabled).
207
+ def remove_expression(expression_to_remove)
208
+ if (current_expression = expression)
209
+ enable current_expression.remove(expression_to_remove)
210
+ end
211
+ end
212
+
163
213
  # Public: Disables a feature for an actor.
164
214
  #
165
215
  # actor - a Flipper::Types::Actor instance or an object that responds
@@ -252,6 +302,10 @@ module Flipper
252
302
  Flipper.groups - enabled_groups
253
303
  end
254
304
 
305
+ def expression
306
+ Flipper::Expression.build(expression_value) if expression_value
307
+ end
308
+
255
309
  # Public: Get the adapter value for the groups gate.
256
310
  #
257
311
  # Returns Set of String group names.
@@ -259,6 +313,13 @@ module Flipper
259
313
  gate_values.groups
260
314
  end
261
315
 
316
+ # Public: Get the adapter value for the expression gate.
317
+ #
318
+ # Returns expression.
319
+ def expression_value
320
+ gate_values.expression
321
+ end
322
+
262
323
  # Public: Get the adapter value for the actors gate.
263
324
  #
264
325
  # Returns Set of String flipper_id's.
@@ -347,6 +408,7 @@ module Flipper
347
408
  def gates_hash
348
409
  @gates_hash ||= {
349
410
  boolean: Gates::Boolean.new,
411
+ expression: Gates::Expression.new,
350
412
  actor: Gates::Actor.new,
351
413
  percentage_of_actors: Gates::PercentageOfActors.new,
352
414
  percentage_of_time: Gates::PercentageOfTime.new,
data/lib/flipper/gate.rb CHANGED
@@ -26,7 +26,7 @@ module Flipper
26
26
  # in subclass.
27
27
  #
28
28
  # Returns true if gate open for any actor, false if not.
29
- def open?(actors, value, options = {})
29
+ def open?(context)
30
30
  false
31
31
  end
32
32
 
@@ -60,3 +60,4 @@ require 'flipper/gates/boolean'
60
60
  require 'flipper/gates/group'
61
61
  require 'flipper/gates/percentage_of_actors'
62
62
  require 'flipper/gates/percentage_of_time'
63
+ require 'flipper/gates/expression'
@@ -6,6 +6,7 @@ module Flipper
6
6
  attr_reader :boolean
7
7
  attr_reader :actors
8
8
  attr_reader :groups
9
+ attr_reader :expression
9
10
  attr_reader :percentage_of_actors
10
11
  attr_reader :percentage_of_time
11
12
 
@@ -13,8 +14,9 @@ module Flipper
13
14
  @boolean = Typecast.to_boolean(adapter_values[:boolean])
14
15
  @actors = Typecast.to_set(adapter_values[:actors])
15
16
  @groups = Typecast.to_set(adapter_values[:groups])
16
- @percentage_of_actors = Typecast.to_percentage(adapter_values[:percentage_of_actors])
17
- @percentage_of_time = Typecast.to_percentage(adapter_values[:percentage_of_time])
17
+ @expression = adapter_values[:expression]
18
+ @percentage_of_actors = Typecast.to_number(adapter_values[:percentage_of_actors])
19
+ @percentage_of_time = Typecast.to_number(adapter_values[:percentage_of_time])
18
20
  end
19
21
 
20
22
  def eql?(other)
@@ -22,6 +24,7 @@ module Flipper
22
24
  boolean == other.boolean &&
23
25
  actors == other.actors &&
24
26
  groups == other.groups &&
27
+ expression == other.expression &&
25
28
  percentage_of_actors == other.percentage_of_actors &&
26
29
  percentage_of_time == other.percentage_of_time
27
30
  end
@@ -0,0 +1,75 @@
1
+ require "flipper/expression"
2
+
3
+ module Flipper
4
+ module Gates
5
+ class Expression < Gate
6
+ # Internal: The name of the gate. Used for instrumentation, etc.
7
+ def name
8
+ :expression
9
+ end
10
+
11
+ # Internal: Name converted to value safe for adapter.
12
+ def key
13
+ :expression
14
+ end
15
+
16
+ def data_type
17
+ :json
18
+ end
19
+
20
+ def enabled?(value)
21
+ !value.nil? && !value.empty?
22
+ end
23
+
24
+ # Internal: Checks if the gate is open for a thing.
25
+ #
26
+ # Returns true if gate open for thing, false if not.
27
+ def open?(context)
28
+ data = context.values.expression
29
+ return false if data.nil? || data.empty?
30
+ expression = Flipper::Expression.build(data)
31
+
32
+ if context.actors.nil? || context.actors.empty?
33
+ !!expression.evaluate(feature_name: context.feature_name, properties: DEFAULT_PROPERTIES)
34
+ else
35
+ context.actors.any? do |actor|
36
+ !!expression.evaluate(feature_name: context.feature_name, properties: properties(actor))
37
+ end
38
+ end
39
+ end
40
+
41
+ def protects?(thing)
42
+ thing.is_a?(Flipper::Expression) || thing.is_a?(Hash)
43
+ end
44
+
45
+ def wrap(thing)
46
+ Flipper::Expression.build(thing)
47
+ end
48
+
49
+ private
50
+
51
+ # Internal
52
+ DEFAULT_PROPERTIES = {}.freeze
53
+
54
+ def properties(actor)
55
+ return DEFAULT_PROPERTIES if actor.nil?
56
+
57
+ properties = {}
58
+
59
+ if actor.respond_to?(:flipper_properties)
60
+ properties.update(actor.flipper_properties)
61
+ else
62
+ warn "#{actor.inspect} does not respond to `flipper_properties` but should."
63
+ end
64
+
65
+ properties.transform_keys!(&:to_s)
66
+
67
+ if actor.respond_to?(:flipper_id)
68
+ properties["flipper_id".freeze] = actor.flipper_id
69
+ end
70
+
71
+ properties
72
+ end
73
+ end
74
+ end
75
+ end
@@ -1,4 +1,5 @@
1
1
  require 'securerandom'
2
+ require 'active_support/gem_version'
2
3
  require 'active_support/notifications'
3
4
  require 'active_support/log_subscriber'
4
5
 
@@ -32,7 +33,7 @@ module Flipper
32
33
  details += " gate_name=#{gate_name}" unless gate_name.nil?
33
34
 
34
35
  name = '%s (%.1fms)' % [description, event.duration]
35
- debug " #{color(name, CYAN, true)} [ #{details} ]"
36
+ debug " #{color_name(name)} [ #{details} ]"
36
37
  end
37
38
 
38
39
  # Logs an adapter operation. If operation is for a feature, then that
@@ -52,11 +53,10 @@ module Flipper
52
53
 
53
54
  feature_name = event.payload[:feature_name]
54
55
  adapter_name = event.payload[:adapter_name]
55
- gate_name = event.payload[:gate_name]
56
56
  operation = event.payload[:operation]
57
57
  result = event.payload[:result]
58
58
 
59
- description = 'Flipper '
59
+ description = String.new('Flipper ')
60
60
  description << "feature(#{feature_name}) " unless feature_name.nil?
61
61
  description << "adapter(#{adapter_name}) "
62
62
  description << "#{operation} "
@@ -64,14 +64,37 @@ module Flipper
64
64
  details = "result=#{result.inspect}"
65
65
 
66
66
  name = '%s (%.1fms)' % [description, event.duration]
67
- debug " #{color(name, CYAN, true)} [ #{details} ]"
67
+ debug " #{color_name(name)} [ #{details} ]"
68
68
  end
69
69
 
70
70
  def logger
71
71
  self.class.logger
72
72
  end
73
+
74
+ def self.attach
75
+ attach_to InstrumentationNamespace
76
+ end
77
+
78
+ def self.detach
79
+ # Rails 5.2 doesn't support this, that's fine
80
+ detach_from InstrumentationNamespace if respond_to?(:detach_from)
81
+ end
82
+
83
+ private
84
+
85
+ # Rails 7.1 changed the signature of this function.
86
+ COLOR_OPTIONS = if Gem::Requirement.new(">=7.1").satisfied_by?(ActiveSupport.gem_version)
87
+ { bold: true }.freeze
88
+ else
89
+ true
90
+ end
91
+ private_constant :COLOR_OPTIONS
92
+
93
+ def color_name(name)
94
+ color(name, CYAN, COLOR_OPTIONS)
95
+ end
73
96
  end
74
97
  end
75
98
 
76
- Instrumentation::LogSubscriber.attach_to InstrumentationNamespace
99
+ Instrumentation::LogSubscriber.attach
77
100
  end
@@ -2,5 +2,7 @@ require 'securerandom'
2
2
  require 'active_support/notifications'
3
3
  require 'flipper/instrumentation/statsd_subscriber'
4
4
 
5
- ActiveSupport::Notifications.subscribe /\.flipper$/,
6
- Flipper::Instrumentation::StatsdSubscriber
5
+ ActiveSupport::Notifications.subscribe(
6
+ /\.flipper$/,
7
+ Flipper::Instrumentation::StatsdSubscriber
8
+ )
@@ -12,13 +12,11 @@ module Flipper
12
12
  end
13
13
 
14
14
  def update_timer(metric)
15
- if self.class.client
16
- self.class.client.timing metric, (@duration * 1_000).round
17
- end
15
+ self.class.client&.timing metric, (@duration * 1_000).round
18
16
  end
19
17
 
20
18
  def update_counter(metric)
21
- self.class.client.increment metric if self.class.client
19
+ self.class.client&.increment metric
22
20
  end
23
21
  end
24
22
  end
@@ -42,7 +42,6 @@ module Flipper
42
42
  # Private
43
43
  def update_feature_operation_metrics
44
44
  feature_name = @payload[:feature_name]
45
- gate_name = @payload[:gate_name]
46
45
  operation = strip_trailing_question_mark(@payload[:operation])
47
46
  result = @payload[:result]
48
47
 
@@ -64,9 +63,6 @@ module Flipper
64
63
  def update_adapter_operation_metrics
65
64
  adapter_name = @payload[:adapter_name]
66
65
  operation = @payload[:operation]
67
- result = @payload[:result]
68
- value = @payload[:value]
69
- key = @payload[:key]
70
66
 
71
67
  update_timer "flipper.adapter.#{adapter_name}.#{operation}"
72
68
  end
@@ -1,5 +1,12 @@
1
+ require_relative './version'
2
+
1
3
  module Flipper
2
4
  METADATA = {
3
- 'changelog_uri' => 'https://github.com/jnunemaker/flipper/blob/main/Changelog.md',
5
+ "documentation_uri" => "https://www.flippercloud.io/docs",
6
+ "homepage_uri" => "https://www.flippercloud.io",
7
+ "source_code_uri" => "https://github.com/flippercloud/flipper",
8
+ "bug_tracker_uri" => "https://github.com/flippercloud/flipper/issues",
9
+ "changelog_uri" => "https://github.com/flippercloud/flipper/releases/tag/v#{Flipper::VERSION}",
10
+ "funding_uri" => "https://github.com/sponsors/flippercloud",
4
11
  }.freeze
5
12
  end
@@ -20,6 +20,14 @@ module Flipper
20
20
  # # using with preload specific features
21
21
  # use Flipper::Middleware::Memoizer, preload: [:stats, :search, :some_feature]
22
22
  #
23
+ # # using with preload block that returns true/false
24
+ # use Flipper::Middleware::Memoizer, preload: ->(request) { !request.path.start_with?('/assets') }
25
+ #
26
+ # # using with preload block that returns specific features
27
+ # use Flipper::Middleware::Memoizer, preload: ->(request) {
28
+ # request.path.starts_with?('/admin') ? [:stats, :search] : false
29
+ # }
30
+ #
23
31
  def initialize(app, opts = {})
24
32
  if opts.is_a?(Flipper::DSL) || opts.is_a?(Proc)
25
33
  raise 'Flipper::Middleware::Memoizer no longer initializes with a flipper instance or block. Read more at: https://git.io/vSo31.'
@@ -34,7 +42,7 @@ module Flipper
34
42
  request = Rack::Request.new(env)
35
43
 
36
44
  if memoize?(request)
37
- memoized_call(env)
45
+ memoized_call(request)
38
46
  else
39
47
  @app.call(env)
40
48
  end
@@ -52,26 +60,34 @@ module Flipper
52
60
  end
53
61
  end
54
62
 
55
- def memoized_call(env)
56
- reset_on_body_close = false
57
- flipper = env.fetch(@env_key) { Flipper }
63
+ def memoized_call(request)
64
+ flipper = request.env.fetch(@env_key) { Flipper }
58
65
 
59
66
  # Already memoizing. This instance does not need to do anything.
60
67
  if flipper.memoizing?
61
- warn "Flipper::Middleware::Memoizer appears to be running twice. Read how to resolve this at https://github.com/jnunemaker/flipper/pull/523"
62
- return @app.call(env)
68
+ warn "Flipper::Middleware::Memoizer appears to be running twice. Read how to resolve this at https://github.com/flippercloud/flipper/pull/523"
69
+ return @app.call(request.env)
63
70
  end
64
71
 
65
- flipper.memoize = true
72
+ begin
73
+ flipper.memoize = true
66
74
 
67
- case @opts[:preload]
68
- when true then flipper.preload_all
69
- when Array then flipper.preload(@opts[:preload])
70
- end
75
+ # Preloading is pointless without memoizing.
76
+ preload = if @opts[:preload].respond_to?(:call)
77
+ @opts[:preload].call(request)
78
+ else
79
+ @opts[:preload]
80
+ end
71
81
 
72
- @app.call(env)
73
- ensure
74
- flipper.memoize = false if flipper
82
+ case preload
83
+ when true then flipper.preload_all
84
+ when Array then flipper.preload(preload)
85
+ end
86
+
87
+ @app.call(request.env)
88
+ ensure
89
+ flipper.memoize = false
90
+ end
75
91
  end
76
92
  end
77
93
  end
@@ -0,0 +1,23 @@
1
+ module Flipper
2
+ module Model
3
+ module ActiveRecord
4
+ # The id of the record when used as an actor.
5
+ #
6
+ # class User < ActiveRecord::Base
7
+ # end
8
+ #
9
+ # user = User.first
10
+ # Flipper.enable :some_feature, user
11
+ # Flipper.enabled? :some_feature, user #=> true
12
+ #
13
+ def flipper_id
14
+ "#{self.class.base_class.name};#{id}"
15
+ end
16
+
17
+ # Properties used to evaluate expressions
18
+ def flipper_properties
19
+ {"type" => self.class.name}.merge(attributes)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -17,9 +17,11 @@ module Flipper
17
17
  end
18
18
 
19
19
  def self.reset
20
- instances.each {|_,poller| poller.stop }.clear
20
+ instances.each {|_, instance| instance.stop }.clear
21
21
  end
22
22
 
23
+ MINIMUM_POLL_INTERVAL = 10
24
+
23
25
  def initialize(options = {})
24
26
  @thread = nil
25
27
  @pid = Process.pid
@@ -28,11 +30,11 @@ module Flipper
28
30
  @remote_adapter = options.fetch(:remote_adapter)
29
31
  @interval = options.fetch(:interval, 10).to_f
30
32
  @last_synced_at = Concurrent::AtomicFixnum.new(0)
31
- @adapter = Adapters::Memory.new
33
+ @adapter = Adapters::Memory.new(nil, threadsafe: true)
32
34
 
33
- if @interval < 1
34
- warn "Flipper::Cloud poll interval must be greater than or equal to 1 but was #{@interval}. Setting @interval to 1."
35
- @interval = 1
35
+ if @interval < MINIMUM_POLL_INTERVAL
36
+ warn "Flipper::Cloud poll interval must be greater than or equal to #{MINIMUM_POLL_INTERVAL} but was #{@interval}. Setting @interval to #{MINIMUM_POLL_INTERVAL}."
37
+ @interval = MINIMUM_POLL_INTERVAL
36
38
  end
37
39
 
38
40
  @start_automatically = options.fetch(:start_automatically, true)
@@ -57,15 +59,14 @@ module Flipper
57
59
  def run
58
60
  loop do
59
61
  sleep jitter
60
- start = Concurrent.monotonic_time
62
+
61
63
  begin
62
64
  sync
63
- rescue => exception
65
+ rescue
64
66
  # you can instrument these using poller.flipper
65
67
  end
66
68
 
67
- sleep_interval = interval - (Concurrent.monotonic_time - start)
68
- sleep sleep_interval if sleep_interval.positive?
69
+ sleep interval
69
70
  end
70
71
  end
71
72
 
@@ -0,0 +1,22 @@
1
+ require "zlib"
2
+ require "stringio"
3
+
4
+ module Flipper
5
+ module Serializers
6
+ class Gzip
7
+ def self.serialize(source)
8
+ return if source.nil?
9
+ output = StringIO.new
10
+ gz = Zlib::GzipWriter.new(output)
11
+ gz.write(source)
12
+ gz.close
13
+ output.string
14
+ end
15
+
16
+ def self.deserialize(source)
17
+ return if source.nil?
18
+ Zlib::GzipReader.wrap(StringIO.new(source), &:read)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ require "json"
2
+
3
+ module Flipper
4
+ module Serializers
5
+ class Json
6
+ def self.serialize(source)
7
+ return if source.nil?
8
+ JSON.generate(source)
9
+ end
10
+
11
+ def self.deserialize(source)
12
+ return if source.nil?
13
+ JSON.parse(source)
14
+ end
15
+ end
16
+ end
17
+ end