flipper 0.28.3 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/ci.yml +56 -10
  4. data/.github/workflows/examples.yml +54 -11
  5. data/.github/workflows/release.yml +54 -0
  6. data/.superset/config.json +4 -0
  7. data/CLAUDE.md +93 -0
  8. data/Changelog.md +1 -559
  9. data/Gemfile +17 -8
  10. data/README.md +34 -29
  11. data/Rakefile +2 -2
  12. data/benchmark/typecast_ips.rb +8 -0
  13. data/docs/images/banner.jpg +0 -0
  14. data/docs/images/flipper_cloud.png +0 -0
  15. data/examples/cloud/app.ru +12 -0
  16. data/examples/cloud/backoff_policy.rb +13 -0
  17. data/examples/cloud/basic.rb +22 -0
  18. data/examples/cloud/cloud_setup.rb +20 -0
  19. data/examples/cloud/forked.rb +36 -0
  20. data/examples/cloud/import.rb +17 -0
  21. data/examples/cloud/poll_interval/README.md +111 -0
  22. data/examples/cloud/poll_interval/client.rb +108 -0
  23. data/examples/cloud/poll_interval/server.rb +98 -0
  24. data/examples/cloud/threaded.rb +33 -0
  25. data/examples/dsl.rb +0 -14
  26. data/examples/expressions.rb +237 -0
  27. data/examples/strict.rb +18 -0
  28. data/exe/flipper +5 -0
  29. data/flipper-cloud.gemspec +19 -0
  30. data/flipper.gemspec +8 -4
  31. data/lib/flipper/actor.rb +6 -3
  32. data/lib/flipper/adapter.rb +27 -1
  33. data/lib/flipper/adapter_builder.rb +44 -0
  34. data/lib/flipper/adapters/actor_limit.rb +54 -0
  35. data/lib/flipper/adapters/cache_base.rb +161 -0
  36. data/lib/flipper/adapters/dual_write.rb +7 -5
  37. data/lib/flipper/adapters/failover.rb +9 -7
  38. data/lib/flipper/adapters/failsafe.rb +2 -6
  39. data/lib/flipper/adapters/http/client.rb +53 -14
  40. data/lib/flipper/adapters/http/error.rb +2 -2
  41. data/lib/flipper/adapters/http.rb +56 -16
  42. data/lib/flipper/adapters/instrumented.rb +2 -6
  43. data/lib/flipper/adapters/memoizable.rb +17 -22
  44. data/lib/flipper/adapters/memory.rb +5 -7
  45. data/lib/flipper/adapters/operation_logger.rb +18 -92
  46. data/lib/flipper/adapters/poll.rb +16 -3
  47. data/lib/flipper/adapters/pstore.rb +18 -12
  48. data/lib/flipper/adapters/read_only.rb +8 -41
  49. data/lib/flipper/adapters/strict.rb +75 -0
  50. data/lib/flipper/adapters/sync/feature_synchronizer.rb +14 -1
  51. data/lib/flipper/adapters/sync/synchronizer.rb +13 -5
  52. data/lib/flipper/adapters/sync.rb +7 -7
  53. data/lib/flipper/adapters/wrapper.rb +54 -0
  54. data/lib/flipper/cli.rb +314 -0
  55. data/lib/flipper/cloud/configuration.rb +267 -0
  56. data/lib/flipper/cloud/dsl.rb +27 -0
  57. data/lib/flipper/cloud/message_verifier.rb +95 -0
  58. data/lib/flipper/cloud/middleware.rb +63 -0
  59. data/lib/flipper/cloud/migrate.rb +71 -0
  60. data/lib/flipper/cloud/routes.rb +14 -0
  61. data/lib/flipper/cloud/telemetry/backoff_policy.rb +96 -0
  62. data/lib/flipper/cloud/telemetry/instrumenter.rb +22 -0
  63. data/lib/flipper/cloud/telemetry/metric.rb +39 -0
  64. data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
  65. data/lib/flipper/cloud/telemetry/submitter.rb +100 -0
  66. data/lib/flipper/cloud/telemetry.rb +191 -0
  67. data/lib/flipper/cloud.rb +54 -0
  68. data/lib/flipper/configuration.rb +25 -4
  69. data/lib/flipper/dsl.rb +48 -43
  70. data/lib/flipper/engine.rb +102 -0
  71. data/lib/flipper/export.rb +0 -2
  72. data/lib/flipper/exporters/json/export.rb +1 -1
  73. data/lib/flipper/exporters/json/v1.rb +1 -1
  74. data/lib/flipper/expression/builder.rb +73 -0
  75. data/lib/flipper/expression/constant.rb +25 -0
  76. data/lib/flipper/expression.rb +71 -0
  77. data/lib/flipper/expressions/all.rb +9 -0
  78. data/lib/flipper/expressions/any.rb +9 -0
  79. data/lib/flipper/expressions/boolean.rb +9 -0
  80. data/lib/flipper/expressions/comparable.rb +13 -0
  81. data/lib/flipper/expressions/equal.rb +9 -0
  82. data/lib/flipper/expressions/feature_enabled.rb +34 -0
  83. data/lib/flipper/expressions/greater_than.rb +9 -0
  84. data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
  85. data/lib/flipper/expressions/less_than.rb +9 -0
  86. data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
  87. data/lib/flipper/expressions/not_equal.rb +9 -0
  88. data/lib/flipper/expressions/now.rb +9 -0
  89. data/lib/flipper/expressions/number.rb +9 -0
  90. data/lib/flipper/expressions/percentage.rb +9 -0
  91. data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
  92. data/lib/flipper/expressions/property.rb +9 -0
  93. data/lib/flipper/expressions/random.rb +9 -0
  94. data/lib/flipper/expressions/string.rb +9 -0
  95. data/lib/flipper/expressions/time.rb +16 -0
  96. data/lib/flipper/feature.rb +63 -1
  97. data/lib/flipper/gate.rb +2 -1
  98. data/lib/flipper/gate_values.rb +5 -2
  99. data/lib/flipper/gates/expression.rb +75 -0
  100. data/lib/flipper/instrumentation/log_subscriber.rb +13 -5
  101. data/lib/flipper/instrumentation/statsd.rb +4 -2
  102. data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
  103. data/lib/flipper/instrumentation/subscriber.rb +0 -4
  104. data/lib/flipper/metadata.rb +8 -1
  105. data/lib/flipper/middleware/memoizer.rb +30 -14
  106. data/lib/flipper/model/active_record.rb +23 -0
  107. data/lib/flipper/poller.rb +54 -12
  108. data/lib/flipper/serializers/gzip.rb +22 -0
  109. data/lib/flipper/serializers/json.rb +17 -0
  110. data/lib/flipper/spec/shared_adapter_specs.rb +82 -63
  111. data/lib/flipper/test/shared_adapter_test.rb +77 -58
  112. data/lib/flipper/test_help.rb +43 -0
  113. data/lib/flipper/typecast.rb +37 -9
  114. data/lib/flipper/types/percentage.rb +1 -1
  115. data/lib/flipper/version.rb +11 -1
  116. data/lib/flipper.rb +61 -8
  117. data/lib/generators/flipper/setup_generator.rb +68 -0
  118. data/lib/generators/flipper/templates/initializer.rb +45 -0
  119. data/lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb +22 -0
  120. data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb +18 -0
  121. data/lib/generators/flipper/update_generator.rb +35 -0
  122. data/package-lock.json +41 -0
  123. data/package.json +10 -0
  124. data/spec/fixtures/environment.rb +1 -0
  125. data/spec/flipper/adapter_builder_spec.rb +72 -0
  126. data/spec/flipper/adapter_spec.rb +21 -0
  127. data/spec/flipper/adapters/actor_limit_spec.rb +75 -0
  128. data/spec/flipper/adapters/dual_write_spec.rb +15 -2
  129. data/spec/flipper/adapters/failover_spec.rb +12 -0
  130. data/spec/flipper/adapters/http/client_spec.rb +61 -0
  131. data/spec/flipper/adapters/http_spec.rb +375 -74
  132. data/spec/flipper/adapters/instrumented_spec.rb +1 -1
  133. data/spec/flipper/adapters/memoizable_spec.rb +21 -21
  134. data/spec/flipper/adapters/operation_logger_spec.rb +2 -2
  135. data/spec/flipper/adapters/poll_spec.rb +41 -0
  136. data/spec/flipper/adapters/read_only_spec.rb +32 -17
  137. data/spec/flipper/adapters/strict_spec.rb +122 -0
  138. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +39 -0
  139. data/spec/flipper/adapters/sync/synchronizer_spec.rb +87 -0
  140. data/spec/flipper/adapters/sync_spec.rb +13 -0
  141. data/spec/flipper/cli_spec.rb +217 -0
  142. data/spec/flipper/cloud/configuration_spec.rb +251 -0
  143. data/spec/flipper/cloud/dsl_spec.rb +82 -0
  144. data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
  145. data/spec/flipper/cloud/middleware_spec.rb +289 -0
  146. data/spec/flipper/cloud/migrate_spec.rb +160 -0
  147. data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +107 -0
  148. data/spec/flipper/cloud/telemetry/metric_spec.rb +87 -0
  149. data/spec/flipper/cloud/telemetry/metric_storage_spec.rb +58 -0
  150. data/spec/flipper/cloud/telemetry/submitter_spec.rb +145 -0
  151. data/spec/flipper/cloud/telemetry_spec.rb +208 -0
  152. data/spec/flipper/cloud_spec.rb +186 -0
  153. data/spec/flipper/configuration_spec.rb +17 -0
  154. data/spec/flipper/dsl_spec.rb +34 -73
  155. data/spec/flipper/engine_spec.rb +374 -0
  156. data/spec/flipper/exporters/json/v1_spec.rb +3 -3
  157. data/spec/flipper/expression/builder_spec.rb +248 -0
  158. data/spec/flipper/expression_spec.rb +188 -0
  159. data/spec/flipper/expressions/all_spec.rb +15 -0
  160. data/spec/flipper/expressions/any_spec.rb +15 -0
  161. data/spec/flipper/expressions/boolean_spec.rb +15 -0
  162. data/spec/flipper/expressions/equal_spec.rb +24 -0
  163. data/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +28 -0
  164. data/spec/flipper/expressions/greater_than_spec.rb +28 -0
  165. data/spec/flipper/expressions/less_than_or_equal_to_spec.rb +28 -0
  166. data/spec/flipper/expressions/less_than_spec.rb +32 -0
  167. data/spec/flipper/expressions/not_equal_spec.rb +15 -0
  168. data/spec/flipper/expressions/now_spec.rb +11 -0
  169. data/spec/flipper/expressions/number_spec.rb +21 -0
  170. data/spec/flipper/expressions/percentage_of_actors_spec.rb +20 -0
  171. data/spec/flipper/expressions/percentage_spec.rb +15 -0
  172. data/spec/flipper/expressions/property_spec.rb +13 -0
  173. data/spec/flipper/expressions/random_spec.rb +9 -0
  174. data/spec/flipper/expressions/string_spec.rb +11 -0
  175. data/spec/flipper/expressions/time_spec.rb +29 -0
  176. data/spec/flipper/feature_spec.rb +380 -10
  177. data/spec/flipper/gate_values_spec.rb +2 -2
  178. data/spec/flipper/gates/expression_spec.rb +190 -0
  179. data/spec/flipper/identifier_spec.rb +4 -5
  180. data/spec/flipper/instrumentation/log_subscriber_spec.rb +10 -2
  181. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +16 -2
  182. data/spec/flipper/middleware/memoizer_spec.rb +107 -7
  183. data/spec/flipper/model/active_record_spec.rb +72 -0
  184. data/spec/flipper/poller_spec.rb +347 -4
  185. data/spec/flipper/serializers/gzip_spec.rb +13 -0
  186. data/spec/flipper/serializers/json_spec.rb +13 -0
  187. data/spec/flipper/typecast_spec.rb +43 -7
  188. data/spec/flipper/types/actor_spec.rb +18 -1
  189. data/spec/flipper_integration_spec.rb +247 -16
  190. data/spec/flipper_spec.rb +92 -29
  191. data/spec/spec_helper.rb +24 -17
  192. data/spec/support/actor_names.yml +1 -0
  193. data/spec/support/fail_on_output.rb +8 -0
  194. data/spec/support/fake_backoff_policy.rb +15 -0
  195. data/spec/support/spec_helpers.rb +34 -8
  196. data/test/adapters/actor_limit_test.rb +20 -0
  197. data/test_rails/generators/flipper/setup_generator_test.rb +69 -0
  198. data/test_rails/generators/flipper/update_generator_test.rb +96 -0
  199. data/test_rails/helper.rb +22 -2
  200. data/test_rails/system/test_help_test.rb +52 -0
  201. metadata +139 -75
  202. data/.tool-versions +0 -1
  203. data/lib/flipper/railtie.rb +0 -47
  204. data/spec/flipper/railtie_spec.rb +0 -109
data/README.md CHANGED
@@ -1,17 +1,17 @@
1
1
  [![Flipper Mark](docs/images/banner.jpg)](https://www.flippercloud.io)
2
2
 
3
- [Website](https://flippercloud.io) | [Documentation](https://flippercloud.io/docs) | [Examples](examples) | [Twitter](https://twitter.com/flipper_cloud)
3
+ [Website](https://flippercloud.io?utm_source=oss&utm_medium=readme&utm_campaign=website_link) | [Documentation](https://flippercloud.io/docs?utm_source=oss&utm_medium=readme&utm_campaign=docs_link) | [Examples](examples) | [Chat](https://chat.flippercloud.io/join/xjHq-aJsA-BeZH) | [Twitter](https://twitter.com/flipper_cloud) | [Ruby.social](https://ruby.social/@flipper)
4
4
 
5
5
  # Flipper
6
6
 
7
- > Beautiful, performant feature flags for Ruby.
7
+ > Beautiful, performant feature flags for Ruby and Rails.
8
8
 
9
9
  Flipper gives you control over who has access to features in your app.
10
10
 
11
- * Enable or disable features for everyone, specific actors, groups of actors, a percentage of actors, or a percentage of time.
12
- * Configure your feature flags from the console or a web UI.
13
- * Regardless of what data store you are using, Flipper can performantly store your feature flags.
14
- * Use [Flipper Cloud](#flipper-cloud) to cascade features from multiple environments, share settings with your team, control permissions, keep an audit history, and rollback.
11
+ - Enable or disable features for everyone, specific actors, groups of actors, a percentage of actors, or a percentage of time.
12
+ - Configure your feature flags from the console or a web UI.
13
+ - Regardless of what data store you are using, Flipper can performantly store your feature flags.
14
+ - Use [Flipper Cloud](#flipper-cloud) to cascade features from multiple environments, share settings with your team, control permissions, keep an audit history, and rollback.
15
15
 
16
16
  Control your software — don't let it control you.
17
17
 
@@ -35,7 +35,7 @@ Or install it yourself with:
35
35
 
36
36
  ## Subscribe & Ship
37
37
 
38
- [💌  Subscribe](https://buttondown.email/flipper) - I'll send you short and sweet emails when we release new versions.
38
+ [💌  Subscribe](https://blog.flippercloud.io/#/portal/signup) - we'll send you short and sweet emails when we release new versions ([examples](https://blog.flippercloud.io/tag/releases/)).
39
39
 
40
40
  ## Getting Started
41
41
 
@@ -43,7 +43,7 @@ Use `Flipper#enabled?` in your app to check if a feature is enabled.
43
43
 
44
44
  ```ruby
45
45
  # check if search is enabled
46
- if Flipper.enabled? :search, current_user
46
+ if Flipper.enabled?(:search, current_user)
47
47
  puts 'Search away!'
48
48
  else
49
49
  puts 'No search for you!'
@@ -66,24 +66,26 @@ Flipper.enable_group :search, :admin
66
66
  Flipper.enable_percentage_of_actors :search, 2
67
67
  ```
68
68
 
69
- Read more about [getting started with Flipper](https://flippercloud.io/docs) and [enabling features](https://flippercloud.io/docs/features).
69
+ Read more about [getting started with Flipper](https://flippercloud.io/docs?utm_source=oss&utm_medium=readme&utm_campaign=getting_started) and [enabling features](https://flippercloud.io/docs/features?utm_source=oss&utm_medium=readme&utm_campaign=enabling_features).
70
70
 
71
71
  ## Flipper Cloud
72
72
 
73
- Like Flipper and want more? Check out [Flipper Cloud](https://www.flippercloud.io), which comes with:
73
+ Like Flipper and want more? Check out [Flipper Cloud](https://www.flippercloud.io?utm_source=oss&utm_medium=readme&utm_campaign=check_out), which comes with:
74
74
 
75
- * **everything in one place** — no need to bounce around from different application UIs or IRB consoles.
76
- * **permissions** — grant access to everyone in your organization or lockdown each project to particular people.
77
- * **multiple environments** — production, staging, enterprise, by continent, whatever you need.
78
- * **personal environments** — no more rake scripts or manual enable/disable to get your laptop to look like production. Every developer gets a personal environment that inherits from production that they can override as they please ([read more](https://www.johnnunemaker.com/flipper-cloud-environments/)).
79
- * **no maintenance** — we'll keep the lights on for you. We also have handy webhooks for keeping your app in sync with Cloud, so **our availability won't affect yours**. All your feature flag reads are local to your app.
80
- * **audit history** — every feature change and who made it.
81
- * **rollbacks** — enable or disable a feature accidentally? No problem. You can roll back to any point in the audit history with a single click.
75
+ - **multiple environments** — production, staging, per continent, whatever you need. Every environment inherits from production by default and every project comes with a [project overview page](https://blog.flippercloud.io/project-overview/) that shows each feature and its status in each environment.
76
+ - **personal environments** — everyone on your team gets a personal environment (that inherits from production) which they can modify however they want without stepping on anyone else's toes.
77
+ - **permissions** — grant access to everyone in your organization or lockdown each project to particular people. You can even limit access to a particular environment (like production) to specific people.
78
+ - **audit history** — every feature change and who made it.
79
+ - **rollbacks** — enable or disable a feature accidentally? No problem. You can roll back to any point in the audit history with a single click.
80
+ - **maintenance** — we'll keep the lights on for you. We also have handy webhooks and background polling for keeping your app in sync with Cloud, so **our availability won't affect yours**. All your feature flag reads are local to your app.
81
+ - **everything in one place** — no need to bounce around from different application UIs or IRB consoles.
82
82
 
83
- [![Flipper Cloud Screenshot](docs/images/flipper_cloud.png)](https://www.flippercloud.io)
83
+ [![Flipper Cloud Screenshot](docs/images/flipper_cloud.png)](https://www.flippercloud.io?utm_source=oss&utm_medium=readme&utm_campaign=screenshot)
84
84
 
85
85
  Cloud is super simple to integrate with Rails ([demo app](https://github.com/fewerandfaster/flipper-rails-demo)), Sinatra or any other framework.
86
86
 
87
+ We also have a [free plan](https://www.flippercloud.io?utm_source=oss&utm_medium=readme&utm_campaign=free_plan) that you can use forever.
88
+
87
89
  ## Contributing
88
90
 
89
91
  1. Fork it
@@ -95,17 +97,20 @@ Cloud is super simple to integrate with Rails ([demo app](https://github.com/few
95
97
 
96
98
  ## Releasing
97
99
 
98
- 1. Update the version to be whatever it should be and commit.
99
- 2. `script/release`
100
- 3. Profit.
100
+ 1. Update the version in `lib/flipper/version.rb` and commit.
101
+ 2. Tag and push: `git tag v1.x.x && git push origin v1.x.x`
102
+ 3. GitHub Actions builds and publishes all gems to RubyGems automatically.
103
+ 4. Edit and publish the draft [GitHub Release](https://github.com/flippercloud/flipper/releases).
101
104
 
102
105
  ## Brought To You By
103
106
 
104
- | pic | @mention | area |
105
- |---|---|---|
106
- | ![@jnunemaker](https://avatars3.githubusercontent.com/u/235?s=64) | [@jnunemaker](https://github.com/jnunemaker) | most things |
107
- | ![@bkeepers](https://avatars3.githubusercontent.com/u/173?s=64) | [@bkeepers](https://github.com/bkeepers) | most things |
108
- | ![@dpep](https://avatars3.githubusercontent.com/u/918804?s=64) | [@dpep](https://github.com/dpep) | tbd |
109
- | ![@alexwheeler](https://avatars3.githubusercontent.com/u/3260042?s=64) | [@alexwheeler](https://github.com/alexwheeler) | api |
110
- | ![@thetimbanks](https://avatars1.githubusercontent.com/u/471801?s=64) | [@thetimbanks](https://github.com/thetimbanks) | ui |
111
- | ![@lazebny](https://avatars1.githubusercontent.com/u/6276766?s=64) | [@lazebny](https://github.com/lazebny) | docker |
107
+ | pic | @mention | area |
108
+ | ---------------------------------------------------------------------- | ---------------------------------------------- | ----------- |
109
+ | ![@jnunemaker](https://avatars3.githubusercontent.com/u/235?s=64) | [@jnunemaker](https://github.com/jnunemaker) | most things |
110
+ | ![@bkeepers](https://avatars3.githubusercontent.com/u/173?s=64) | [@bkeepers](https://github.com/bkeepers) | most things |
111
+ | ![@dpep](https://avatars3.githubusercontent.com/u/918804?s=64) | [@dpep](https://github.com/dpep) | tbd |
112
+ | ![@alexwheeler](https://avatars3.githubusercontent.com/u/3260042?s=64) | [@alexwheeler](https://github.com/alexwheeler) | api |
113
+ | ![@thetimbanks](https://avatars1.githubusercontent.com/u/471801?s=64) | [@thetimbanks](https://github.com/thetimbanks) | ui |
114
+ | ![@lazebny](https://avatars1.githubusercontent.com/u/6276766?s=64) | [@lazebny](https://github.com/lazebny) | docker |
115
+ | ![@pagertree](https://avatars.githubusercontent.com/u/24941240?s=64) | [@pagertree](https://github.com/pagertree) | sponsor |
116
+ | ![@kdaigle](https://avatars.githubusercontent.com/u/2501?s=64) | [@kdaigle](https://github.com/kdaigle) | sponsor |
data/Rakefile CHANGED
@@ -27,7 +27,8 @@ end
27
27
 
28
28
  require 'rspec/core/rake_task'
29
29
  RSpec::Core::RakeTask.new(:spec) do |t|
30
- t.rspec_opts = %w(--color --format documentation)
30
+ t.rspec_opts = %w(--color)
31
+ t.verbose = false
31
32
  end
32
33
 
33
34
  namespace :spec do
@@ -41,7 +42,6 @@ end
41
42
  Rake::TestTask.new do |t|
42
43
  t.libs = %w(lib test)
43
44
  t.pattern = 'test/**/*_test.rb'
44
- t.options = '--documentation'
45
45
  t.warning = false
46
46
  end
47
47
 
@@ -16,4 +16,12 @@ Benchmark.ips do |x|
16
16
  x.report("Typecast.to_float '1'") { Flipper::Typecast.to_float('1'.freeze) }
17
17
  x.report("Typecast.to_float 1.01") { Flipper::Typecast.to_float(1) }
18
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) }
19
27
  end
Binary file
Binary file
@@ -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
+ 5.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,111 @@
1
+ # Poll Interval Dynamic Adjustment Demo
2
+
3
+ This demo shows how the Flipper poller dynamically adjusts its polling interval based on the `poll-interval` header from the server, and how it responds to the `poll-shutdown` header.
4
+
5
+ ## Files
6
+
7
+ - `server.rb` - Test server that responds with configurable headers
8
+ - `client.rb` - Client that polls the server and logs interval changes
9
+ - `README.md` - This file
10
+
11
+ ## How to Run
12
+
13
+ ### Terminal 1: Start the Server
14
+
15
+ ```bash
16
+ bundle exec ruby examples/cloud/poll_interval/server.rb
17
+ ```
18
+
19
+ The server will start on http://localhost:3000 and show a prompt where you can control what headers to send.
20
+
21
+ ### Terminal 2: Start the Client
22
+
23
+ ```bash
24
+ bundle exec ruby examples/cloud/poll_interval/client.rb
25
+ ```
26
+
27
+ The client will start polling the server every 10 seconds (the minimum) and log all activity.
28
+
29
+ ## Testing Scenarios
30
+
31
+ ### 1. Change Poll Interval
32
+
33
+ In the **server terminal**, type a number to set the poll interval:
34
+
35
+ ```
36
+ > 20
37
+ ```
38
+
39
+ In the **client terminal**, you'll see:
40
+
41
+ ```
42
+ [HH:MM:SS] WARN: ⚠️ INTERVAL CHANGED: 10.0s → 20.0s
43
+ ```
44
+
45
+ The client will now poll every 20 seconds instead of 10.
46
+
47
+ ### 2. Try an Invalid Interval (Below Minimum)
48
+
49
+ In the **server terminal**:
50
+
51
+ ```
52
+ > 5
53
+ ```
54
+
55
+ In the **client terminal**, you'll see a warning:
56
+
57
+ ```
58
+ Flipper::Cloud poll interval must be greater than or equal to 10 but was 5.0. Setting interval to 10.
59
+ ```
60
+
61
+ The interval will remain at 10 seconds (the minimum).
62
+
63
+ ### 3. Trigger Shutdown
64
+
65
+ In the **server terminal**:
66
+
67
+ ```
68
+ > shutdown
69
+ ```
70
+
71
+ In the **client terminal**, you'll see:
72
+
73
+ ```
74
+ [HH:MM:SS] WARN: Shutdown requested by server via poll-shutdown header
75
+ [HH:MM:SS] WARN: Poller stopped
76
+ [HH:MM:SS] WARN: Poller thread is no longer running
77
+ ```
78
+
79
+ The poller will stop gracefully.
80
+
81
+ ### 4. Reset Headers
82
+
83
+ In the **server terminal**:
84
+
85
+ ```
86
+ > reset
87
+ ```
88
+
89
+ The server will stop sending special headers. The client will continue with its current interval.
90
+
91
+ ## What You'll Learn
92
+
93
+ - How `poll-interval` header dynamically adjusts polling frequency
94
+ - How `poll-shutdown` header gracefully stops the poller
95
+ - How minimum interval enforcement works (10 seconds minimum)
96
+ - How the poller continues working even if the server returns errors
97
+ - Real-time logging of poller events via instrumentation
98
+
99
+ ## Implementation Details
100
+
101
+ The poller checks response headers in the `ensure` block of the `sync` method, which means:
102
+
103
+ - Interval adjustments happen even if the sync fails with an error
104
+ - Shutdown signals are never missed, even during failures
105
+ - The poller is resilient to network issues
106
+
107
+ The `interval=` setter handles all validation:
108
+
109
+ - Type conversion via `Flipper::Typecast.to_float`
110
+ - Minimum enforcement (10 seconds)
111
+ - Warning messages for invalid values
@@ -0,0 +1,108 @@
1
+ # Example showing poll interval being dynamically adjusted via poll-interval header
2
+ #
3
+ # Usage:
4
+ # 1. Terminal 1: bundle exec ruby examples/cloud/poll_interval/server.rb
5
+ # 2. Terminal 2: bundle exec ruby examples/cloud/poll_interval/client.rb
6
+
7
+ require 'bundler/setup'
8
+ require 'flipper'
9
+ require 'flipper/adapters/http'
10
+ require 'flipper/poller'
11
+ require 'logger'
12
+
13
+ # Setup logging to show what's happening
14
+ logger = Logger.new(STDOUT)
15
+ logger.level = Logger::INFO
16
+ logger.formatter = proc do |severity, datetime, progname, msg|
17
+ "[#{datetime.strftime('%H:%M:%S')}] #{severity}: #{msg}\n"
18
+ end
19
+
20
+ # Create HTTP adapter pointing to localhost:3000
21
+ http_adapter = Flipper::Adapters::Http.new(url: 'http://localhost:3000/flipper')
22
+
23
+ # Create instrumenter to log poller events
24
+ instrumenter = Module.new do
25
+ def self.instrument(name, payload = {})
26
+ case payload[:operation]
27
+ when :poll
28
+ logger.info "Polling remote adapter..."
29
+ when :shutdown_requested
30
+ logger.warn "Shutdown requested by server via poll-shutdown header"
31
+ when :stop
32
+ logger.warn "Poller stopped"
33
+ when :thread_start
34
+ logger.info "Poller thread started"
35
+ end
36
+
37
+ result = yield if block_given?
38
+
39
+ if payload[:operation] == :poll && result
40
+ logger.info "Poll completed successfully"
41
+ end
42
+
43
+ result
44
+ end
45
+
46
+ def self.logger=(l)
47
+ @logger = l
48
+ end
49
+
50
+ def self.logger
51
+ @logger
52
+ end
53
+ end
54
+ instrumenter.logger = logger
55
+
56
+ # Create poller with custom instrumenter and short initial interval
57
+ poller = Flipper::Poller.new(
58
+ remote_adapter: http_adapter,
59
+ interval: 5, # Start with 5 second interval (will be enforced to 10 minimum)
60
+ instrumenter: instrumenter,
61
+ start_automatically: false,
62
+ shutdown_automatically: false
63
+ )
64
+
65
+ logger.info "Starting poller with interval: #{poller.interval} seconds"
66
+ logger.info "Minimum allowed interval: #{Flipper::Poller::MINIMUM_POLL_INTERVAL} seconds"
67
+ logger.info ""
68
+ logger.info "Server can control polling via response headers:"
69
+ logger.info " - poll-interval: <seconds> (adjust poll frequency)"
70
+ logger.info " - poll-shutdown: true (stop polling)"
71
+ logger.info ""
72
+
73
+ # Track interval changes
74
+ last_interval = poller.interval
75
+
76
+ # Start the poller
77
+ poller.start
78
+
79
+ # Monitor for interval changes and log them
80
+ logger.info "Monitoring poller... (Ctrl+C to exit)"
81
+ logger.info ""
82
+
83
+ begin
84
+ loop do
85
+ sleep 2
86
+
87
+ current_interval = poller.interval
88
+
89
+ # Highlight when it changes
90
+ if current_interval != last_interval
91
+ logger.warn "⚠️ INTERVAL CHANGED: #{last_interval}s → #{current_interval}s"
92
+ last_interval = current_interval
93
+ end
94
+
95
+ # Check if poller thread is still alive
96
+ unless poller.thread&.alive?
97
+ logger.warn "Poller thread is no longer running"
98
+ break
99
+ end
100
+ end
101
+ rescue Interrupt
102
+ logger.info ""
103
+ logger.info "Interrupted by user"
104
+ ensure
105
+ logger.info "Stopping poller..."
106
+ poller.stop
107
+ logger.info "Final interval: #{poller.interval} seconds"
108
+ end
@@ -0,0 +1,98 @@
1
+ # Simple test server for demonstrating poll interval changes
2
+ #
3
+ # Usage:
4
+ # 1. Terminal 1: bundle exec ruby examples/cloud/poll_interval/server.rb
5
+ # 2. Terminal 2: bundle exec ruby examples/cloud/poll_interval/client.rb
6
+ #
7
+ # Commands in server terminal:
8
+ # - Type a number (e.g., "15") to set poll-interval header to that value
9
+ # - Type "shutdown" to send poll-shutdown: true header
10
+ # - Type "reset" to stop sending special headers
11
+ # - Ctrl+C to exit
12
+
13
+ require 'bundler/setup'
14
+ require 'webrick'
15
+ require 'json'
16
+
17
+ # State for what headers to send
18
+ $poll_interval = nil
19
+ $poll_shutdown = false
20
+
21
+ # Thread to handle user input for changing headers
22
+ input_thread = Thread.new do
23
+ puts ""
24
+ puts "=" * 60
25
+ puts "Server Controls:"
26
+ puts " Type a number (e.g., '15') to set poll-interval"
27
+ puts " Type 'shutdown' to trigger poll shutdown"
28
+ puts " Type 'reset' to clear all special headers"
29
+ puts "=" * 60
30
+ puts ""
31
+
32
+ loop do
33
+ print "> "
34
+ input = gets&.chomp
35
+ break if input.nil?
36
+
37
+ case input
38
+ when /^\d+$/
39
+ $poll_interval = input.to_i
40
+ puts "✓ Will send poll-interval: #{$poll_interval}"
41
+ when "shutdown"
42
+ $poll_shutdown = true
43
+ puts "✓ Will send poll-shutdown: true"
44
+ when "reset"
45
+ $poll_interval = nil
46
+ $poll_shutdown = false
47
+ puts "✓ Cleared all special headers"
48
+ else
49
+ puts "Unknown command. Use a number, 'shutdown', or 'reset'"
50
+ end
51
+ end
52
+ end
53
+
54
+ # Setup WEBrick server
55
+ server = WEBrick::HTTPServer.new(
56
+ Port: 3000,
57
+ Logger: WEBrick::Log.new($stdout, WEBrick::Log::INFO),
58
+ AccessLog: [[
59
+ $stdout,
60
+ WEBrick::AccessLog::COMMON_LOG_FORMAT
61
+ ]]
62
+ )
63
+
64
+ # Handle GET /flipper/features
65
+ server.mount_proc '/flipper/features' do |req, res|
66
+ # Build response
67
+ response_body = {
68
+ features: []
69
+ }
70
+
71
+ res.status = 200
72
+ res['Content-Type'] = 'application/json'
73
+ res.body = JSON.generate(response_body)
74
+
75
+ # Add special headers if configured
76
+ if $poll_interval
77
+ res['poll-interval'] = $poll_interval.to_s
78
+ puts "→ Sent poll-interval: #{$poll_interval}"
79
+ end
80
+
81
+ if $poll_shutdown
82
+ res['poll-shutdown'] = 'true'
83
+ puts "→ Sent poll-shutdown: true"
84
+ end
85
+ end
86
+
87
+ # Trap interrupt and shutdown gracefully
88
+ trap('INT') do
89
+ puts "\nShutting down server..."
90
+ server.shutdown
91
+ input_thread.kill
92
+ end
93
+
94
+ puts "Server starting on http://localhost:3000"
95
+ puts "Endpoint: GET http://localhost:3000/flipper/features"
96
+ puts ""
97
+
98
+ server.start
@@ -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
@@ -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
- actor = Struct.new(:flipper_id).new(22)
62
- puts Flipper.actor(actor).inspect
63
-
64
50
  # register a top level group
65
51
  admins = Flipper.register(:admins) { |actor|
66
52
  actor.respond_to?(:admin?) && actor.admin?