flipper 0.16.0 → 1.4.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 (285) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +1 -0
  3. data/.github/FUNDING.yml +1 -0
  4. data/.github/dependabot.yml +6 -0
  5. data/.github/workflows/ci.yml +110 -0
  6. data/.github/workflows/examples.yml +105 -0
  7. data/.github/workflows/release.yml +54 -0
  8. data/.rspec +1 -0
  9. data/CLAUDE.md +87 -0
  10. data/Changelog.md +2 -215
  11. data/Dockerfile +1 -1
  12. data/Gemfile +28 -20
  13. data/README.md +72 -62
  14. data/Rakefile +13 -3
  15. data/benchmark/enabled_ips.rb +10 -0
  16. data/benchmark/enabled_multiple_actors_ips.rb +20 -0
  17. data/benchmark/enabled_profile.rb +20 -0
  18. data/benchmark/instrumentation_ips.rb +21 -0
  19. data/benchmark/typecast_ips.rb +27 -0
  20. data/docker-compose.yml +37 -34
  21. data/docs/DockerCompose.md +0 -1
  22. data/docs/README.md +1 -0
  23. data/docs/images/banner.jpg +0 -0
  24. data/docs/images/flipper_cloud.png +0 -0
  25. data/examples/api/basic.ru +18 -0
  26. data/examples/api/custom_memoized.ru +36 -0
  27. data/examples/api/memoized.ru +42 -0
  28. data/examples/basic.rb +1 -12
  29. data/examples/cloud/app.ru +12 -0
  30. data/examples/cloud/backoff_policy.rb +13 -0
  31. data/examples/cloud/basic.rb +22 -0
  32. data/examples/cloud/cloud_setup.rb +20 -0
  33. data/examples/cloud/forked.rb +36 -0
  34. data/examples/cloud/import.rb +17 -0
  35. data/examples/cloud/poll_interval/README.md +111 -0
  36. data/examples/cloud/poll_interval/client.rb +108 -0
  37. data/examples/cloud/poll_interval/server.rb +98 -0
  38. data/examples/cloud/threaded.rb +33 -0
  39. data/examples/configuring_default.rb +2 -5
  40. data/examples/dsl.rb +10 -35
  41. data/examples/enabled_for_actor.rb +10 -15
  42. data/examples/expressions.rb +237 -0
  43. data/examples/group.rb +3 -6
  44. data/examples/group_dynamic_lookup.rb +5 -19
  45. data/examples/group_with_members.rb +4 -14
  46. data/examples/importing.rb +1 -1
  47. data/examples/individual_actor.rb +2 -5
  48. data/examples/instrumentation.rb +2 -2
  49. data/examples/instrumentation_last_accessed_at.rb +38 -0
  50. data/examples/memoizing.rb +35 -0
  51. data/examples/mirroring.rb +59 -0
  52. data/examples/percentage_of_actors.rb +6 -16
  53. data/examples/percentage_of_actors_enabled_check.rb +7 -10
  54. data/examples/percentage_of_actors_group.rb +5 -18
  55. data/examples/percentage_of_time.rb +3 -6
  56. data/examples/strict.rb +18 -0
  57. data/exe/flipper +5 -0
  58. data/flipper-cloud.gemspec +19 -0
  59. data/flipper.gemspec +10 -7
  60. data/lib/flipper/actor.rb +10 -3
  61. data/lib/flipper/adapter.rb +50 -8
  62. data/lib/flipper/adapter_builder.rb +44 -0
  63. data/lib/flipper/adapters/actor_limit.rb +54 -0
  64. data/lib/flipper/adapters/cache_base.rb +161 -0
  65. data/lib/flipper/adapters/dual_write.rb +63 -0
  66. data/lib/flipper/adapters/failover.rb +85 -0
  67. data/lib/flipper/adapters/failsafe.rb +72 -0
  68. data/lib/flipper/adapters/http/client.rb +64 -7
  69. data/lib/flipper/adapters/http/error.rb +19 -1
  70. data/lib/flipper/adapters/http.rb +97 -43
  71. data/lib/flipper/adapters/instrumented.rb +47 -26
  72. data/lib/flipper/adapters/memoizable.rb +44 -40
  73. data/lib/flipper/adapters/memory.rb +75 -111
  74. data/lib/flipper/adapters/operation_logger.rb +22 -78
  75. data/lib/flipper/adapters/poll/poller.rb +2 -0
  76. data/lib/flipper/adapters/poll.rb +52 -0
  77. data/lib/flipper/adapters/pstore.rb +27 -17
  78. data/lib/flipper/adapters/read_only.rb +8 -41
  79. data/lib/flipper/adapters/strict.rb +45 -0
  80. data/lib/flipper/adapters/sync/feature_synchronizer.rb +14 -1
  81. data/lib/flipper/adapters/sync/interval_synchronizer.rb +2 -7
  82. data/lib/flipper/adapters/sync/synchronizer.rb +13 -6
  83. data/lib/flipper/adapters/sync.rb +23 -29
  84. data/lib/flipper/adapters/wrapper.rb +54 -0
  85. data/lib/flipper/cli.rb +314 -0
  86. data/lib/flipper/cloud/configuration.rb +271 -0
  87. data/lib/flipper/cloud/dsl.rb +27 -0
  88. data/lib/flipper/cloud/message_verifier.rb +95 -0
  89. data/lib/flipper/cloud/middleware.rb +63 -0
  90. data/lib/flipper/cloud/migrate.rb +71 -0
  91. data/lib/flipper/cloud/routes.rb +14 -0
  92. data/lib/flipper/cloud/telemetry/backoff_policy.rb +96 -0
  93. data/lib/flipper/cloud/telemetry/instrumenter.rb +22 -0
  94. data/lib/flipper/cloud/telemetry/metric.rb +39 -0
  95. data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
  96. data/lib/flipper/cloud/telemetry/submitter.rb +100 -0
  97. data/lib/flipper/cloud/telemetry.rb +191 -0
  98. data/lib/flipper/cloud.rb +54 -0
  99. data/lib/flipper/configuration.rb +54 -7
  100. data/lib/flipper/dsl.rb +58 -47
  101. data/lib/flipper/engine.rb +102 -0
  102. data/lib/flipper/errors.rb +3 -21
  103. data/lib/flipper/export.rb +24 -0
  104. data/lib/flipper/exporter.rb +17 -0
  105. data/lib/flipper/exporters/json/export.rb +32 -0
  106. data/lib/flipper/exporters/json/v1.rb +33 -0
  107. data/lib/flipper/expression/builder.rb +73 -0
  108. data/lib/flipper/expression/constant.rb +25 -0
  109. data/lib/flipper/expression.rb +71 -0
  110. data/lib/flipper/expressions/all.rb +9 -0
  111. data/lib/flipper/expressions/any.rb +9 -0
  112. data/lib/flipper/expressions/boolean.rb +9 -0
  113. data/lib/flipper/expressions/comparable.rb +13 -0
  114. data/lib/flipper/expressions/equal.rb +9 -0
  115. data/lib/flipper/expressions/feature_enabled.rb +34 -0
  116. data/lib/flipper/expressions/greater_than.rb +9 -0
  117. data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
  118. data/lib/flipper/expressions/less_than.rb +9 -0
  119. data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
  120. data/lib/flipper/expressions/not_equal.rb +9 -0
  121. data/lib/flipper/expressions/now.rb +9 -0
  122. data/lib/flipper/expressions/number.rb +9 -0
  123. data/lib/flipper/expressions/percentage.rb +9 -0
  124. data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
  125. data/lib/flipper/expressions/property.rb +9 -0
  126. data/lib/flipper/expressions/random.rb +9 -0
  127. data/lib/flipper/expressions/string.rb +9 -0
  128. data/lib/flipper/expressions/time.rb +16 -0
  129. data/lib/flipper/feature.rb +95 -28
  130. data/lib/flipper/feature_check_context.rb +10 -6
  131. data/lib/flipper/gate.rb +13 -11
  132. data/lib/flipper/gate_values.rb +5 -18
  133. data/lib/flipper/gates/actor.rb +10 -17
  134. data/lib/flipper/gates/boolean.rb +1 -1
  135. data/lib/flipper/gates/expression.rb +75 -0
  136. data/lib/flipper/gates/group.rb +5 -7
  137. data/lib/flipper/gates/percentage_of_actors.rb +10 -13
  138. data/lib/flipper/gates/percentage_of_time.rb +1 -2
  139. data/lib/flipper/identifier.rb +17 -0
  140. data/lib/flipper/instrumentation/log_subscriber.rb +35 -8
  141. data/lib/flipper/instrumentation/statsd.rb +4 -2
  142. data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
  143. data/lib/flipper/instrumentation/subscriber.rb +8 -5
  144. data/lib/flipper/instrumenters/memory.rb +6 -2
  145. data/lib/flipper/metadata.rb +8 -1
  146. data/lib/flipper/middleware/memoizer.rb +46 -27
  147. data/lib/flipper/middleware/setup_env.rb +13 -3
  148. data/lib/flipper/model/active_record.rb +23 -0
  149. data/lib/flipper/poller.rb +157 -0
  150. data/lib/flipper/serializers/gzip.rb +22 -0
  151. data/lib/flipper/serializers/json.rb +17 -0
  152. data/lib/flipper/spec/shared_adapter_specs.rb +122 -56
  153. data/lib/flipper/test/shared_adapter_test.rb +120 -52
  154. data/lib/flipper/test_help.rb +43 -0
  155. data/lib/flipper/typecast.rb +59 -18
  156. data/lib/flipper/types/actor.rb +19 -13
  157. data/lib/flipper/types/group.rb +12 -5
  158. data/lib/flipper/types/percentage.rb +1 -1
  159. data/lib/flipper/version.rb +11 -1
  160. data/lib/flipper.rb +71 -12
  161. data/lib/generators/flipper/setup_generator.rb +68 -0
  162. data/lib/generators/flipper/templates/initializer.rb +45 -0
  163. data/lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb +22 -0
  164. data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb +18 -0
  165. data/lib/generators/flipper/update_generator.rb +35 -0
  166. data/package-lock.json +41 -0
  167. data/package.json +10 -0
  168. data/spec/fixtures/environment.rb +1 -0
  169. data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
  170. data/spec/flipper/actor_spec.rb +10 -2
  171. data/spec/flipper/adapter_builder_spec.rb +72 -0
  172. data/spec/flipper/adapter_spec.rb +52 -6
  173. data/spec/flipper/adapters/actor_limit_spec.rb +75 -0
  174. data/spec/flipper/adapters/dual_write_spec.rb +82 -0
  175. data/spec/flipper/adapters/failover_spec.rb +141 -0
  176. data/spec/flipper/adapters/failsafe_spec.rb +58 -0
  177. data/spec/flipper/adapters/http/client_spec.rb +61 -0
  178. data/spec/flipper/adapters/http_spec.rb +402 -65
  179. data/spec/flipper/adapters/instrumented_spec.rb +31 -13
  180. data/spec/flipper/adapters/memoizable_spec.rb +51 -33
  181. data/spec/flipper/adapters/memory_spec.rb +33 -5
  182. data/spec/flipper/adapters/operation_logger_spec.rb +38 -12
  183. data/spec/flipper/adapters/poll_spec.rb +41 -0
  184. data/spec/flipper/adapters/pstore_spec.rb +0 -2
  185. data/spec/flipper/adapters/read_only_spec.rb +32 -18
  186. data/spec/flipper/adapters/strict_spec.rb +64 -0
  187. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +39 -1
  188. data/spec/flipper/adapters/sync/interval_synchronizer_spec.rb +4 -5
  189. data/spec/flipper/adapters/sync/synchronizer_spec.rb +87 -1
  190. data/spec/flipper/adapters/sync_spec.rb +17 -6
  191. data/spec/flipper/cli_spec.rb +217 -0
  192. data/spec/flipper/cloud/configuration_spec.rb +257 -0
  193. data/spec/flipper/cloud/dsl_spec.rb +90 -0
  194. data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
  195. data/spec/flipper/cloud/middleware_spec.rb +307 -0
  196. data/spec/flipper/cloud/migrate_spec.rb +160 -0
  197. data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +107 -0
  198. data/spec/flipper/cloud/telemetry/metric_spec.rb +87 -0
  199. data/spec/flipper/cloud/telemetry/metric_storage_spec.rb +58 -0
  200. data/spec/flipper/cloud/telemetry/submitter_spec.rb +145 -0
  201. data/spec/flipper/cloud/telemetry_spec.rb +208 -0
  202. data/spec/flipper/cloud_spec.rb +186 -0
  203. data/spec/flipper/configuration_spec.rb +37 -3
  204. data/spec/flipper/dsl_spec.rb +67 -80
  205. data/spec/flipper/engine_spec.rb +374 -0
  206. data/spec/flipper/export_spec.rb +13 -0
  207. data/spec/flipper/exporter_spec.rb +16 -0
  208. data/spec/flipper/exporters/json/export_spec.rb +60 -0
  209. data/spec/flipper/exporters/json/v1_spec.rb +33 -0
  210. data/spec/flipper/expression/builder_spec.rb +248 -0
  211. data/spec/flipper/expression_spec.rb +188 -0
  212. data/spec/flipper/expressions/all_spec.rb +15 -0
  213. data/spec/flipper/expressions/any_spec.rb +15 -0
  214. data/spec/flipper/expressions/boolean_spec.rb +15 -0
  215. data/spec/flipper/expressions/equal_spec.rb +24 -0
  216. data/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +28 -0
  217. data/spec/flipper/expressions/greater_than_spec.rb +28 -0
  218. data/spec/flipper/expressions/less_than_or_equal_to_spec.rb +28 -0
  219. data/spec/flipper/expressions/less_than_spec.rb +32 -0
  220. data/spec/flipper/expressions/not_equal_spec.rb +15 -0
  221. data/spec/flipper/expressions/now_spec.rb +11 -0
  222. data/spec/flipper/expressions/number_spec.rb +21 -0
  223. data/spec/flipper/expressions/percentage_of_actors_spec.rb +20 -0
  224. data/spec/flipper/expressions/percentage_spec.rb +15 -0
  225. data/spec/flipper/expressions/property_spec.rb +13 -0
  226. data/spec/flipper/expressions/random_spec.rb +9 -0
  227. data/spec/flipper/expressions/string_spec.rb +11 -0
  228. data/spec/flipper/expressions/time_spec.rb +29 -0
  229. data/spec/flipper/feature_check_context_spec.rb +18 -20
  230. data/spec/flipper/feature_spec.rb +461 -48
  231. data/spec/flipper/gate_spec.rb +0 -2
  232. data/spec/flipper/gate_values_spec.rb +2 -34
  233. data/spec/flipper/gates/actor_spec.rb +0 -2
  234. data/spec/flipper/gates/boolean_spec.rb +1 -3
  235. data/spec/flipper/gates/expression_spec.rb +190 -0
  236. data/spec/flipper/gates/group_spec.rb +2 -5
  237. data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -7
  238. data/spec/flipper/gates/percentage_of_time_spec.rb +2 -4
  239. data/spec/flipper/identifier_spec.rb +12 -0
  240. data/spec/flipper/instrumentation/log_subscriber_spec.rb +24 -7
  241. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +26 -3
  242. data/spec/flipper/instrumenters/memory_spec.rb +18 -1
  243. data/spec/flipper/instrumenters/noop_spec.rb +14 -8
  244. data/spec/flipper/middleware/memoizer_spec.rb +199 -62
  245. data/spec/flipper/middleware/setup_env_spec.rb +23 -5
  246. data/spec/flipper/model/active_record_spec.rb +72 -0
  247. data/spec/flipper/poller_spec.rb +390 -0
  248. data/spec/flipper/registry_spec.rb +0 -1
  249. data/spec/flipper/serializers/gzip_spec.rb +13 -0
  250. data/spec/flipper/serializers/json_spec.rb +13 -0
  251. data/spec/flipper/typecast_spec.rb +121 -7
  252. data/spec/flipper/types/actor_spec.rb +63 -47
  253. data/spec/flipper/types/boolean_spec.rb +0 -1
  254. data/spec/flipper/types/group_spec.rb +24 -3
  255. data/spec/flipper/types/percentage_of_actors_spec.rb +0 -1
  256. data/spec/flipper/types/percentage_of_time_spec.rb +0 -1
  257. data/spec/flipper/types/percentage_spec.rb +0 -1
  258. data/spec/{integration_spec.rb → flipper_integration_spec.rb} +301 -59
  259. data/spec/flipper_spec.rb +123 -29
  260. data/spec/{helper.rb → spec_helper.rb} +23 -21
  261. data/spec/support/actor_names.yml +1 -0
  262. data/spec/support/descriptions.yml +1 -0
  263. data/spec/support/fail_on_output.rb +8 -0
  264. data/spec/support/fake_backoff_policy.rb +15 -0
  265. data/spec/support/skippable.rb +18 -0
  266. data/spec/support/spec_helpers.rb +53 -6
  267. data/test/adapters/actor_limit_test.rb +20 -0
  268. data/test/test_helper.rb +2 -1
  269. data/test_rails/generators/flipper/setup_generator_test.rb +69 -0
  270. data/test_rails/generators/flipper/update_generator_test.rb +96 -0
  271. data/test_rails/helper.rb +31 -0
  272. data/test_rails/system/test_help_test.rb +52 -0
  273. metadata +200 -82
  274. data/.rubocop.yml +0 -54
  275. data/.rubocop_todo.yml +0 -199
  276. data/docs/Adapters.md +0 -124
  277. data/docs/Caveats.md +0 -4
  278. data/docs/Gates.md +0 -167
  279. data/docs/Instrumentation.md +0 -27
  280. data/docs/Optimization.md +0 -114
  281. data/docs/api/README.md +0 -849
  282. data/docs/http/README.md +0 -35
  283. data/docs/read-only/README.md +0 -21
  284. data/examples/example_setup.rb +0 -8
  285. data/test/helper.rb +0 -11
data/Gemfile CHANGED
@@ -6,28 +6,36 @@ Dir['flipper-*.gemspec'].each do |gemspec|
6
6
  gemspec(name: "flipper-#{plugin}", development_group: plugin)
7
7
  end
8
8
 
9
- gem 'pry'
10
- gem 'rake', '~> 10.4.2'
11
- gem 'shotgun', '~> 0.9'
9
+ gem 'concurrent-ruby', '1.3.4'
10
+ gem 'connection_pool'
11
+ gem 'debug'
12
+ gem 'rake'
12
13
  gem 'statsd-ruby', '~> 1.2.1'
13
14
  gem 'rspec', '~> 3.0'
14
- gem 'rack-test', '~> 0.6.3'
15
- gem 'sqlite3', '~> 1.3.11'
16
- gem 'rails', "~> #{ENV['RAILS_VERSION'] || '5.1.4'}"
17
- gem 'minitest', '~> 5.8.0'
18
- gem 'rubocop', '~> 0.45.0'
19
- gem 'rubocop-rspec', '= 1.5.1'
20
- gem 'webmock', '~> 2.0'
21
-
22
- # for active support tests in test/ and only needed for ruby 2.2.x
23
- gem 'test-unit', '~> 3.0'
15
+ gem 'rack-test'
16
+ gem 'rackup', '= 1.0.0'
17
+ gem 'sqlite3', "~> #{ENV['SQLITE3_VERSION'] || '2.1.0'}"
18
+ gem 'rails', "~> #{ENV['RAILS_VERSION'] || '8.0'}"
19
+ gem 'minitest', '~> 5.18'
20
+ gem 'minitest-documentation'
21
+ gem 'pstore'
22
+ gem 'webmock'
23
+ gem 'ice_age'
24
+ gem 'redis-namespace'
25
+ gem 'webrick'
26
+ gem 'stackprof'
27
+ gem 'benchmark-ips'
28
+ gem 'stackprof-webnav'
29
+ gem 'flamegraph'
30
+ gem 'mysql2'
31
+ gem 'pg'
32
+ gem 'cuprite'
33
+ gem 'puma'
34
+ gem 'warning'
24
35
 
25
36
  group(:guard) do
26
- gem 'guard', '~> 2.12.5'
27
- gem 'guard-rubocop', '~> 1.3.0'
28
- gem 'guard-rspec', '~> 4.5.0'
29
- gem 'guard-bundler', '~> 2.1.0'
30
- gem 'guard-coffeescript', '~> 2.0.1'
31
- gem 'guard-sass', '~> 1.6.0'
32
- gem 'rb-fsevent', '~> 0.9.4'
37
+ gem 'guard'
38
+ gem 'guard-rspec'
39
+ gem 'guard-bundler'
40
+ gem 'rb-fsevent'
33
41
  end
data/README.md CHANGED
@@ -1,23 +1,19 @@
1
- <pre>
2
- __
3
- _.-~ )
4
- _..--~~~~,' ,-/ _
5
- .-'. . . .' ,-',' ,' )
6
- ,'. . . _ ,--~,-'__..-' ,'
7
- ,'. . . (@)' ---~~~~ ,'
8
- /. . . . '~~ ,-'
9
- /. . . . . ,-'
10
- ; . . . . - . ,'
11
- : . . . . _ /
12
- . . . . . `-.:
13
- . . . ./ - . )
14
- . . . | _____..---.._/ _____
15
- ~---~~~~----~~~~ ~~
16
- </pre>
17
-
18
- Feature flipping is the act of enabling or disabling features or parts of your application, ideally without re-deploying or changing anything in your code base.
19
-
20
- The goal of this gem is to make turning features on or off so easy that everyone does it. Whatever your data store, throughput, or experience, feature flipping should be easy and have minimal impact on your application.
1
+ [![Flipper Mark](docs/images/banner.jpg)](https://www.flippercloud.io)
2
+
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
+
5
+ # Flipper
6
+
7
+ > Beautiful, performant feature flags for Ruby and Rails.
8
+
9
+ Flipper gives you control over who has access to features in your app.
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.
15
+
16
+ Control your software &mdash; don't let it control you.
21
17
 
22
18
  ## Installation
23
19
 
@@ -25,6 +21,10 @@ Add this line to your application's Gemfile:
25
21
 
26
22
  gem 'flipper'
27
23
 
24
+ You'll also want to pick a storage [adapter](https://flippercloud.io/docs/adapters), for example:
25
+
26
+ gem 'flipper-active_record'
27
+
28
28
  And then execute:
29
29
 
30
30
  $ bundle
@@ -33,74 +33,84 @@ Or install it yourself with:
33
33
 
34
34
  $ gem install flipper
35
35
 
36
- ## Examples
37
-
38
- The goal of the API for flipper was to have everything revolve around features and what ways they can be enabled. Start with top level and dig into a feature, then dig in further and enable that feature for a given type of access, as opposed to thinking about how the feature will be accessed first (ie: `stats.enable` vs `activate_group(:stats, ...)`).
36
+ ## Subscribe &amp; Ship
39
37
 
40
- ```ruby
41
- require 'flipper'
38
+ [💌 &nbsp;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/)).
42
39
 
43
- Flipper.configure do |config|
44
- config.default do
45
- # pick an adapter, this uses memory, any will do
46
- adapter = Flipper::Adapters::Memory.new
40
+ ## Getting Started
47
41
 
48
- # pass adapter to handy DSL instance
49
- Flipper.new(adapter)
50
- end
51
- end
42
+ Use `Flipper#enabled?` in your app to check if a feature is enabled.
52
43
 
44
+ ```ruby
53
45
  # check if search is enabled
54
- if Flipper.enabled?(:search)
46
+ if Flipper.enabled?(:search, current_user)
55
47
  puts 'Search away!'
56
48
  else
57
49
  puts 'No search for you!'
58
50
  end
51
+ ```
59
52
 
60
- puts 'Enabling Search...'
61
- Flipper.enable(:search)
53
+ All features are disabled by default, so you'll need to explicitly enable them.
62
54
 
63
- # check if search is enabled
64
- if Flipper.enabled?(:search)
65
- puts 'Search away!'
66
- else
67
- puts 'No search for you!'
68
- end
55
+ ```ruby
56
+ # Enable a feature for everyone
57
+ Flipper.enable :search
58
+
59
+ # Enable a feature for a specific actor
60
+ Flipper.enable_actor :search, current_user
61
+
62
+ # Enable a feature for a group of actors
63
+ Flipper.enable_group :search, :admin
64
+
65
+ # Enable a feature for a percentage of actors
66
+ Flipper.enable_percentage_of_actors :search, 2
69
67
  ```
70
68
 
71
- Of course there are more [examples for you to peruse](examples/). You could also check out the [DSL](lib/flipper/dsl.rb) and [Feature](lib/flipper/feature.rb) classes for code/docs.
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
+
71
+ ## Flipper Cloud
72
+
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
+
75
+ - **multiple environments** &mdash; 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** &mdash; 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** &mdash; 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** &mdash; every feature change and who made it.
79
+ - **rollbacks** &mdash; 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** &mdash; 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** &mdash; no need to bounce around from different application UIs or IRB consoles.
82
+
83
+ [![Flipper Cloud Screenshot](docs/images/flipper_cloud.png)](https://www.flippercloud.io?utm_source=oss&utm_medium=readme&utm_campaign=screenshot)
72
84
 
73
- ## Docs
85
+ Cloud is super simple to integrate with Rails ([demo app](https://github.com/fewerandfaster/flipper-rails-demo)), Sinatra or any other framework.
74
86
 
75
- * [Gates](docs/Gates.md) - Boolean, Groups, Actors, % of Actors, and % of Time
76
- * [Adapters](docs/Adapters.md) - Mongo, Redis, Cassandra, Active Record...
77
- * [Instrumentation](docs/Instrumentation.md) - ActiveSupport::Notifications and Statsd
78
- * [Optimization](docs/Optimization.md) - Memoization middleware and Cache adapters
79
- * [Web Interface](docs/ui/README.md) - Point and click...
80
- * [API](docs/api/README.md) - HTTP API interface
81
- * [Caveats](docs/Caveats.md) - Flipper beware! (see what I did there)
82
- * [Docker-Compose](docs/DockerCompose.md) - Using docker-compose in contributing
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.
83
88
 
84
89
  ## Contributing
85
90
 
86
91
  1. Fork it
87
92
  2. Create your feature branch (`git checkout -b my-new-feature`)
88
- 3. Check your changes with Rubocop tests (`script/rubocop`)
93
+ 3. Run the tests (`bundle exec rake`). Check out [Docker-Compose](docs/DockerCompose.md) if you need help getting all the adapters running.
89
94
  4. Commit your changes (`git commit -am 'Added some feature'`)
90
95
  5. Push to the branch (`git push origin my-new-feature`)
91
96
  6. Create new Pull Request
92
97
 
93
98
  ## Releasing
94
99
 
95
- 1. Update the version to be whatever it should be and commit.
96
- 2. `script/release`
97
- 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).
98
104
 
99
105
  ## Brought To You By
100
106
 
101
- | pic | @mention | area |
102
- |---|---|---|
103
- | ![@jnunemaker](https://avatars3.githubusercontent.com/u/235?s=64) | [@jnunemaker](https://github.com/jnunemaker) | most things |
104
- | ![@alexwheeler](https://avatars3.githubusercontent.com/u/3260042?s=64) | [@alexwheeler](https://github.com/alexwheeler) | api |
105
- | ![@thetimbanks](https://avatars1.githubusercontent.com/u/471801?s=64) | [@thetimbanks](https://github.com/thetimbanks) | ui |
106
- | ![@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
@@ -18,14 +18,17 @@ end
18
18
  desc 'Tags version, pushes to remote, and pushes gem'
19
19
  task release: :build do
20
20
  sh 'git', 'tag', "v#{Flipper::VERSION}"
21
- sh 'git push origin master'
21
+ sh 'git push origin main'
22
22
  sh "git push origin v#{Flipper::VERSION}"
23
- sh 'ls pkg/*.gem | xargs -n 1 gem push'
23
+ puts "\nWhat OTP code should be used?"
24
+ otp_code = STDIN.gets.chomp
25
+ sh "ls pkg/*.gem | xargs -n 1 gem push --otp #{otp_code}"
24
26
  end
25
27
 
26
28
  require 'rspec/core/rake_task'
27
29
  RSpec::Core::RakeTask.new(:spec) do |t|
28
30
  t.rspec_opts = %w(--color)
31
+ t.verbose = false
29
32
  end
30
33
 
31
34
  namespace :spec do
@@ -39,6 +42,13 @@ end
39
42
  Rake::TestTask.new do |t|
40
43
  t.libs = %w(lib test)
41
44
  t.pattern = 'test/**/*_test.rb'
45
+ t.warning = false
42
46
  end
43
47
 
44
- task default: [:spec, :test]
48
+ Rake::TestTask.new(:test_rails) do |t|
49
+ t.libs = %w(lib test_rails)
50
+ t.pattern = 'test_rails/**/*_test.rb'
51
+ t.warning = false
52
+ end
53
+
54
+ task default: [:spec, :test, :test_rails]
@@ -0,0 +1,10 @@
1
+ require 'bundler/setup'
2
+ require 'flipper'
3
+ require 'benchmark/ips'
4
+
5
+ actor = Flipper::Actor.new("User;1")
6
+
7
+ Benchmark.ips do |x|
8
+ x.report("with actor") { Flipper.enabled?(:foo, actor) }
9
+ x.report("without actor") { Flipper.enabled?(:foo) }
10
+ end
@@ -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
data/docker-compose.yml CHANGED
@@ -1,34 +1,37 @@
1
- # postgres:
2
- # container_name: flipper_postgres
3
- # image: postgres:9.4
4
- redis:
5
- container_name: flipper_redis
6
- image: redis:2.8
7
- mongo:
8
- container_name: flipper_mongo
9
- image: mongo:3.3
10
- memcached:
11
- container_name: flipper_memcached
12
- image: memcached:1.4.33
13
- app:
14
- container_name: flipper_app
15
- build: .
16
- dockerfile: Dockerfile
17
- volumes:
18
- - .:/srv/app
19
- volumes_from:
20
- - bundle_cache
21
- links:
22
- # - postgres
23
- - redis
24
- - mongo
25
- - memcached
26
- environment:
27
- - REDIS_URL=redis://redis:6379
28
- - MONGODB_HOST=mongo
29
- - MEMCACHED_URL=memcached:11211
30
- bundle_cache:
31
- container_name: flipper_bundle_cache
32
- image: busybox
33
- volumes:
34
- - /bundle_cache
1
+ version: "2.4"
2
+ services:
3
+ # postgres:
4
+ # container_name: flipper_postgres
5
+ # image: postgres:9.4
6
+ redis:
7
+ container_name: flipper_redis
8
+ image: redis:6.2.5
9
+ mongo:
10
+ container_name: flipper_mongo
11
+ image: mongo:4.4.8
12
+ memcached:
13
+ container_name: flipper_memcached
14
+ image: memcached:1.4.33
15
+ app:
16
+ container_name: flipper_app
17
+ build:
18
+ context: .
19
+ dockerfile: Dockerfile
20
+ volumes:
21
+ - .:/srv/app
22
+ volumes_from:
23
+ - bundle_cache
24
+ links:
25
+ # - postgres
26
+ - redis
27
+ - mongo
28
+ - memcached
29
+ environment:
30
+ - REDIS_URL=redis://redis:6379
31
+ - MONGODB_HOST=mongo
32
+ - MEMCACHED_URL=memcached:11211
33
+ bundle_cache:
34
+ container_name: flipper_bundle_cache
35
+ image: busybox
36
+ volumes:
37
+ - /bundle_cache
@@ -11,7 +11,6 @@ new contributor could start working on code with a minumum efforts.
11
11
  1. Install gems `docker-compose run --rm app bundle install`
12
12
  1. Run specs `docker-compose run --rm app bundle exec rspec`
13
13
  1. Run tests `docker-compose run --rm app bundle exec rake test`
14
- 1. Clear and check files with Rubocop `docker-compose run --rm app bundle exec rubocop -D`
15
14
  1. Optional: log in to container an using a `bash` shell for running specs
16
15
  ```sh
17
16
  docker-compose run --rm app bash
data/docs/README.md ADDED
@@ -0,0 +1 @@
1
+ See [flippercloud.io](https://flippercloud.io/docs) for extensive docs.
Binary file
Binary file
@@ -0,0 +1,18 @@
1
+ #
2
+ # Usage:
3
+ # bin/rackup examples/api/basic.ru -p 9999
4
+ #
5
+ # http://localhost:9999/
6
+ #
7
+
8
+ require 'bundler/setup'
9
+ require 'rack/reloader'
10
+ require "flipper/api"
11
+ require "flipper/adapters/pstore"
12
+
13
+ # You can uncomment this to get some default data:
14
+ # Flipper.enable :logging
15
+
16
+ use Rack::Reloader
17
+
18
+ run Flipper::Api.app
@@ -0,0 +1,36 @@
1
+ #
2
+ # Usage:
3
+ # bin/rackup examples/api/custom_memoized.ru -p 9999
4
+ #
5
+ # http://localhost:9999/
6
+ #
7
+
8
+ require 'bundler/setup'
9
+ require 'rack/reloader'
10
+ require "active_support/notifications"
11
+ require "flipper/api"
12
+ require "flipper/adapters/pstore"
13
+
14
+ adapter = Flipper::Adapters::Instrumented.new(
15
+ Flipper::Adapters::PStore.new,
16
+ instrumenter: ActiveSupport::Notifications,
17
+ )
18
+ flipper = Flipper.new(adapter)
19
+
20
+ ActiveSupport::Notifications.subscribe(/.*/, ->(*args) {
21
+ name, start, finish, id, data = args
22
+ case name
23
+ when "adapter_operation.flipper"
24
+ p data[:adapter_name] => data[:operation]
25
+ end
26
+ })
27
+
28
+ # You can uncomment this to get some default data:
29
+ # flipper[:logging].enable_percentage_of_time 5
30
+
31
+ use Rack::Reloader
32
+
33
+ run Flipper::Api.app(flipper) { |builder|
34
+ builder.use Flipper::Middleware::SetupEnv, flipper
35
+ builder.use Flipper::Middleware::Memoizer, preload: true
36
+ }
@@ -0,0 +1,42 @@
1
+ #
2
+ # Usage:
3
+ # bin/rackup examples/api/memoized.ru -p 9999
4
+ #
5
+ # http://localhost:9999/
6
+ #
7
+
8
+ require 'bundler/setup'
9
+ require 'rack/reloader'
10
+ require "active_support/notifications"
11
+ require "flipper/api"
12
+ require "flipper/adapters/pstore"
13
+
14
+ Flipper.configure do |config|
15
+ config.adapter {
16
+ Flipper::Adapters::Instrumented.new(
17
+ Flipper::Adapters::PStore.new,
18
+ instrumenter: ActiveSupport::Notifications,
19
+ )
20
+ }
21
+ end
22
+
23
+ ActiveSupport::Notifications.subscribe(/.*/, ->(*args) {
24
+ name, start, finish, id, data = args
25
+ case name
26
+ when "adapter_operation.flipper"
27
+ p data[:adapter_name] => data[:operation]
28
+ end
29
+ })
30
+
31
+ Flipper.register(:admins) { |actor|
32
+ actor.respond_to?(:admin?) && actor.admin?
33
+ }
34
+
35
+ # You can uncomment this to get some default data:
36
+ # Flipper.enable :logging
37
+
38
+ use Rack::Reloader
39
+
40
+ run Flipper::Api.app { |builder|
41
+ builder.use Flipper::Middleware::Memoizer, preload: true
42
+ }
data/examples/basic.rb CHANGED
@@ -1,17 +1,6 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
 
5
- Flipper.configure do |config|
6
- config.default do
7
- # pick an adapter, this uses memory, any will do
8
- adapter = Flipper::Adapters::Memory.new
9
-
10
- # pass adapter to handy DSL instance
11
- Flipper.new(adapter)
12
- end
13
- end
14
-
15
4
  # check if search is enabled
16
5
  if Flipper.enabled?(:search)
17
6
  puts 'Search away!'
@@ -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