flipper 0.24.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (226) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/dependabot.yml +6 -0
  4. data/.github/workflows/ci.yml +45 -14
  5. data/.github/workflows/examples.yml +39 -16
  6. data/Changelog.md +2 -443
  7. data/Gemfile +19 -11
  8. data/README.md +31 -27
  9. data/Rakefile +6 -4
  10. data/benchmark/enabled_ips.rb +10 -0
  11. data/benchmark/enabled_multiple_actors_ips.rb +20 -0
  12. data/benchmark/enabled_profile.rb +20 -0
  13. data/benchmark/instrumentation_ips.rb +21 -0
  14. data/benchmark/typecast_ips.rb +27 -0
  15. data/docs/images/banner.jpg +0 -0
  16. data/docs/images/flipper_cloud.png +0 -0
  17. data/examples/api/basic.ru +3 -4
  18. data/examples/api/custom_memoized.ru +3 -4
  19. data/examples/api/memoized.ru +3 -4
  20. data/examples/cloud/app.ru +12 -0
  21. data/examples/cloud/backoff_policy.rb +13 -0
  22. data/examples/cloud/basic.rb +22 -0
  23. data/examples/cloud/cloud_setup.rb +20 -0
  24. data/examples/cloud/forked.rb +36 -0
  25. data/examples/cloud/import.rb +17 -0
  26. data/examples/cloud/threaded.rb +33 -0
  27. data/examples/dsl.rb +1 -15
  28. data/examples/enabled_for_actor.rb +4 -2
  29. data/examples/expressions.rb +213 -0
  30. data/examples/instrumentation.rb +1 -0
  31. data/examples/instrumentation_last_accessed_at.rb +1 -0
  32. data/examples/mirroring.rb +59 -0
  33. data/examples/strict.rb +18 -0
  34. data/exe/flipper +5 -0
  35. data/flipper-cloud.gemspec +19 -0
  36. data/flipper.gemspec +10 -6
  37. data/lib/flipper/actor.rb +6 -3
  38. data/lib/flipper/adapter.rb +33 -7
  39. data/lib/flipper/adapter_builder.rb +44 -0
  40. data/lib/flipper/adapters/actor_limit.rb +28 -0
  41. data/lib/flipper/adapters/cache_base.rb +143 -0
  42. data/lib/flipper/adapters/dual_write.rb +1 -3
  43. data/lib/flipper/adapters/failover.rb +0 -4
  44. data/lib/flipper/adapters/failsafe.rb +72 -0
  45. data/lib/flipper/adapters/http/client.rb +44 -20
  46. data/lib/flipper/adapters/http/error.rb +1 -1
  47. data/lib/flipper/adapters/http.rb +31 -16
  48. data/lib/flipper/adapters/instrumented.rb +25 -6
  49. data/lib/flipper/adapters/memoizable.rb +33 -21
  50. data/lib/flipper/adapters/memory.rb +81 -46
  51. data/lib/flipper/adapters/operation_logger.rb +17 -78
  52. data/lib/flipper/adapters/poll/poller.rb +2 -0
  53. data/lib/flipper/adapters/poll.rb +37 -0
  54. data/lib/flipper/adapters/pstore.rb +17 -11
  55. data/lib/flipper/adapters/read_only.rb +8 -41
  56. data/lib/flipper/adapters/strict.rb +45 -0
  57. data/lib/flipper/adapters/sync/feature_synchronizer.rb +10 -1
  58. data/lib/flipper/adapters/sync.rb +0 -4
  59. data/lib/flipper/adapters/wrapper.rb +54 -0
  60. data/lib/flipper/cli.rb +263 -0
  61. data/lib/flipper/cloud/configuration.rb +263 -0
  62. data/lib/flipper/cloud/dsl.rb +27 -0
  63. data/lib/flipper/cloud/message_verifier.rb +95 -0
  64. data/lib/flipper/cloud/middleware.rb +63 -0
  65. data/lib/flipper/cloud/routes.rb +14 -0
  66. data/lib/flipper/cloud/telemetry/backoff_policy.rb +93 -0
  67. data/lib/flipper/cloud/telemetry/instrumenter.rb +22 -0
  68. data/lib/flipper/cloud/telemetry/metric.rb +39 -0
  69. data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
  70. data/lib/flipper/cloud/telemetry/submitter.rb +98 -0
  71. data/lib/flipper/cloud/telemetry.rb +191 -0
  72. data/lib/flipper/cloud.rb +53 -0
  73. data/lib/flipper/configuration.rb +25 -4
  74. data/lib/flipper/dsl.rb +46 -45
  75. data/lib/flipper/engine.rb +102 -0
  76. data/lib/flipper/errors.rb +3 -20
  77. data/lib/flipper/export.rb +26 -0
  78. data/lib/flipper/exporter.rb +17 -0
  79. data/lib/flipper/exporters/json/export.rb +32 -0
  80. data/lib/flipper/exporters/json/v1.rb +33 -0
  81. data/lib/flipper/expression/builder.rb +73 -0
  82. data/lib/flipper/expression/constant.rb +25 -0
  83. data/lib/flipper/expression.rb +71 -0
  84. data/lib/flipper/expressions/all.rb +11 -0
  85. data/lib/flipper/expressions/any.rb +9 -0
  86. data/lib/flipper/expressions/boolean.rb +9 -0
  87. data/lib/flipper/expressions/comparable.rb +13 -0
  88. data/lib/flipper/expressions/duration.rb +28 -0
  89. data/lib/flipper/expressions/equal.rb +9 -0
  90. data/lib/flipper/expressions/greater_than.rb +9 -0
  91. data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
  92. data/lib/flipper/expressions/less_than.rb +9 -0
  93. data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
  94. data/lib/flipper/expressions/not_equal.rb +9 -0
  95. data/lib/flipper/expressions/now.rb +9 -0
  96. data/lib/flipper/expressions/number.rb +9 -0
  97. data/lib/flipper/expressions/percentage.rb +9 -0
  98. data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
  99. data/lib/flipper/expressions/property.rb +9 -0
  100. data/lib/flipper/expressions/random.rb +9 -0
  101. data/lib/flipper/expressions/string.rb +9 -0
  102. data/lib/flipper/expressions/time.rb +9 -0
  103. data/lib/flipper/feature.rb +87 -26
  104. data/lib/flipper/feature_check_context.rb +10 -6
  105. data/lib/flipper/gate.rb +13 -11
  106. data/lib/flipper/gate_values.rb +5 -18
  107. data/lib/flipper/gates/actor.rb +10 -17
  108. data/lib/flipper/gates/boolean.rb +1 -1
  109. data/lib/flipper/gates/expression.rb +75 -0
  110. data/lib/flipper/gates/group.rb +5 -7
  111. data/lib/flipper/gates/percentage_of_actors.rb +10 -13
  112. data/lib/flipper/gates/percentage_of_time.rb +1 -2
  113. data/lib/flipper/identifier.rb +2 -2
  114. data/lib/flipper/instrumentation/log_subscriber.rb +34 -6
  115. data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
  116. data/lib/flipper/instrumentation/subscriber.rb +8 -1
  117. data/lib/flipper/metadata.rb +7 -1
  118. data/lib/flipper/middleware/memoizer.rb +28 -22
  119. data/lib/flipper/model/active_record.rb +23 -0
  120. data/lib/flipper/poller.rb +118 -0
  121. data/lib/flipper/serializers/gzip.rb +22 -0
  122. data/lib/flipper/serializers/json.rb +17 -0
  123. data/lib/flipper/spec/shared_adapter_specs.rb +105 -63
  124. data/lib/flipper/test/shared_adapter_test.rb +101 -58
  125. data/lib/flipper/test_help.rb +43 -0
  126. data/lib/flipper/typecast.rb +59 -18
  127. data/lib/flipper/types/actor.rb +13 -13
  128. data/lib/flipper/types/group.rb +4 -4
  129. data/lib/flipper/types/percentage.rb +1 -1
  130. data/lib/flipper/version.rb +11 -1
  131. data/lib/flipper.rb +50 -11
  132. data/lib/generators/flipper/setup_generator.rb +63 -0
  133. data/lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb +22 -0
  134. data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb +18 -0
  135. data/lib/generators/flipper/update_generator.rb +35 -0
  136. data/package-lock.json +41 -0
  137. data/package.json +10 -0
  138. data/spec/fixtures/environment.rb +1 -0
  139. data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
  140. data/spec/flipper/adapter_builder_spec.rb +72 -0
  141. data/spec/flipper/adapter_spec.rb +30 -2
  142. data/spec/flipper/adapters/actor_limit_spec.rb +20 -0
  143. data/spec/flipper/adapters/dual_write_spec.rb +2 -2
  144. data/spec/flipper/adapters/failsafe_spec.rb +58 -0
  145. data/spec/flipper/adapters/http/client_spec.rb +61 -0
  146. data/spec/flipper/adapters/http_spec.rb +137 -55
  147. data/spec/flipper/adapters/instrumented_spec.rb +29 -11
  148. data/spec/flipper/adapters/memoizable_spec.rb +51 -31
  149. data/spec/flipper/adapters/memory_spec.rb +14 -3
  150. data/spec/flipper/adapters/operation_logger_spec.rb +31 -12
  151. data/spec/flipper/adapters/read_only_spec.rb +32 -17
  152. data/spec/flipper/adapters/strict_spec.rb +64 -0
  153. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +27 -0
  154. data/spec/flipper/cli_spec.rb +164 -0
  155. data/spec/flipper/cloud/configuration_spec.rb +251 -0
  156. data/spec/flipper/cloud/dsl_spec.rb +82 -0
  157. data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
  158. data/spec/flipper/cloud/middleware_spec.rb +289 -0
  159. data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +107 -0
  160. data/spec/flipper/cloud/telemetry/metric_spec.rb +87 -0
  161. data/spec/flipper/cloud/telemetry/metric_storage_spec.rb +58 -0
  162. data/spec/flipper/cloud/telemetry/submitter_spec.rb +145 -0
  163. data/spec/flipper/cloud/telemetry_spec.rb +208 -0
  164. data/spec/flipper/cloud_spec.rb +181 -0
  165. data/spec/flipper/configuration_spec.rb +17 -0
  166. data/spec/flipper/dsl_spec.rb +54 -73
  167. data/spec/flipper/engine_spec.rb +373 -0
  168. data/spec/flipper/export_spec.rb +13 -0
  169. data/spec/flipper/exporter_spec.rb +16 -0
  170. data/spec/flipper/exporters/json/export_spec.rb +60 -0
  171. data/spec/flipper/exporters/json/v1_spec.rb +33 -0
  172. data/spec/flipper/expression/builder_spec.rb +248 -0
  173. data/spec/flipper/expression_spec.rb +188 -0
  174. data/spec/flipper/expressions/all_spec.rb +15 -0
  175. data/spec/flipper/expressions/any_spec.rb +15 -0
  176. data/spec/flipper/expressions/boolean_spec.rb +15 -0
  177. data/spec/flipper/expressions/duration_spec.rb +43 -0
  178. data/spec/flipper/expressions/equal_spec.rb +24 -0
  179. data/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +28 -0
  180. data/spec/flipper/expressions/greater_than_spec.rb +28 -0
  181. data/spec/flipper/expressions/less_than_or_equal_to_spec.rb +28 -0
  182. data/spec/flipper/expressions/less_than_spec.rb +32 -0
  183. data/spec/flipper/expressions/not_equal_spec.rb +15 -0
  184. data/spec/flipper/expressions/now_spec.rb +11 -0
  185. data/spec/flipper/expressions/number_spec.rb +21 -0
  186. data/spec/flipper/expressions/percentage_of_actors_spec.rb +20 -0
  187. data/spec/flipper/expressions/percentage_spec.rb +15 -0
  188. data/spec/flipper/expressions/property_spec.rb +13 -0
  189. data/spec/flipper/expressions/random_spec.rb +9 -0
  190. data/spec/flipper/expressions/string_spec.rb +11 -0
  191. data/spec/flipper/expressions/time_spec.rb +13 -0
  192. data/spec/flipper/feature_check_context_spec.rb +17 -17
  193. data/spec/flipper/feature_spec.rb +436 -33
  194. data/spec/flipper/gate_values_spec.rb +2 -33
  195. data/spec/flipper/gates/boolean_spec.rb +1 -1
  196. data/spec/flipper/gates/expression_spec.rb +108 -0
  197. data/spec/flipper/gates/group_spec.rb +2 -3
  198. data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -5
  199. data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
  200. data/spec/flipper/identifier_spec.rb +4 -5
  201. data/spec/flipper/instrumentation/log_subscriber_spec.rb +23 -6
  202. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +25 -1
  203. data/spec/flipper/middleware/memoizer_spec.rb +74 -24
  204. data/spec/flipper/model/active_record_spec.rb +61 -0
  205. data/spec/flipper/poller_spec.rb +47 -0
  206. data/spec/flipper/serializers/gzip_spec.rb +13 -0
  207. data/spec/flipper/serializers/json_spec.rb +13 -0
  208. data/spec/flipper/typecast_spec.rb +121 -6
  209. data/spec/flipper/types/actor_spec.rb +63 -46
  210. data/spec/flipper/types/group_spec.rb +2 -2
  211. data/spec/flipper_integration_spec.rb +168 -58
  212. data/spec/flipper_spec.rb +93 -29
  213. data/spec/spec_helper.rb +8 -14
  214. data/spec/support/actor_names.yml +1 -0
  215. data/spec/support/fail_on_output.rb +8 -0
  216. data/spec/support/fake_backoff_policy.rb +15 -0
  217. data/spec/support/skippable.rb +18 -0
  218. data/spec/support/spec_helpers.rb +23 -8
  219. data/test/adapters/actor_limit_test.rb +20 -0
  220. data/test_rails/generators/flipper/setup_generator_test.rb +64 -0
  221. data/test_rails/generators/flipper/update_generator_test.rb +96 -0
  222. data/test_rails/helper.rb +19 -2
  223. data/test_rails/system/test_help_test.rb +51 -0
  224. metadata +223 -19
  225. data/lib/flipper/railtie.rb +0 -47
  226. data/spec/flipper/railtie_spec.rb +0 -73
@@ -0,0 +1,20 @@
1
+ require 'bundler/setup'
2
+ require 'flipper'
3
+ require 'benchmark/ips'
4
+
5
+ actor1 = Flipper::Actor.new("User;1")
6
+ actor2 = Flipper::Actor.new("User;2")
7
+ actor3 = Flipper::Actor.new("User;3")
8
+ actor4 = Flipper::Actor.new("User;4")
9
+ actor5 = Flipper::Actor.new("User;5")
10
+ actor6 = Flipper::Actor.new("User;6")
11
+ actor7 = Flipper::Actor.new("User;7")
12
+ actor8 = Flipper::Actor.new("User;8")
13
+
14
+ actors = [actor1, actor2, actor3, actor4, actor5, actor6, actor7, actor8]
15
+
16
+ Benchmark.ips do |x|
17
+ x.report("with array of actors") { Flipper.enabled?(:foo, actors) }
18
+ x.report("with multiple enabled? checks") { actors.each { |actor| Flipper.enabled?(:foo, actor) } }
19
+ x.compare!
20
+ end
@@ -0,0 +1,20 @@
1
+ require 'bundler/setup'
2
+ require 'flipper'
3
+ require 'stackprof'
4
+ require 'benchmark/ips'
5
+
6
+ flipper = Flipper.new(Flipper::Adapters::Memory.new)
7
+ feature = flipper.feature(:foo)
8
+ actor = Flipper::Actor.new("User;1")
9
+
10
+ profile = StackProf.run(mode: :wall, interval: 1_000) do
11
+ 2_000_000.times do
12
+ feature.enabled?(actor)
13
+ end
14
+ end
15
+
16
+ result = StackProf::Report.new(profile)
17
+ puts
18
+ result.print_text
19
+ puts "\n\n\n"
20
+ result.print_method(/Flipper::Feature#enabled?/)
@@ -0,0 +1,21 @@
1
+ require 'bundler/setup'
2
+ require 'flipper'
3
+ require 'active_support/notifications'
4
+ require 'active_support/isolated_execution_state'
5
+ require 'benchmark/ips'
6
+
7
+ class FlipperSubscriber
8
+ def call(name, start, finish, id, payload)
9
+ end
10
+
11
+ ActiveSupport::Notifications.subscribe(/flipper/, new)
12
+ end
13
+
14
+ actor = Flipper::Actor.new("User;1")
15
+ bare = Flipper.new(Flipper::Adapters::Memory.new)
16
+ instrumented = Flipper.new(Flipper::Adapters::Memory.new, instrumenter: ActiveSupport::Notifications)
17
+
18
+ Benchmark.ips do |x|
19
+ x.report("with instrumentation") { instrumented.enabled?(:foo, actor) }
20
+ x.report("without instrumentation") { bare.enabled?(:foo, actor) }
21
+ end
@@ -0,0 +1,27 @@
1
+ require 'bundler/setup'
2
+ require 'flipper'
3
+ require 'benchmark/ips'
4
+
5
+ Benchmark.ips do |x|
6
+ x.report("Typecast.to_boolean true") { Flipper::Typecast.to_boolean(true) }
7
+ x.report("Typecast.to_boolean 1") { Flipper::Typecast.to_boolean(1) }
8
+ x.report("Typecast.to_boolean 'true'") { Flipper::Typecast.to_boolean('true'.freeze) }
9
+ x.report("Typecast.to_boolean '1'") { Flipper::Typecast.to_boolean('1'.freeze) }
10
+ x.report("Typecast.to_boolean false") { Flipper::Typecast.to_boolean(false) }
11
+
12
+ x.report("Typecast.to_integer 1") { Flipper::Typecast.to_integer(1) }
13
+ x.report("Typecast.to_integer '1'") { Flipper::Typecast.to_integer('1') }
14
+
15
+ x.report("Typecast.to_float 1") { Flipper::Typecast.to_float(1) }
16
+ x.report("Typecast.to_float '1'") { Flipper::Typecast.to_float('1'.freeze) }
17
+ x.report("Typecast.to_float 1.01") { Flipper::Typecast.to_float(1) }
18
+ x.report("Typecast.to_float '1.01'") { Flipper::Typecast.to_float('1'.freeze) }
19
+
20
+ x.report("Typecast.to_number 1") { Flipper::Typecast.to_number(1) }
21
+ x.report("Typecast.to_number 1.1") { Flipper::Typecast.to_number(1.1) }
22
+ x.report("Typecast.to_number '1'") { Flipper::Typecast.to_number('1'.freeze) }
23
+ x.report("Typecast.to_number '1.1'") { Flipper::Typecast.to_number('1.1'.freeze) }
24
+ x.report("Typecast.to_number nil") { Flipper::Typecast.to_number(nil) }
25
+ time = Time.now
26
+ x.report("Typecast.to_number Time.now") { Flipper::Typecast.to_number(time) }
27
+ end
Binary file
Binary file
@@ -1,19 +1,18 @@
1
1
  #
2
2
  # Usage:
3
- # # if you want it to not reload and be really fast
4
3
  # bin/rackup examples/api/basic.ru -p 9999
5
4
  #
6
- # # if you want reloading
7
- # bin/shotgun examples/api/basic.ru -p 9999
8
- #
9
5
  # http://localhost:9999/
10
6
  #
11
7
 
12
8
  require 'bundler/setup'
9
+ require 'rack/reloader'
13
10
  require "flipper/api"
14
11
  require "flipper/adapters/pstore"
15
12
 
16
13
  # You can uncomment this to get some default data:
17
14
  # Flipper.enable :logging
18
15
 
16
+ use Rack::Reloader
17
+
19
18
  run Flipper::Api.app
@@ -1,15 +1,12 @@
1
1
  #
2
2
  # Usage:
3
- # # if you want it to not reload and be really fast
4
3
  # bin/rackup examples/api/custom_memoized.ru -p 9999
5
4
  #
6
- # # if you want reloading
7
- # bin/shotgun examples/api/custom_memoized.ru -p 9999
8
- #
9
5
  # http://localhost:9999/
10
6
  #
11
7
 
12
8
  require 'bundler/setup'
9
+ require 'rack/reloader'
13
10
  require "active_support/notifications"
14
11
  require "flipper/api"
15
12
  require "flipper/adapters/pstore"
@@ -31,6 +28,8 @@ ActiveSupport::Notifications.subscribe(/.*/, ->(*args) {
31
28
  # You can uncomment this to get some default data:
32
29
  # flipper[:logging].enable_percentage_of_time 5
33
30
 
31
+ use Rack::Reloader
32
+
34
33
  run Flipper::Api.app(flipper) { |builder|
35
34
  builder.use Flipper::Middleware::SetupEnv, flipper
36
35
  builder.use Flipper::Middleware::Memoizer, preload: true
@@ -1,15 +1,12 @@
1
1
  #
2
2
  # Usage:
3
- # # if you want it to not reload and be really fast
4
3
  # bin/rackup examples/api/memoized.ru -p 9999
5
4
  #
6
- # # if you want reloading
7
- # bin/shotgun examples/api/memoized.ru -p 9999
8
- #
9
5
  # http://localhost:9999/
10
6
  #
11
7
 
12
8
  require 'bundler/setup'
9
+ require 'rack/reloader'
13
10
  require "active_support/notifications"
14
11
  require "flipper/api"
15
12
  require "flipper/adapters/pstore"
@@ -38,6 +35,8 @@ Flipper.register(:admins) { |actor|
38
35
  # You can uncomment this to get some default data:
39
36
  # Flipper.enable :logging
40
37
 
38
+ use Rack::Reloader
39
+
41
40
  run Flipper::Api.app { |builder|
42
41
  builder.use Flipper::Middleware::Memoizer, preload: true
43
42
  }
@@ -0,0 +1,12 @@
1
+ # Usage (from the repo root):
2
+ # env FLIPPER_CLOUD_TOKEN=<token> FLIPPER_CLOUD_SYNC_SECRET=<secret> bundle exec rackup examples/cloud/app.ru -p 9999
3
+ # http://localhost:9999/
4
+
5
+ require 'bundler/setup'
6
+ require 'flipper/cloud'
7
+
8
+ Flipper.configure do |config|
9
+ config.default { Flipper::Cloud.new }
10
+ end
11
+
12
+ run Flipper::Cloud.app
@@ -0,0 +1,13 @@
1
+ # Just a simple example that shows how the backoff policy works.
2
+ require 'bundler/setup'
3
+ require 'flipper/cloud/telemetry/backoff_policy'
4
+
5
+ intervals = []
6
+ policy = Flipper::Cloud::Telemetry::BackoffPolicy.new
7
+
8
+ 10.times do |n|
9
+ intervals << policy.next_interval
10
+ end
11
+
12
+ pp intervals.map { |i| i.round(2) }
13
+ puts "Total: #{intervals.sum.round(2)}ms (#{(intervals.sum/1_000.0).round(2)} sec)"
@@ -0,0 +1,22 @@
1
+ # Usage (from the repo root):
2
+ # env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/basic.rb
3
+
4
+ require_relative "./cloud_setup"
5
+ require 'bundler/setup'
6
+ require 'flipper/cloud'
7
+
8
+ Flipper[:stats].enable
9
+
10
+ if Flipper[:stats].enabled?
11
+ puts 'Enabled!'
12
+ else
13
+ puts 'Disabled!'
14
+ end
15
+
16
+ Flipper[:stats].disable
17
+
18
+ if Flipper[:stats].enabled?
19
+ puts 'Enabled!'
20
+ else
21
+ puts 'Disabled!'
22
+ end
@@ -0,0 +1,20 @@
1
+ if ENV["FLIPPER_CLOUD_TOKEN"].nil? || ENV["FLIPPER_CLOUD_TOKEN"].empty?
2
+ warn "FLIPPER_CLOUD_TOKEN missing so skipping cloud example."
3
+ exit
4
+ end
5
+
6
+ matrix_key = if ENV["CI"]
7
+ suffix_rails = ENV["RAILS_VERSION"].split(".").take(2).join
8
+ suffix_ruby = RUBY_VERSION.split(".").take(2).join
9
+ "FLIPPER_CLOUD_TOKEN_#{suffix_ruby}_#{suffix_rails}"
10
+ else
11
+ "FLIPPER_CLOUD_TOKEN"
12
+ end
13
+
14
+ if matrix_token = ENV[matrix_key]
15
+ puts "Using #{matrix_key} for FLIPPER_CLOUD_TOKEN"
16
+ ENV["FLIPPER_CLOUD_TOKEN"] = matrix_token
17
+ else
18
+ warn "Missing #{matrix_key}. Go create an environment in flipper cloud and set #{matrix_key} to the adapter token for that environment in github actions secrets."
19
+ exit 1
20
+ end
@@ -0,0 +1,36 @@
1
+ # Usage (from the repo root):
2
+ # env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/threaded.rb
3
+
4
+ require_relative "./cloud_setup"
5
+ require 'bundler/setup'
6
+ require 'flipper/cloud'
7
+
8
+ puts Process.pid
9
+
10
+ # Make a call in the parent process so we can detect forking.
11
+ Flipper.enabled?(:stats)
12
+
13
+ pids = 2.times.map do |n|
14
+ fork {
15
+ # Check every second to see if the feature is enabled
16
+ threads = []
17
+ 2.times do
18
+ threads << Thread.new do
19
+ loop do
20
+ sleep rand
21
+
22
+ if Flipper[:stats].enabled?
23
+ puts "#{Process.pid} #{Time.now.to_i} Enabled!"
24
+ else
25
+ puts "#{Process.pid} #{Time.now.to_i} Disabled!"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ threads.map(&:join)
31
+ }
32
+ end
33
+
34
+ pids.each do |pid|
35
+ Process.waitpid pid, 0
36
+ end
@@ -0,0 +1,17 @@
1
+ # Usage (from the repo root):
2
+ # env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/import.rb
3
+
4
+ require_relative "./cloud_setup"
5
+ require 'bundler/setup'
6
+ require 'flipper'
7
+ require 'flipper/cloud'
8
+
9
+ Flipper.enable(:test)
10
+ Flipper.enable(:search)
11
+ Flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
12
+ Flipper.enable_percentage_of_time(:logging, 5)
13
+
14
+ cloud = Flipper::Cloud.new
15
+
16
+ # makes cloud identical to memory flipper
17
+ cloud.import(Flipper)
@@ -0,0 +1,33 @@
1
+ # Usage (from the repo root):
2
+ # env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/threaded.rb
3
+
4
+ require_relative "./cloud_setup"
5
+ require 'bundler/setup'
6
+ require 'flipper/cloud'
7
+
8
+ puts Process.pid
9
+
10
+ Flipper.configure do |config|
11
+ config.default {
12
+ Flipper::Cloud.new(
13
+ local_adapter: config.adapter,
14
+ debug_output: STDOUT,
15
+ )
16
+ }
17
+ end
18
+
19
+ # You might want to do this at some point to see different results:
20
+ # Flipper.enable(:search)
21
+ # Flipper.disable(:stats)
22
+
23
+ # Check every second to see if the feature is enabled
24
+ 5.times.map { |i|
25
+ Thread.new {
26
+ loop do
27
+ sleep rand
28
+
29
+ Flipper.enabled?(:stats)
30
+ Flipper.enabled?(:search)
31
+ end
32
+ }
33
+ }.each(&:join)
data/examples/dsl.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'bundler/setup'
2
2
  require 'flipper'
3
3
 
4
- # create a thing with an identifier
4
+ # create an actor with an identifier
5
5
  class Person < Struct.new(:id)
6
6
  include Flipper::Identifier
7
7
  end
@@ -47,20 +47,6 @@ puts "stats.enabled?: #{stats.enabled?}"
47
47
  puts "stats.enabled? person: #{stats.enabled? person}"
48
48
  puts
49
49
 
50
- # get an instance of the percentage of time type set to 5
51
- puts Flipper.time(5).inspect
52
-
53
- # get an instance of the percentage of actors type set to 15
54
- puts Flipper.actors(15).inspect
55
-
56
- # get an instance of an actor using an object that responds to flipper_id
57
- responds_to_flipper_id = Struct.new(:flipper_id).new(10)
58
- puts Flipper.actor(responds_to_flipper_id).inspect
59
-
60
- # get an instance of an actor using an object
61
- thing = Struct.new(:flipper_id).new(22)
62
- puts Flipper.actor(thing).inspect
63
-
64
50
  # register a top level group
65
51
  admins = Flipper.register(:admins) { |actor|
66
52
  actor.respond_to?(:admin?) && actor.admin?
@@ -31,5 +31,7 @@ Flipper.enable_percentage_of_actors :pro_stats, 50
31
31
  Flipper.enable_group :tweets, :admins
32
32
  Flipper.enable_actor :posts, user2
33
33
 
34
- pp Flipper.features.select { |feature| feature.enabled?(user1) }.map(&:name)
35
- pp Flipper.features.select { |feature| feature.enabled?(user2) }.map(&:name)
34
+ pp Flipper.features.select { |feature| feature.enabled?(user1) }.map(&:name).sort
35
+ pp Flipper.features.select { |feature| feature.enabled?(user2) }.map(&:name).sort
36
+ pp Flipper.features.select { |feature| feature.enabled?(user1, user2) }.map(&:name).sort
37
+ pp Flipper.features.select { |feature| feature.enabled?([user2, user1]) }.map(&:name).sort
@@ -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)
@@ -1,5 +1,6 @@
1
1
  require 'bundler/setup'
2
2
  require 'securerandom'
3
+ require 'active_support/isolated_execution_state'
3
4
  require 'active_support/notifications'
4
5
 
5
6
  class FlipperSubscriber
@@ -1,6 +1,7 @@
1
1
  # Quick example of how to keep track of when a feature was last checked.
2
2
  require 'bundler/setup'
3
3
  require 'securerandom'
4
+ require 'active_support/isolated_execution_state'
4
5
  require 'active_support/notifications'
5
6
  require 'flipper'
6
7
 
@@ -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