flipper 0.26.0 → 1.1.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.
Files changed (199) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/ci.yml +19 -13
  4. data/.github/workflows/examples.yml +32 -15
  5. data/Changelog.md +294 -154
  6. data/Gemfile +15 -10
  7. data/README.md +13 -11
  8. data/benchmark/enabled_ips.rb +10 -0
  9. data/benchmark/enabled_multiple_actors_ips.rb +20 -0
  10. data/benchmark/enabled_profile.rb +20 -0
  11. data/benchmark/instrumentation_ips.rb +21 -0
  12. data/benchmark/typecast_ips.rb +27 -0
  13. data/docs/images/flipper_cloud.png +0 -0
  14. data/examples/api/basic.ru +3 -4
  15. data/examples/api/custom_memoized.ru +3 -4
  16. data/examples/api/memoized.ru +3 -4
  17. data/examples/cloud/app.ru +12 -0
  18. data/examples/cloud/backoff_policy.rb +13 -0
  19. data/examples/cloud/basic.rb +22 -0
  20. data/examples/cloud/cloud_setup.rb +20 -0
  21. data/examples/cloud/forked.rb +36 -0
  22. data/examples/cloud/import.rb +17 -0
  23. data/examples/cloud/threaded.rb +33 -0
  24. data/examples/dsl.rb +1 -15
  25. data/examples/enabled_for_actor.rb +4 -2
  26. data/examples/expressions.rb +213 -0
  27. data/examples/mirroring.rb +59 -0
  28. data/examples/strict.rb +18 -0
  29. data/flipper-cloud.gemspec +19 -0
  30. data/flipper.gemspec +3 -5
  31. data/lib/flipper/actor.rb +6 -3
  32. data/lib/flipper/adapter.rb +33 -7
  33. data/lib/flipper/adapter_builder.rb +44 -0
  34. data/lib/flipper/adapters/dual_write.rb +1 -3
  35. data/lib/flipper/adapters/failover.rb +0 -4
  36. data/lib/flipper/adapters/failsafe.rb +0 -4
  37. data/lib/flipper/adapters/http/client.rb +26 -7
  38. data/lib/flipper/adapters/http/error.rb +1 -1
  39. data/lib/flipper/adapters/http.rb +29 -16
  40. data/lib/flipper/adapters/instrumented.rb +25 -6
  41. data/lib/flipper/adapters/memoizable.rb +33 -21
  42. data/lib/flipper/adapters/memory.rb +81 -46
  43. data/lib/flipper/adapters/operation_logger.rb +16 -7
  44. data/lib/flipper/adapters/poll/poller.rb +2 -125
  45. data/lib/flipper/adapters/poll.rb +5 -3
  46. data/lib/flipper/adapters/pstore.rb +17 -11
  47. data/lib/flipper/adapters/read_only.rb +4 -4
  48. data/lib/flipper/adapters/strict.rb +47 -0
  49. data/lib/flipper/adapters/sync/feature_synchronizer.rb +10 -1
  50. data/lib/flipper/adapters/sync.rb +0 -4
  51. data/lib/flipper/cloud/configuration.rb +258 -0
  52. data/lib/flipper/cloud/dsl.rb +27 -0
  53. data/lib/flipper/cloud/message_verifier.rb +95 -0
  54. data/lib/flipper/cloud/middleware.rb +63 -0
  55. data/lib/flipper/cloud/routes.rb +14 -0
  56. data/lib/flipper/cloud/telemetry/backoff_policy.rb +93 -0
  57. data/lib/flipper/cloud/telemetry/instrumenter.rb +26 -0
  58. data/lib/flipper/cloud/telemetry/metric.rb +39 -0
  59. data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
  60. data/lib/flipper/cloud/telemetry/submitter.rb +98 -0
  61. data/lib/flipper/cloud/telemetry.rb +183 -0
  62. data/lib/flipper/cloud.rb +53 -0
  63. data/lib/flipper/configuration.rb +25 -4
  64. data/lib/flipper/dsl.rb +46 -45
  65. data/lib/flipper/engine.rb +88 -0
  66. data/lib/flipper/errors.rb +3 -3
  67. data/lib/flipper/export.rb +26 -0
  68. data/lib/flipper/exporter.rb +17 -0
  69. data/lib/flipper/exporters/json/export.rb +32 -0
  70. data/lib/flipper/exporters/json/v1.rb +33 -0
  71. data/lib/flipper/expression/builder.rb +73 -0
  72. data/lib/flipper/expression/constant.rb +25 -0
  73. data/lib/flipper/expression.rb +71 -0
  74. data/lib/flipper/expressions/all.rb +11 -0
  75. data/lib/flipper/expressions/any.rb +9 -0
  76. data/lib/flipper/expressions/boolean.rb +9 -0
  77. data/lib/flipper/expressions/comparable.rb +13 -0
  78. data/lib/flipper/expressions/duration.rb +28 -0
  79. data/lib/flipper/expressions/equal.rb +9 -0
  80. data/lib/flipper/expressions/greater_than.rb +9 -0
  81. data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
  82. data/lib/flipper/expressions/less_than.rb +9 -0
  83. data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
  84. data/lib/flipper/expressions/not_equal.rb +9 -0
  85. data/lib/flipper/expressions/now.rb +9 -0
  86. data/lib/flipper/expressions/number.rb +9 -0
  87. data/lib/flipper/expressions/percentage.rb +9 -0
  88. data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
  89. data/lib/flipper/expressions/property.rb +9 -0
  90. data/lib/flipper/expressions/random.rb +9 -0
  91. data/lib/flipper/expressions/string.rb +9 -0
  92. data/lib/flipper/expressions/time.rb +9 -0
  93. data/lib/flipper/feature.rb +87 -26
  94. data/lib/flipper/feature_check_context.rb +10 -6
  95. data/lib/flipper/gate.rb +13 -11
  96. data/lib/flipper/gate_values.rb +5 -18
  97. data/lib/flipper/gates/actor.rb +10 -17
  98. data/lib/flipper/gates/boolean.rb +1 -1
  99. data/lib/flipper/gates/expression.rb +75 -0
  100. data/lib/flipper/gates/group.rb +5 -7
  101. data/lib/flipper/gates/percentage_of_actors.rb +10 -13
  102. data/lib/flipper/gates/percentage_of_time.rb +1 -2
  103. data/lib/flipper/identifier.rb +2 -2
  104. data/lib/flipper/instrumentation/log_subscriber.rb +24 -5
  105. data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
  106. data/lib/flipper/instrumentation/subscriber.rb +8 -1
  107. data/lib/flipper/metadata.rb +5 -1
  108. data/lib/flipper/middleware/memoizer.rb +30 -14
  109. data/lib/flipper/poller.rb +117 -0
  110. data/lib/flipper/serializers/gzip.rb +24 -0
  111. data/lib/flipper/serializers/json.rb +19 -0
  112. data/lib/flipper/spec/shared_adapter_specs.rb +95 -54
  113. data/lib/flipper/test/shared_adapter_test.rb +91 -48
  114. data/lib/flipper/typecast.rb +56 -15
  115. data/lib/flipper/types/actor.rb +13 -13
  116. data/lib/flipper/types/group.rb +4 -4
  117. data/lib/flipper/types/percentage.rb +1 -1
  118. data/lib/flipper/version.rb +1 -1
  119. data/lib/flipper.rb +47 -10
  120. data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
  121. data/spec/flipper/adapter_builder_spec.rb +73 -0
  122. data/spec/flipper/adapter_spec.rb +30 -2
  123. data/spec/flipper/adapters/dual_write_spec.rb +2 -2
  124. data/spec/flipper/adapters/http_spec.rb +64 -8
  125. data/spec/flipper/adapters/instrumented_spec.rb +29 -11
  126. data/spec/flipper/adapters/memoizable_spec.rb +51 -31
  127. data/spec/flipper/adapters/memory_spec.rb +14 -3
  128. data/spec/flipper/adapters/operation_logger_spec.rb +31 -12
  129. data/spec/flipper/adapters/read_only_spec.rb +32 -17
  130. data/spec/flipper/adapters/strict_spec.rb +62 -0
  131. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +27 -0
  132. data/spec/flipper/cloud/configuration_spec.rb +252 -0
  133. data/spec/flipper/cloud/dsl_spec.rb +82 -0
  134. data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
  135. data/spec/flipper/cloud/middleware_spec.rb +289 -0
  136. data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +108 -0
  137. data/spec/flipper/cloud/telemetry/metric_spec.rb +87 -0
  138. data/spec/flipper/cloud/telemetry/metric_storage_spec.rb +58 -0
  139. data/spec/flipper/cloud/telemetry/submitter_spec.rb +145 -0
  140. data/spec/flipper/cloud/telemetry_spec.rb +156 -0
  141. data/spec/flipper/cloud_spec.rb +180 -0
  142. data/spec/flipper/configuration_spec.rb +17 -0
  143. data/spec/flipper/dsl_spec.rb +54 -73
  144. data/spec/flipper/engine_spec.rb +291 -0
  145. data/spec/flipper/export_spec.rb +13 -0
  146. data/spec/flipper/exporter_spec.rb +16 -0
  147. data/spec/flipper/exporters/json/export_spec.rb +60 -0
  148. data/spec/flipper/exporters/json/v1_spec.rb +33 -0
  149. data/spec/flipper/expression/builder_spec.rb +248 -0
  150. data/spec/flipper/expression_spec.rb +188 -0
  151. data/spec/flipper/expressions/all_spec.rb +15 -0
  152. data/spec/flipper/expressions/any_spec.rb +15 -0
  153. data/spec/flipper/expressions/boolean_spec.rb +15 -0
  154. data/spec/flipper/expressions/duration_spec.rb +43 -0
  155. data/spec/flipper/expressions/equal_spec.rb +24 -0
  156. data/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +28 -0
  157. data/spec/flipper/expressions/greater_than_spec.rb +28 -0
  158. data/spec/flipper/expressions/less_than_or_equal_to_spec.rb +28 -0
  159. data/spec/flipper/expressions/less_than_spec.rb +32 -0
  160. data/spec/flipper/expressions/not_equal_spec.rb +15 -0
  161. data/spec/flipper/expressions/now_spec.rb +11 -0
  162. data/spec/flipper/expressions/number_spec.rb +21 -0
  163. data/spec/flipper/expressions/percentage_of_actors_spec.rb +20 -0
  164. data/spec/flipper/expressions/percentage_spec.rb +15 -0
  165. data/spec/flipper/expressions/property_spec.rb +13 -0
  166. data/spec/flipper/expressions/random_spec.rb +9 -0
  167. data/spec/flipper/expressions/string_spec.rb +11 -0
  168. data/spec/flipper/expressions/time_spec.rb +13 -0
  169. data/spec/flipper/feature_check_context_spec.rb +17 -17
  170. data/spec/flipper/feature_spec.rb +436 -33
  171. data/spec/flipper/gate_values_spec.rb +2 -33
  172. data/spec/flipper/gates/boolean_spec.rb +1 -1
  173. data/spec/flipper/gates/expression_spec.rb +108 -0
  174. data/spec/flipper/gates/group_spec.rb +2 -3
  175. data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -5
  176. data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
  177. data/spec/flipper/identifier_spec.rb +4 -5
  178. data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -5
  179. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +25 -1
  180. data/spec/flipper/middleware/memoizer_spec.rb +67 -0
  181. data/spec/flipper/poller_spec.rb +47 -0
  182. data/spec/flipper/serializers/gzip_spec.rb +13 -0
  183. data/spec/flipper/serializers/json_spec.rb +13 -0
  184. data/spec/flipper/typecast_spec.rb +121 -6
  185. data/spec/flipper/types/actor_spec.rb +63 -46
  186. data/spec/flipper/types/group_spec.rb +2 -2
  187. data/spec/flipper_integration_spec.rb +168 -58
  188. data/spec/flipper_spec.rb +92 -28
  189. data/spec/spec_helper.rb +6 -13
  190. data/spec/support/actor_names.yml +1 -0
  191. data/spec/support/climate_control.rb +7 -0
  192. data/spec/support/fake_backoff_policy.rb +15 -0
  193. data/spec/support/skippable.rb +18 -0
  194. data/spec/support/spec_helpers.rb +11 -3
  195. metadata +166 -13
  196. data/.github/workflows/release.yml +0 -44
  197. data/.tool-versions +0 -1
  198. data/lib/flipper/railtie.rb +0 -47
  199. data/spec/flipper/railtie_spec.rb +0 -109
@@ -7,6 +7,7 @@ module Flipper
7
7
  @feature = @flipper[:stats]
8
8
  @boolean_gate = @feature.gate(:boolean)
9
9
  @group_gate = @feature.gate(:group)
10
+ @expression_gate = @feature.gate(:expression)
10
11
  @actor_gate = @feature.gate(:actor)
11
12
  @actors_gate = @feature.gate(:percentage_of_actors)
12
13
  @time_gate = @feature.gate(:percentage_of_time)
@@ -34,28 +35,55 @@ module Flipper
34
35
  assert_includes @adapter.class.ancestors, Flipper::Adapter
35
36
  end
36
37
 
38
+ def test_knows_how_to_get_adapter_from_source
39
+ adapter = Flipper::Adapters::Memory.new
40
+ flipper = Flipper.new(adapter)
41
+
42
+ assert_includes adapter.class.from(adapter).class.ancestors, Flipper::Adapter
43
+ assert_includes adapter.class.from(flipper).class.ancestors, Flipper::Adapter
44
+ end
45
+
37
46
  def test_returns_correct_default_values_for_gates_if_none_are_enabled
47
+ assert_equal @adapter.class.default_config, @adapter.get(@feature)
38
48
  assert_equal @adapter.default_config, @adapter.get(@feature)
39
49
  end
40
50
 
41
51
  def test_can_enable_disable_and_get_value_for_boolean_gate
42
- assert_equal true, @adapter.enable(@feature, @boolean_gate, @flipper.boolean)
52
+ assert_equal true, @adapter.enable(@feature, @boolean_gate, Flipper::Types::Boolean.new)
43
53
  assert_equal 'true', @adapter.get(@feature)[:boolean]
44
- assert_equal true, @adapter.disable(@feature, @boolean_gate, @flipper.boolean(false))
54
+ assert_equal true, @adapter.disable(@feature, @boolean_gate, Flipper::Types::Boolean.new(false))
45
55
  assert_nil @adapter.get(@feature)[:boolean]
46
56
  end
47
57
 
48
58
  def test_fully_disables_all_enabled_things_when_boolean_gate_disabled
49
59
  actor22 = Flipper::Actor.new('22')
50
- assert_equal true, @adapter.enable(@feature, @boolean_gate, @flipper.boolean)
60
+ assert_equal true, @adapter.enable(@feature, @boolean_gate, Flipper::Types::Boolean.new)
51
61
  assert_equal true, @adapter.enable(@feature, @group_gate, @flipper.group(:admins))
52
- assert_equal true, @adapter.enable(@feature, @actor_gate, @flipper.actor(actor22))
53
- assert_equal true, @adapter.enable(@feature, @actors_gate, @flipper.actors(25))
54
- assert_equal true, @adapter.enable(@feature, @time_gate, @flipper.time(45))
55
- assert_equal true, @adapter.disable(@feature, @boolean_gate, @flipper.boolean(false))
62
+ assert_equal true, @adapter.enable(@feature, @actor_gate, Flipper::Types::Actor.new(actor22))
63
+ assert_equal true, @adapter.enable(@feature, @actors_gate, Flipper::Types::PercentageOfActors.new(25))
64
+ assert_equal true, @adapter.enable(@feature, @time_gate, Flipper::Types::PercentageOfTime.new(45))
65
+ assert_equal true, @adapter.disable(@feature, @boolean_gate, Flipper::Types::Boolean.new(false))
56
66
  assert_equal @adapter.default_config, @adapter.get(@feature)
57
67
  end
58
68
 
69
+ def test_can_enable_disable_and_get_value_for_expression_gate
70
+ basic_expression = Flipper.property(:plan).eq("basic")
71
+ age_expression = Flipper.property(:age).gte(21)
72
+ any_expression = Flipper.any(basic_expression, age_expression)
73
+
74
+ assert_equal true, @adapter.enable(@feature, @expression_gate, any_expression)
75
+ result = @adapter.get(@feature)
76
+ assert_equal any_expression.value, result[:expression]
77
+
78
+ assert_equal true, @adapter.enable(@feature, @expression_gate, basic_expression)
79
+ result = @adapter.get(@feature)
80
+ assert_equal basic_expression.value, result[:expression]
81
+
82
+ assert_equal true, @adapter.disable(@feature, @expression_gate, basic_expression)
83
+ result = @adapter.get(@feature)
84
+ assert_nil result[:expression]
85
+ end
86
+
59
87
  def test_can_enable_disable_get_value_for_group_gate
60
88
  assert_equal true, @adapter.enable(@feature, @group_gate, @flipper.group(:admins))
61
89
  assert_equal true, @adapter.enable(@feature, @group_gate, @flipper.group(:early_access))
@@ -76,34 +104,34 @@ module Flipper
76
104
  actor22 = Flipper::Actor.new('22')
77
105
  actor_asdf = Flipper::Actor.new('asdf')
78
106
 
79
- assert_equal true, @adapter.enable(@feature, @actor_gate, @flipper.actor(actor22))
80
- assert_equal true, @adapter.enable(@feature, @actor_gate, @flipper.actor(actor_asdf))
107
+ assert_equal true, @adapter.enable(@feature, @actor_gate, Flipper::Types::Actor.new(actor22))
108
+ assert_equal true, @adapter.enable(@feature, @actor_gate, Flipper::Types::Actor.new(actor_asdf))
81
109
 
82
110
  result = @adapter.get(@feature)
83
111
  assert_equal Set['22', 'asdf'], result[:actors]
84
112
 
85
- assert true, @adapter.disable(@feature, @actor_gate, @flipper.actor(actor22))
113
+ assert true, @adapter.disable(@feature, @actor_gate, Flipper::Types::Actor.new(actor22))
86
114
  result = @adapter.get(@feature)
87
115
  assert_equal Set['asdf'], result[:actors]
88
116
 
89
- assert_equal true, @adapter.disable(@feature, @actor_gate, @flipper.actor(actor_asdf))
117
+ assert_equal true, @adapter.disable(@feature, @actor_gate, Flipper::Types::Actor.new(actor_asdf))
90
118
  result = @adapter.get(@feature)
91
119
  assert_equal Set.new, result[:actors]
92
120
  end
93
121
 
94
122
  def test_can_enable_disable_get_value_for_percentage_of_actors_gate
95
- assert_equal true, @adapter.enable(@feature, @actors_gate, @flipper.actors(15))
123
+ assert_equal true, @adapter.enable(@feature, @actors_gate, Flipper::Types::PercentageOfActors.new(15))
96
124
  result = @adapter.get(@feature)
97
125
  assert_equal '15', result[:percentage_of_actors]
98
126
 
99
- assert_equal true, @adapter.disable(@feature, @actors_gate, @flipper.actors(0))
127
+ assert_equal true, @adapter.disable(@feature, @actors_gate, Flipper::Types::PercentageOfActors.new(0))
100
128
  result = @adapter.get(@feature)
101
129
  assert_equal '0', result[:percentage_of_actors]
102
130
  end
103
131
 
104
132
  def test_can_enable_percentage_of_actors_gate_many_times_and_consistently_return_values
105
133
  (1..100).each do |percentage|
106
- assert_equal true, @adapter.enable(@feature, @actors_gate, @flipper.actors(percentage))
134
+ assert_equal true, @adapter.enable(@feature, @actors_gate, Flipper::Types::PercentageOfActors.new(percentage))
107
135
  result = @adapter.get(@feature)
108
136
  assert_equal percentage.to_s, result[:percentage_of_actors]
109
137
  end
@@ -111,25 +139,25 @@ module Flipper
111
139
 
112
140
  def test_can_disable_percentage_of_actors_gate_many_times_and_consistently_return_values
113
141
  (1..100).each do |percentage|
114
- assert_equal true, @adapter.disable(@feature, @actors_gate, @flipper.actors(percentage))
142
+ assert_equal true, @adapter.disable(@feature, @actors_gate, Flipper::Types::PercentageOfActors.new(percentage))
115
143
  result = @adapter.get(@feature)
116
144
  assert_equal percentage.to_s, result[:percentage_of_actors]
117
145
  end
118
146
  end
119
147
 
120
148
  def test_can_enable_disable_and_get_value_for_percentage_of_time_gate
121
- assert_equal true, @adapter.enable(@feature, @time_gate, @flipper.time(10))
149
+ assert_equal true, @adapter.enable(@feature, @time_gate, Flipper::Types::PercentageOfTime.new(10))
122
150
  result = @adapter.get(@feature)
123
151
  assert_equal '10', result[:percentage_of_time]
124
152
 
125
- assert_equal true, @adapter.disable(@feature, @time_gate, @flipper.time(0))
153
+ assert_equal true, @adapter.disable(@feature, @time_gate, Flipper::Types::PercentageOfTime.new(0))
126
154
  result = @adapter.get(@feature)
127
155
  assert_equal '0', result[:percentage_of_time]
128
156
  end
129
157
 
130
158
  def test_can_enable_percentage_of_time_gate_many_times_and_consistently_return_values
131
159
  (1..100).each do |percentage|
132
- assert_equal true, @adapter.enable(@feature, @time_gate, @flipper.time(percentage))
160
+ assert_equal true, @adapter.enable(@feature, @time_gate, Flipper::Types::PercentageOfTime.new(percentage))
133
161
  result = @adapter.get(@feature)
134
162
  assert_equal percentage.to_s, result[:percentage_of_time]
135
163
  end
@@ -137,21 +165,21 @@ module Flipper
137
165
 
138
166
  def test_can_disable_percentage_of_time_gate_many_times_and_consistently_return_values
139
167
  (1..100).each do |percentage|
140
- assert_equal true, @adapter.disable(@feature, @time_gate, @flipper.time(percentage))
168
+ assert_equal true, @adapter.disable(@feature, @time_gate, Flipper::Types::PercentageOfTime.new(percentage))
141
169
  result = @adapter.get(@feature)
142
170
  assert_equal percentage.to_s, result[:percentage_of_time]
143
171
  end
144
172
  end
145
173
 
146
174
  def test_converts_boolean_value_to_a_string
147
- assert_equal true, @adapter.enable(@feature, @boolean_gate, @flipper.boolean)
175
+ assert_equal true, @adapter.enable(@feature, @boolean_gate, Flipper::Types::Boolean.new)
148
176
  result = @adapter.get(@feature)
149
177
  assert_equal 'true', result[:boolean]
150
178
  end
151
179
 
152
180
  def test_converts_the_actor_value_to_a_string
153
181
  assert_equal true,
154
- @adapter.enable(@feature, @actor_gate, @flipper.actor(Flipper::Actor.new(22)))
182
+ @adapter.enable(@feature, @actor_gate, Flipper::Types::Actor.new(Flipper::Actor.new(22)))
155
183
  result = @adapter.get(@feature)
156
184
  assert_equal Set['22'], result[:actors]
157
185
  end
@@ -163,13 +191,13 @@ module Flipper
163
191
  end
164
192
 
165
193
  def test_converts_percentage_of_time_integer_value_to_a_string
166
- assert_equal true, @adapter.enable(@feature, @time_gate, @flipper.time(10))
194
+ assert_equal true, @adapter.enable(@feature, @time_gate, Flipper::Types::PercentageOfTime.new(10))
167
195
  result = @adapter.get(@feature)
168
196
  assert_equal '10', result[:percentage_of_time]
169
197
  end
170
198
 
171
199
  def test_converts_percentage_of_actors_integer_value_to_a_string
172
- assert_equal true, @adapter.enable(@feature, @actors_gate, @flipper.actors(10))
200
+ assert_equal true, @adapter.enable(@feature, @actors_gate, Flipper::Types::PercentageOfActors.new(10))
173
201
  result = @adapter.get(@feature)
174
202
  assert_equal '10', result[:percentage_of_actors]
175
203
  end
@@ -192,11 +220,11 @@ module Flipper
192
220
 
193
221
  def test_clears_all_the_gate_values_for_the_feature_on_remove
194
222
  actor22 = Flipper::Actor.new('22')
195
- assert_equal true, @adapter.enable(@feature, @boolean_gate, @flipper.boolean)
223
+ assert_equal true, @adapter.enable(@feature, @boolean_gate, Flipper::Types::Boolean.new)
196
224
  assert_equal true, @adapter.enable(@feature, @group_gate, @flipper.group(:admins))
197
- assert_equal true, @adapter.enable(@feature, @actor_gate, @flipper.actor(actor22))
198
- assert_equal true, @adapter.enable(@feature, @actors_gate, @flipper.actors(25))
199
- assert_equal true, @adapter.enable(@feature, @time_gate, @flipper.time(45))
225
+ assert_equal true, @adapter.enable(@feature, @actor_gate, Flipper::Types::Actor.new(actor22))
226
+ assert_equal true, @adapter.enable(@feature, @actors_gate, Flipper::Types::PercentageOfActors.new(25))
227
+ assert_equal true, @adapter.enable(@feature, @time_gate, Flipper::Types::PercentageOfTime.new(45))
200
228
 
201
229
  assert_equal true, @adapter.remove(@feature)
202
230
 
@@ -208,11 +236,11 @@ module Flipper
208
236
  @adapter.add(@feature)
209
237
  assert_includes @adapter.features, @feature.key
210
238
 
211
- assert_equal true, @adapter.enable(@feature, @boolean_gate, @flipper.boolean)
239
+ assert_equal true, @adapter.enable(@feature, @boolean_gate, Flipper::Types::Boolean.new)
212
240
  assert_equal true, @adapter.enable(@feature, @group_gate, @flipper.group(:admins))
213
- assert_equal true, @adapter.enable(@feature, @actor_gate, @flipper.actor(actor22))
214
- assert_equal true, @adapter.enable(@feature, @actors_gate, @flipper.actors(25))
215
- assert_equal true, @adapter.enable(@feature, @time_gate, @flipper.time(45))
241
+ assert_equal true, @adapter.enable(@feature, @actor_gate, Flipper::Types::Actor.new(actor22))
242
+ assert_equal true, @adapter.enable(@feature, @actors_gate, Flipper::Types::PercentageOfActors.new(25))
243
+ assert_equal true, @adapter.enable(@feature, @time_gate, Flipper::Types::PercentageOfTime.new(45))
216
244
 
217
245
  assert_equal true, @adapter.clear(@feature)
218
246
  assert_includes @adapter.features, @feature.key
@@ -225,7 +253,7 @@ module Flipper
225
253
 
226
254
  def test_can_get_multiple_features
227
255
  assert @adapter.add(@flipper[:stats])
228
- assert @adapter.enable(@flipper[:stats], @boolean_gate, @flipper.boolean)
256
+ assert @adapter.enable(@flipper[:stats], @boolean_gate, Flipper::Types::Boolean.new)
229
257
  assert @adapter.add(@flipper[:search])
230
258
 
231
259
  result = @adapter.get_multi([@flipper[:stats], @flipper[:search], @flipper[:other]])
@@ -241,16 +269,16 @@ module Flipper
241
269
 
242
270
  def test_can_get_all_features
243
271
  assert @adapter.add(@flipper[:stats])
244
- assert @adapter.enable(@flipper[:stats], @boolean_gate, @flipper.boolean)
272
+ assert @adapter.enable(@flipper[:stats], @boolean_gate, Flipper::Types::Boolean.new)
245
273
  assert @adapter.add(@flipper[:search])
274
+ @flipper.enable :analytics, Flipper.property(:plan).eq("pro")
246
275
 
247
276
  result = @adapter.get_all
248
- assert_instance_of Hash, result
249
277
 
250
- stats = result["stats"]
251
- search = result["search"]
252
- assert_equal @adapter.default_config.merge(boolean: 'true'), stats
253
- assert_equal @adapter.default_config, search
278
+ assert_instance_of Hash, result
279
+ assert_equal @adapter.default_config.merge(boolean: 'true'), result["stats"]
280
+ assert_equal @adapter.default_config, result["search"]
281
+ assert_equal @adapter.default_config.merge(expression: {"Equal"=>[{"Property"=>["plan"]}, "pro"]}), result["analytics"]
254
282
  end
255
283
 
256
284
  def test_includes_explicitly_disabled_features_when_getting_all_features
@@ -264,8 +292,8 @@ module Flipper
264
292
 
265
293
  def test_can_double_enable_an_actor_without_error
266
294
  actor = Flipper::Actor.new('Flipper::Actor;22')
267
- assert_equal true, @adapter.enable(@feature, @actor_gate, @flipper.actor(actor))
268
- assert_equal true, @adapter.enable(@feature, @actor_gate, @flipper.actor(actor))
295
+ assert_equal true, @adapter.enable(@feature, @actor_gate, Flipper::Types::Actor.new(actor))
296
+ assert_equal true, @adapter.enable(@feature, @actor_gate, Flipper::Types::Actor.new(actor))
269
297
  assert_equal Set['Flipper::Actor;22'], @adapter.get(@feature).fetch(:actors)
270
298
  end
271
299
 
@@ -276,13 +304,13 @@ module Flipper
276
304
  end
277
305
 
278
306
  def test_can_double_enable_percentage_without_error
279
- assert_equal true, @adapter.enable(@feature, @actors_gate, @flipper.actors(25))
280
- assert_equal true, @adapter.enable(@feature, @actors_gate, @flipper.actors(25))
307
+ assert_equal true, @adapter.enable(@feature, @actors_gate, Flipper::Types::PercentageOfActors.new(25))
308
+ assert_equal true, @adapter.enable(@feature, @actors_gate, Flipper::Types::PercentageOfActors.new(25))
281
309
  end
282
310
 
283
311
  def test_can_double_enable_without_error
284
- assert_equal true, @adapter.enable(@feature, @boolean_gate, @flipper.boolean)
285
- assert_equal true, @adapter.enable(@feature, @boolean_gate, @flipper.boolean)
312
+ assert_equal true, @adapter.enable(@feature, @boolean_gate, Flipper::Types::Boolean.new)
313
+ assert_equal true, @adapter.enable(@feature, @boolean_gate, Flipper::Types::Boolean.new)
286
314
  end
287
315
 
288
316
  def test_can_get_all_features_when_there_are_none
@@ -293,13 +321,28 @@ module Flipper
293
321
 
294
322
  def test_clears_other_gate_values_on_enable
295
323
  actor = Flipper::Actor.new('Flipper::Actor;22')
296
- assert_equal true, @adapter.enable(@feature, @actors_gate, @flipper.actors(25))
297
- assert_equal true, @adapter.enable(@feature, @time_gate, @flipper.time(25))
324
+ assert_equal true, @adapter.enable(@feature, @actors_gate, Flipper::Types::PercentageOfActors.new(25))
325
+ assert_equal true, @adapter.enable(@feature, @time_gate, Flipper::Types::PercentageOfTime.new(25))
298
326
  assert_equal true, @adapter.enable(@feature, @group_gate, @flipper.group(:admins))
299
- assert_equal true, @adapter.enable(@feature, @actor_gate, @flipper.actor(actor))
300
- assert_equal true, @adapter.enable(@feature, @boolean_gate, @flipper.boolean(true))
327
+ assert_equal true, @adapter.enable(@feature, @actor_gate, Flipper::Types::Actor.new(actor))
328
+ assert_equal true, @adapter.enable(@feature, @boolean_gate, Flipper::Types::Boolean.new(true))
301
329
  assert_equal @adapter.default_config.merge(boolean: "true"), @adapter.get(@feature)
302
330
  end
331
+
332
+ def test_can_import_and_export
333
+ adapter = Flipper::Adapters::Memory.new
334
+ source_flipper = Flipper.new(adapter)
335
+ source_flipper.enable(:stats)
336
+ export = adapter.export
337
+
338
+ # some adapters cannot import so if they return false lets assert it
339
+ # didn't happen
340
+ if @adapter.import(export)
341
+ assert @flipper[:stats].enabled?
342
+ else
343
+ refute @flipper[:stats].enabled?
344
+ end
345
+ end
303
346
  end
304
347
  end
305
348
  end
@@ -1,4 +1,6 @@
1
1
  require 'set'
2
+ require "flipper/serializers/json"
3
+ require "flipper/serializers/gzip"
2
4
 
3
5
  module Flipper
4
6
  module Typecast
@@ -21,11 +23,9 @@ module Flipper
21
23
  # Returns an Integer representation of the value.
22
24
  # Raises ArgumentError if conversion is not possible.
23
25
  def self.to_integer(value)
24
- if value.respond_to?(:to_i)
25
- value.to_i
26
- else
27
- raise ArgumentError, "#{value.inspect} cannot be converted to an integer"
28
- end
26
+ value.to_i
27
+ rescue NoMethodError
28
+ raise ArgumentError, "#{value.inspect} cannot be converted to an integer"
29
29
  end
30
30
 
31
31
  # Internal: Convert value to a float.
@@ -33,24 +33,30 @@ module Flipper
33
33
  # Returns a Float representation of the value.
34
34
  # Raises ArgumentError if conversion is not possible.
35
35
  def self.to_float(value)
36
- if value.respond_to?(:to_f)
37
- value.to_f
38
- else
39
- raise ArgumentError, "#{value.inspect} cannot be converted to a float"
40
- end
36
+ value.to_f
37
+ rescue NoMethodError
38
+ raise ArgumentError, "#{value.inspect} cannot be converted to a float"
41
39
  end
42
40
 
43
- # Internal: Convert value to a percentage.
41
+ # Internal: Convert value to a number.
44
42
  #
45
43
  # Returns a Integer or Float representation of the value.
46
44
  # Raises ArgumentError if conversion is not possible.
47
- def self.to_percentage(value)
48
- if value.to_s.include?('.'.freeze)
49
- to_float(value)
45
+ def self.to_number(value)
46
+ case value
47
+ when Numeric
48
+ value
49
+ when String
50
+ value.include?('.') ? to_float(value) : to_integer(value)
51
+ when NilClass
52
+ 0
50
53
  else
51
- to_integer(value)
54
+ value.to_f
52
55
  end
56
+ rescue NoMethodError
57
+ raise ArgumentError, "#{value.inspect} cannot be converted to a number"
53
58
  end
59
+ singleton_class.send(:alias_method, :to_percentage, :to_number)
54
60
 
55
61
  # Internal: Convert value to a set.
56
62
  #
@@ -66,5 +72,40 @@ module Flipper
66
72
  raise ArgumentError, "#{value.inspect} cannot be converted to a set"
67
73
  end
68
74
  end
75
+
76
+ def self.features_hash(source)
77
+ normalized_source = {}
78
+ (source || {}).each do |feature_key, gates|
79
+ normalized_source[feature_key] ||= {}
80
+ gates.each do |gate_key, value|
81
+ normalized_value = case value
82
+ when Array, Set
83
+ value.to_set
84
+ when Hash
85
+ value
86
+ else
87
+ value ? value.to_s : value
88
+ end
89
+ normalized_source[feature_key][gate_key.to_sym] = normalized_value
90
+ end
91
+ end
92
+ normalized_source
93
+ end
94
+
95
+ def self.to_json(source)
96
+ Serializers::Json.serialize(source)
97
+ end
98
+
99
+ def self.from_json(source)
100
+ Serializers::Json.deserialize(source)
101
+ end
102
+
103
+ def self.to_gzip(source)
104
+ Serializers::Gzip.serialize(source)
105
+ end
106
+
107
+ def self.from_gzip(source)
108
+ Serializers::Gzip.deserialize(source)
109
+ end
69
110
  end
70
111
  end
@@ -1,35 +1,35 @@
1
1
  module Flipper
2
2
  module Types
3
3
  class Actor < Type
4
- def self.wrappable?(thing)
5
- return false if thing.nil?
6
- thing.respond_to?(:flipper_id)
4
+ def self.wrappable?(actor)
5
+ return false if actor.nil?
6
+ actor.respond_to?(:flipper_id)
7
7
  end
8
8
 
9
- attr_reader :thing
9
+ attr_reader :actor
10
10
 
11
- def initialize(thing)
12
- raise ArgumentError, 'thing cannot be nil' if thing.nil?
11
+ def initialize(actor)
12
+ raise ArgumentError, 'actor cannot be nil' if actor.nil?
13
13
 
14
- unless thing.respond_to?(:flipper_id)
15
- raise ArgumentError, "#{thing.inspect} must respond to flipper_id, but does not"
14
+ unless actor.respond_to?(:flipper_id)
15
+ raise ArgumentError, "#{actor.inspect} must respond to flipper_id, but does not"
16
16
  end
17
17
 
18
- @thing = thing
19
- @value = thing.flipper_id.to_s
18
+ @actor = actor
19
+ @value = actor.flipper_id.to_s
20
20
  end
21
21
 
22
22
  def respond_to?(*args)
23
- super || @thing.respond_to?(*args)
23
+ super || @actor.respond_to?(*args)
24
24
  end
25
25
 
26
26
  if RUBY_VERSION >= '3.0'
27
27
  def method_missing(name, *args, **kwargs, &block)
28
- @thing.send name, *args, **kwargs, &block
28
+ @actor.send name, *args, **kwargs, &block
29
29
  end
30
30
  else
31
31
  def method_missing(name, *args, &block)
32
- @thing.send name, *args, &block
32
+ @actor.send name, *args, &block
33
33
  end
34
34
  end
35
35
  end
@@ -16,16 +16,16 @@ module Flipper
16
16
  @block = block
17
17
  @single_argument = call_with_no_context?(@block)
18
18
  else
19
- @block = ->(_thing, _context) { false }
19
+ @block = ->(actor, context) { false }
20
20
  @single_argument = false
21
21
  end
22
22
  end
23
23
 
24
- def match?(thing, context)
24
+ def match?(actor, context)
25
25
  if @single_argument
26
- @block.call(thing)
26
+ @block.call(actor)
27
27
  else
28
- @block.call(thing, context)
28
+ @block.call(actor, context)
29
29
  end
30
30
  end
31
31
 
@@ -4,7 +4,7 @@ module Flipper
4
4
  module Types
5
5
  class Percentage < Type
6
6
  def initialize(value)
7
- value = Typecast.to_percentage(value)
7
+ value = Typecast.to_number(value)
8
8
 
9
9
  if value < 0 || value > 100
10
10
  raise ArgumentError,
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '0.26.0'.freeze
2
+ VERSION = '1.1.0'.freeze
3
3
  end
data/lib/flipper.rb CHANGED
@@ -56,28 +56,60 @@ module Flipper
56
56
  # Public: All the methods delegated to instance. These should match the
57
57
  # interface of Flipper::DSL.
58
58
  def_delegators :instance,
59
- :enabled?, :enable, :disable, :bool, :boolean,
60
- :enable_actor, :disable_actor, :actor,
59
+ :enabled?, :enable, :disable,
60
+ :enable_expression, :disable_expression,
61
+ :expression, :add_expression, :remove_expression,
62
+ :enable_actor, :disable_actor,
61
63
  :enable_group, :disable_group,
62
64
  :enable_percentage_of_actors, :disable_percentage_of_actors,
63
- :actors, :percentage_of_actors,
64
65
  :enable_percentage_of_time, :disable_percentage_of_time,
65
- :time, :percentage_of_time,
66
66
  :features, :feature, :[], :preload, :preload_all,
67
- :adapter, :add, :exist?, :remove, :import,
68
- :memoize=, :memoizing?,
67
+ :adapter, :add, :exist?, :remove, :import, :export,
68
+ :memoize=, :memoizing?, :read_only?,
69
69
  :sync, :sync_secret # For Flipper::Cloud. Will error for OSS Flipper.
70
70
 
71
+ def any(*args)
72
+ Expression.build({ Any: args.flatten })
73
+ end
74
+
75
+ def all(*args)
76
+ Expression.build({ All: args.flatten })
77
+ end
78
+
79
+ def constant(value)
80
+ Expression.build(value)
81
+ end
82
+
83
+ def property(name)
84
+ Expression.build({ Property: name })
85
+ end
86
+
87
+ def string(value)
88
+ Expression.build({ String: value })
89
+ end
90
+
91
+ def number(value)
92
+ Expression.build({ Number: value })
93
+ end
94
+
95
+ def boolean(value)
96
+ Expression.build({ Boolean: value })
97
+ end
98
+
99
+ def random(max)
100
+ Expression.build({ Random: max })
101
+ end
102
+
71
103
  # Public: Use this to register a group by name.
72
104
  #
73
105
  # name - The Symbol name of the group.
74
106
  # block - The block that should be used to determine if the group matches a
75
- # given thing.
107
+ # given actor.
76
108
  #
77
109
  # Examples
78
110
  #
79
- # Flipper.register(:admins) { |thing|
80
- # thing.respond_to?(:admin?) && thing.admin?
111
+ # Flipper.register(:admins) { |actor|
112
+ # actor.respond_to?(:admin?) && actor.admin?
81
113
  # }
82
114
  #
83
115
  # Returns a Flipper::Group.
@@ -144,7 +176,9 @@ require 'flipper/actor'
144
176
  require 'flipper/adapter'
145
177
  require 'flipper/adapters/memoizable'
146
178
  require 'flipper/adapters/memory'
179
+ require 'flipper/adapters/strict'
147
180
  require 'flipper/adapters/instrumented'
181
+ require 'flipper/adapter_builder'
148
182
  require 'flipper/configuration'
149
183
  require 'flipper/dsl'
150
184
  require 'flipper/errors'
@@ -155,7 +189,9 @@ require 'flipper/instrumenters/noop'
155
189
  require 'flipper/identifier'
156
190
  require 'flipper/middleware/memoizer'
157
191
  require 'flipper/middleware/setup_env'
192
+ require 'flipper/poller'
158
193
  require 'flipper/registry'
194
+ require 'flipper/expression'
159
195
  require 'flipper/type'
160
196
  require 'flipper/types/actor'
161
197
  require 'flipper/types/boolean'
@@ -164,5 +200,6 @@ require 'flipper/types/percentage'
164
200
  require 'flipper/types/percentage_of_actors'
165
201
  require 'flipper/types/percentage_of_time'
166
202
  require 'flipper/typecast'
203
+ require 'flipper/version'
167
204
 
168
- require "flipper/railtie" if defined?(Rails::Railtie)
205
+ require "flipper/engine" if defined?(Rails)
@@ -0,0 +1,46 @@
1
+ {
2
+ "version": 1,
3
+ "features": {
4
+ "search": {
5
+ "boolean": null,
6
+ "actors": [
7
+ "john",
8
+ "another",
9
+ "testing"
10
+ ],
11
+ "percentage_of_actors": null,
12
+ "percentage_of_time": null,
13
+ "groups": [
14
+ "admins"
15
+ ]
16
+ },
17
+ "new_pricing": {
18
+ "boolean": "true",
19
+ "actors": [],
20
+ "percentage_of_actors": null,
21
+ "percentage_of_time": null,
22
+ "groups": []
23
+ },
24
+ "google_analytics_tag": {
25
+ "boolean": null,
26
+ "actors": [],
27
+ "percentage_of_actors": "100",
28
+ "percentage_of_time": null,
29
+ "groups": []
30
+ },
31
+ "help_scout_tag": {
32
+ "boolean": null,
33
+ "actors": [],
34
+ "percentage_of_actors": null,
35
+ "percentage_of_time": "50",
36
+ "groups": []
37
+ },
38
+ "nope": {
39
+ "boolean": null,
40
+ "actors": [],
41
+ "percentage_of_actors": null,
42
+ "percentage_of_time": null,
43
+ "groups": []
44
+ }
45
+ }
46
+ }