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
@@ -0,0 +1,213 @@
1
+ require 'bundler/setup'
2
+ require 'flipper'
3
+
4
+ def assert(value)
5
+ if value
6
+ p value
7
+ else
8
+ puts "Expected true but was #{value}. Please correct."
9
+ exit 1
10
+ end
11
+ end
12
+
13
+ def refute(value)
14
+ if value
15
+ puts "Expected false but was #{value}. Please correct."
16
+ exit 1
17
+ else
18
+ p value
19
+ end
20
+ end
21
+
22
+ def reset
23
+ Flipper.disable_expression :something
24
+ end
25
+
26
+ class User < Struct.new(:id, :flipper_properties)
27
+ include Flipper::Identifier
28
+ end
29
+
30
+ class Org < Struct.new(:id, :flipper_properties)
31
+ include Flipper::Identifier
32
+ end
33
+
34
+ NOW = Time.now.to_i
35
+ DAY = 60 * 60 * 24
36
+
37
+ org = Org.new(1, {
38
+ "type" => "Org",
39
+ "id" => 1,
40
+ "now" => NOW,
41
+ })
42
+
43
+ user = User.new(1, {
44
+ "type" => "User",
45
+ "id" => 1,
46
+ "plan" => "basic",
47
+ "age" => 39,
48
+ "team_user" => true,
49
+ "now" => NOW,
50
+ })
51
+
52
+ admin_user = User.new(2, {
53
+ "type" => "User",
54
+ "id" => 2,
55
+ "admin" => true,
56
+ "team_user" => true,
57
+ "now" => NOW,
58
+ })
59
+
60
+ other_user = User.new(3, {
61
+ "type" => "User",
62
+ "id" => 3,
63
+ "plan" => "plus",
64
+ "age" => 18,
65
+ "org_admin" => true,
66
+ "now" => NOW - DAY,
67
+ })
68
+
69
+ age_expression = Flipper.property(:age).gte(21)
70
+ plan_expression = Flipper.property(:plan).eq("basic")
71
+ admin_expression = Flipper.property(:admin).eq(true)
72
+
73
+ puts "Single Expression"
74
+ refute Flipper.enabled?(:something, user)
75
+
76
+ puts "Enabling single expression"
77
+ Flipper.enable :something, plan_expression
78
+ assert Flipper.enabled?(:something, user)
79
+ refute Flipper.enabled?(:something, admin_user)
80
+ refute Flipper.enabled?(:something, other_user)
81
+
82
+ puts "Disabling single expression"
83
+ reset
84
+ refute Flipper.enabled?(:something, user)
85
+
86
+ puts "\n\nAny Expression"
87
+ any_expression = Flipper.any(plan_expression, age_expression)
88
+ refute Flipper.enabled?(:something, user)
89
+
90
+ puts "Enabling any expression"
91
+ Flipper.enable :something, any_expression
92
+ assert Flipper.enabled?(:something, user)
93
+ refute Flipper.enabled?(:something, admin_user)
94
+ refute Flipper.enabled?(:something, other_user)
95
+
96
+ puts "Disabling any expression"
97
+ reset
98
+ refute Flipper.enabled?(:something, user)
99
+
100
+ puts "\n\nAll Expression"
101
+ all_expression = Flipper.all(plan_expression, age_expression)
102
+ refute Flipper.enabled?(:something, user)
103
+
104
+ puts "Enabling all expression"
105
+ Flipper.enable :something, all_expression
106
+ assert Flipper.enabled?(:something, user)
107
+ refute Flipper.enabled?(:something, admin_user)
108
+ refute Flipper.enabled?(:something, other_user)
109
+
110
+ puts "Disabling all expression"
111
+ reset
112
+ refute Flipper.enabled?(:something, user)
113
+
114
+ puts "\n\nNested Expression"
115
+ nested_expression = Flipper.any(admin_expression, all_expression)
116
+ refute Flipper.enabled?(:something, user)
117
+ refute Flipper.enabled?(:something, admin_user)
118
+ refute Flipper.enabled?(:something, other_user)
119
+
120
+ puts "Enabling nested expression"
121
+ Flipper.enable :something, nested_expression
122
+ assert Flipper.enabled?(:something, user)
123
+ assert Flipper.enabled?(:something, admin_user)
124
+ refute Flipper.enabled?(:something, other_user)
125
+
126
+ puts "Disabling nested expression"
127
+ reset
128
+ refute Flipper.enabled?(:something, user)
129
+ refute Flipper.enabled?(:something, admin_user)
130
+ refute Flipper.enabled?(:something, other_user)
131
+
132
+ puts "\n\nBoolean Expression"
133
+ boolean_expression = Flipper.boolean(true)
134
+ Flipper.enable :something, boolean_expression
135
+ assert Flipper.enabled?(:something)
136
+ assert Flipper.enabled?(:something, user)
137
+ reset
138
+
139
+ puts "\n\nSet of Actors Expression"
140
+ set_of_actors_expression = Flipper.any(
141
+ Flipper.property(:flipper_id).eq("User;1"),
142
+ Flipper.property(:flipper_id).eq("User;3"),
143
+ )
144
+ Flipper.enable :something, set_of_actors_expression
145
+ assert Flipper.enabled?(:something, user)
146
+ assert Flipper.enabled?(:something, other_user)
147
+ refute Flipper.enabled?(:something, admin_user)
148
+ reset
149
+
150
+ puts "\n\n% of Actors Expression"
151
+ percentage_of_actors = Flipper.property(:flipper_id).percentage_of_actors(30)
152
+ Flipper.enable :something, percentage_of_actors
153
+ refute Flipper.enabled?(:something, user)
154
+ refute Flipper.enabled?(:something, other_user)
155
+ assert Flipper.enabled?(:something, admin_user)
156
+ reset
157
+
158
+ puts "\n\n% of Actors Per Type Expression"
159
+ percentage_of_actors_per_type = Flipper.any(
160
+ Flipper.all(
161
+ Flipper.property(:type).eq("User"),
162
+ Flipper.property(:flipper_id).percentage_of_actors(40),
163
+ ),
164
+ Flipper.all(
165
+ Flipper.property(:type).eq("Org"),
166
+ Flipper.property(:flipper_id).percentage_of_actors(10),
167
+ )
168
+ )
169
+ Flipper.enable :something, percentage_of_actors_per_type
170
+ refute Flipper.enabled?(:something, user) # not in the 40% enabled for Users
171
+ assert Flipper.enabled?(:something, other_user)
172
+ assert Flipper.enabled?(:something, admin_user)
173
+ refute Flipper.enabled?(:something, org) # not in the 10% of enabled for Orgs
174
+ reset
175
+
176
+ puts "\n\nPercentage of Time Expression"
177
+ percentage_of_time_expression = Flipper.random(100).lt(50)
178
+ Flipper.enable :something, percentage_of_time_expression
179
+ results = (1..10000).map { |n| Flipper.enabled?(:something, user) }
180
+ enabled, disabled = results.partition { |r| r }
181
+ p enabled: enabled.size
182
+ p disabled: disabled.size
183
+ assert (4_700..5_200).include?(enabled.size)
184
+ assert (4_700..5_200).include?(disabled.size)
185
+ reset
186
+
187
+ puts "\n\nChanging single expression to all expression"
188
+ Flipper.enable :something, plan_expression
189
+ Flipper.enable :something, Flipper.expression(:something).all.add(age_expression)
190
+ assert Flipper.enabled?(:something, user)
191
+ refute Flipper.enabled?(:something, admin_user)
192
+ refute Flipper.enabled?(:something, other_user)
193
+
194
+ puts "\n\nChanging single expression to any expression"
195
+ Flipper.enable :something, plan_expression
196
+ Flipper.enable :something, Flipper.expression(:something).any.add(age_expression, admin_expression)
197
+ assert Flipper.enabled?(:something, user)
198
+ assert Flipper.enabled?(:something, admin_user)
199
+ refute Flipper.enabled?(:something, other_user)
200
+
201
+ puts "\n\nChanging single expression to any expression by adding to condition"
202
+ Flipper.enable :something, plan_expression
203
+ Flipper.enable :something, Flipper.expression(:something).add(admin_expression)
204
+ assert Flipper.enabled?(:something, user)
205
+ assert Flipper.enabled?(:something, admin_user)
206
+ refute Flipper.enabled?(:something, other_user)
207
+
208
+ puts "\n\nEnabling based on time"
209
+ scheduled_time_expression = Flipper.property(:now).gte(NOW)
210
+ Flipper.enable :something, scheduled_time_expression
211
+ assert Flipper.enabled?(:something, user)
212
+ assert Flipper.enabled?(:something, admin_user)
213
+ refute Flipper.enabled?(:something, other_user)
@@ -0,0 +1,59 @@
1
+ require 'bundler/setup'
2
+ require_relative 'active_record/ar_setup'
3
+ require 'flipper'
4
+ require 'flipper/adapters/redis'
5
+ require 'flipper/adapters/active_record'
6
+
7
+ # Say you have production...
8
+ production_adapter = Flipper::Adapters::Memory.new
9
+ production = Flipper.new(production_adapter)
10
+
11
+ # And production has some stuff enabled...
12
+ production.enable(:search)
13
+ production.enable_percentage_of_time(:verbose_logging, 5)
14
+ production.enable_percentage_of_actors(:new_feature, 5)
15
+ production.enable_actor(:issues, Flipper::Actor.new('1'))
16
+ production.enable_actor(:issues, Flipper::Actor.new('2'))
17
+ production.enable_group(:request_tracing, :staff)
18
+
19
+ # And you would like to mirror production to staging...
20
+ staging_adapter = Flipper::Adapters::Memory.new
21
+ staging = Flipper.new(staging_adapter)
22
+ staging_export = staging.export
23
+
24
+ puts "Here is the state of the world for staging and production..."
25
+ puts "Staging"
26
+ staging.features.each do |feature|
27
+ pp feature: feature.key, values: feature.gate_values
28
+ end
29
+ puts "Production"
30
+ production.features.each do |feature|
31
+ pp feature: feature.key, values: feature.gate_values
32
+ end
33
+
34
+ # NOTE: This wipes active record clean and copies features/gates from redis into active record.
35
+ puts "Mirroring production to staging..."
36
+ staging.import(production.export)
37
+ puts "Staging is now identical to Production."
38
+
39
+ puts "Staging"
40
+ staging.features.each do |feature|
41
+ pp feature: feature.key, values: feature.gate_values
42
+ end
43
+ puts "Production"
44
+ production.features.each do |feature|
45
+ pp feature: feature.key, values: feature.gate_values
46
+ end
47
+
48
+ puts "Restoring staging to original state..."
49
+ staging.import(staging_export)
50
+ puts "Staging restored."
51
+
52
+ puts "Staging"
53
+ staging.features.each do |feature|
54
+ pp feature: feature.key, values: feature.gate_values
55
+ end
56
+ puts "Production"
57
+ production.features.each do |feature|
58
+ pp feature: feature.key, values: feature.gate_values
59
+ end
@@ -0,0 +1,18 @@
1
+ require 'bundler/setup'
2
+ require 'flipper'
3
+
4
+ adapter = Flipper::Adapters::Strict.new(Flipper::Adapters::Memory.new)
5
+ flipper = Flipper.new(adapter)
6
+
7
+ begin
8
+ puts "Checking :unknown_feature, which should raise an error."
9
+ flipper.enabled?(:unknown_feature)
10
+ warn "An error was not raised, but should have been"
11
+ exit 1
12
+ rescue Flipper::Adapters::Strict::NotFound => exception
13
+ puts "Ok, the exepcted error was raised: #{exception.message}"
14
+ end
15
+
16
+ puts "Flipper.add(:new_feature)"
17
+ flipper.add(:new_feature)
18
+ puts "Flipper.enabled?(:new_feature) => #{flipper.enabled?(:new_feature)}"
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/flipper/version', __FILE__)
3
+ require File.expand_path('../lib/flipper/metadata', __FILE__)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.authors = ['John Nunemaker']
7
+ gem.email = 'support@flippercloud.io'
8
+ gem.summary = '[DEPRECATED] This gem has been merged into the `flipper` gem'
9
+ gem.license = 'MIT'
10
+ gem.homepage = 'https://www.flippercloud.io'
11
+
12
+ gem.files = [ 'lib/flipper-cloud.rb', 'lib/flipper/version.rb' ]
13
+ gem.name = 'flipper-cloud'
14
+ gem.require_paths = ['lib']
15
+ gem.version = Flipper::VERSION
16
+ gem.metadata = Flipper::METADATA
17
+
18
+ gem.add_dependency 'flipper', "~> #{Flipper::VERSION}"
19
+ end
data/flipper.gemspec CHANGED
@@ -13,7 +13,6 @@ end
13
13
 
14
14
  ignored_files = plugin_files
15
15
  ignored_files << Dir['script/*']
16
- ignored_files << '.travis.yml'
17
16
  ignored_files << '.gitignore'
18
17
  ignored_files << 'Guardfile'
19
18
  ignored_files.flatten!.uniq!
@@ -23,12 +22,11 @@ ignored_test_files.flatten!.uniq!
23
22
 
24
23
  Gem::Specification.new do |gem|
25
24
  gem.authors = ['John Nunemaker']
26
- gem.email = ['nunemaker@gmail.com']
27
- gem.summary = 'Feature flipper for ANYTHING'
28
- gem.homepage = 'https://github.com/jnunemaker/flipper'
25
+ gem.email = 'support@flippercloud.io'
26
+ gem.summary = 'Beautiful, performant feature flags for Ruby.'
27
+ gem.homepage = 'https://www.flippercloud.io/docs'
29
28
  gem.license = 'MIT'
30
29
 
31
- gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
32
30
  gem.files = `git ls-files`.split("\n") - ignored_files + ['lib/flipper/version.rb']
33
31
  gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") - ignored_test_files
34
32
  gem.name = 'flipper'
data/lib/flipper/actor.rb CHANGED
@@ -2,14 +2,17 @@
2
2
  # to Flipper::Feature#enabled?.
3
3
  module Flipper
4
4
  class Actor
5
- attr_reader :flipper_id
5
+ attr_reader :flipper_id, :flipper_properties
6
6
 
7
- def initialize(flipper_id)
7
+ def initialize(flipper_id, flipper_properties = {})
8
8
  @flipper_id = flipper_id
9
+ @flipper_properties = flipper_properties
9
10
  end
10
11
 
11
12
  def eql?(other)
12
- self.class.eql?(other.class) && @flipper_id == other.flipper_id
13
+ self.class.eql?(other.class) &&
14
+ @flipper_id == other.flipper_id &&
15
+ @flipper_properties == other.flipper_properties
13
16
  end
14
17
  alias_method :==, :eql?
15
18
 
@@ -1,7 +1,3 @@
1
- require "set"
2
- require "flipper/feature"
3
- require "flipper/adapters/sync/synchronizer"
4
-
5
1
  module Flipper
6
2
  # Adding a module include so we have some hooks for stuff down the road
7
3
  module Adapter
@@ -16,10 +12,20 @@ module Flipper
16
12
  boolean: nil,
17
13
  groups: Set.new,
18
14
  actors: Set.new,
15
+ expression: nil,
19
16
  percentage_of_actors: nil,
20
17
  percentage_of_time: nil,
21
18
  }
22
19
  end
20
+
21
+ def from(source)
22
+ return source if source.is_a?(Flipper::Adapter)
23
+ source.adapter
24
+ end
25
+ end
26
+
27
+ def read_only?
28
+ false
23
29
  end
24
30
 
25
31
  # Public: Get all features and gate values in one call. Defaults to one call
@@ -43,14 +49,34 @@ module Flipper
43
49
 
44
50
  # Public: Ensure that adapter is in sync with source adapter provided.
45
51
  #
46
- # Returns result of Synchronizer#call.
47
- def import(source_adapter)
48
- Adapters::Sync::Synchronizer.new(self, source_adapter, raise: true).call
52
+ # source - The source dsl, adapter or export to import.
53
+ #
54
+ # Returns true if successful.
55
+ def import(source)
56
+ Adapters::Sync::Synchronizer.new(self, self.class.from(source), raise: true).call
57
+ true
58
+ end
59
+
60
+ # Public: Exports the adapter in a given format for a given format version.
61
+ #
62
+ # Returns a Flipper::Export instance.
63
+ def export(format: :json, version: 1)
64
+ Flipper::Exporter.build(format: format, version: version).call(self)
49
65
  end
50
66
 
51
67
  # Public: Default config for a feature's gate values.
52
68
  def default_config
53
69
  self.class.default_config
54
70
  end
71
+
72
+ # Public: default name of the adapter
73
+ def name
74
+ @name ||= self.class.name.split('::').last.split(/(?=[A-Z])/).join('_').downcase.to_sym
75
+ end
55
76
  end
56
77
  end
78
+
79
+ require "set"
80
+ require "flipper/exporter"
81
+ require "flipper/feature"
82
+ require "flipper/adapters/sync/synchronizer"
@@ -0,0 +1,44 @@
1
+ module Flipper
2
+ # Builds an adapter from a stack of adapters.
3
+ #
4
+ # adapter = Flipper::AdapterBuilder.new do
5
+ # use Flipper::Adapters::Strict
6
+ # use Flipper::Adapters::Memoizer
7
+ # store Flipper::Adapters::Memory
8
+ # end.to_adapter
9
+ #
10
+ class AdapterBuilder
11
+ def initialize(&block)
12
+ @stack = []
13
+
14
+ # Default to memory adapter
15
+ store Flipper::Adapters::Memory
16
+
17
+ block.arity == 0 ? instance_eval(&block) : block.call(self) if block
18
+ end
19
+
20
+ if RUBY_VERSION >= '3.0'
21
+ def use(klass, *args, **kwargs, &block)
22
+ @stack.push ->(adapter) { klass.new(adapter, *args, **kwargs, &block) }
23
+ end
24
+ else
25
+ def use(klass, *args, &block)
26
+ @stack.push ->(adapter) { klass.new(adapter, *args, &block) }
27
+ end
28
+ end
29
+
30
+ if RUBY_VERSION >= '3.0'
31
+ def store(adapter, *args, **kwargs, &block)
32
+ @store = adapter.respond_to?(:call) ? adapter : -> { adapter.new(*args, **kwargs, &block) }
33
+ end
34
+ else
35
+ def store(adapter, *args, &block)
36
+ @store = adapter.respond_to?(:call) ? adapter : -> { adapter.new(*args, &block) }
37
+ end
38
+ end
39
+
40
+ def to_adapter
41
+ @stack.reverse.inject(@store.call) { |adapter, wrapper| wrapper.call(adapter) }
42
+ end
43
+ end
44
+ end
@@ -3,8 +3,7 @@ module Flipper
3
3
  class DualWrite
4
4
  include ::Flipper::Adapter
5
5
 
6
- # Public: The name of the adapter.
7
- attr_reader :name, :local, :remote
6
+ attr_reader :local, :remote
8
7
 
9
8
  # Public: Build a new sync instance.
10
9
  #
@@ -12,7 +11,6 @@ module Flipper
12
11
  # remote - The remote flipper adapter that writes should go to first (in
13
12
  # addition to the local adapter).
14
13
  def initialize(local, remote, options = {})
15
- @name = :dual_write
16
14
  @local = local
17
15
  @remote = remote
18
16
  end
@@ -3,9 +3,6 @@ module Flipper
3
3
  class Failover
4
4
  include ::Flipper::Adapter
5
5
 
6
- # Public: The name of the adapter.
7
- attr_reader :name
8
-
9
6
  # Public: Build a new failover instance.
10
7
  #
11
8
  # primary - The primary flipper adapter.
@@ -17,7 +14,6 @@ module Flipper
17
14
  # :errors - Array of exception types for which to failover
18
15
 
19
16
  def initialize(primary, secondary, options = {})
20
- @name = :failover
21
17
  @primary = primary
22
18
  @secondary = secondary
23
19
 
@@ -3,9 +3,6 @@ module Flipper
3
3
  class Failsafe
4
4
  include ::Flipper::Adapter
5
5
 
6
- # Public: The name of the adapter.
7
- attr_reader :name
8
-
9
6
  # Public: Build a new Failsafe instance.
10
7
  #
11
8
  # adapter - Flipper adapter to guard.
@@ -15,7 +12,6 @@ module Flipper
15
12
  def initialize(adapter, options = {})
16
13
  @adapter = adapter
17
14
  @errors = options.fetch(:errors, [StandardError])
18
- @name = :failsafe
19
15
  end
20
16
 
21
17
  def features
@@ -14,6 +14,12 @@ module Flipper
14
14
 
15
15
  HTTPS_SCHEME = "https".freeze
16
16
 
17
+ CLIENT_FRAMEWORKS = {
18
+ rails: -> { Rails.version if defined?(Rails) },
19
+ sinatra: -> { Sinatra::VERSION if defined?(Sinatra) },
20
+ hanami: -> { Hanami::VERSION if defined?(Hanami) },
21
+ }
22
+
17
23
  attr_reader :uri, :headers
18
24
  attr_reader :basic_auth_username, :basic_auth_password
19
25
  attr_reader :read_timeout, :open_timeout, :write_timeout, :max_retries, :debug_output
@@ -30,6 +36,10 @@ module Flipper
30
36
  @debug_output = options[:debug_output]
31
37
  end
32
38
 
39
+ def add_header(key, value)
40
+ @headers[key] = value
41
+ end
42
+
33
43
  def get(path)
34
44
  perform Net::HTTP::Get, path, @headers
35
45
  end
@@ -77,18 +87,23 @@ module Flipper
77
87
 
78
88
  def build_request(http_method, uri, headers, options)
79
89
  request_headers = {
80
- "Client-Language" => "ruby",
81
- "Client-Language-Version" => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})",
82
- "Client-Platform" => RUBY_PLATFORM,
83
- "Client-Engine" => defined?(RUBY_ENGINE) ? RUBY_ENGINE : "",
84
- "Client-Pid" => Process.pid.to_s,
85
- "Client-Thread" => Thread.current.object_id.to_s,
86
- "Client-Hostname" => Socket.gethostname,
90
+ client_language: "ruby",
91
+ client_language_version: "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})",
92
+ client_platform: RUBY_PLATFORM,
93
+ client_engine: defined?(RUBY_ENGINE) ? RUBY_ENGINE : "",
94
+ client_pid: Process.pid.to_s,
95
+ client_thread: Thread.current.object_id.to_s,
96
+ client_hostname: Socket.gethostname,
87
97
  }.merge(headers)
88
98
 
89
99
  body = options[:body]
90
100
  request = http_method.new(uri.request_uri)
91
101
  request.initialize_http_header(request_headers)
102
+
103
+ client_frameworks.each do |framework, version|
104
+ request.add_field("Client-Framework", [framework, version].join("="))
105
+ end
106
+
92
107
  request.body = body if body
93
108
 
94
109
  if @basic_auth_username && @basic_auth_password
@@ -97,6 +112,10 @@ module Flipper
97
112
 
98
113
  request
99
114
  end
115
+
116
+ def client_frameworks
117
+ CLIENT_FRAMEWORKS.transform_values { |detect| detect.call rescue nil }.compact
118
+ end
100
119
  end
101
120
  end
102
121
  end
@@ -11,7 +11,7 @@ module Flipper
11
11
  message = "Failed with status: #{response.code}"
12
12
 
13
13
  begin
14
- data = JSON.parse(response.body)
14
+ data = Typecast.from_json(response.body)
15
15
 
16
16
  if error_message = data["message"]
17
17
  message << "\n\n#{data["message"]}"