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
@@ -1,7 +1,44 @@
1
1
  module Flipper
2
2
  class Configuration
3
- def initialize
4
- @default = -> { raise DefaultNotSet }
3
+ def initialize(options = {})
4
+ @builder = AdapterBuilder.new { store Flipper::Adapters::Memory }
5
+ @default = -> { Flipper.new(@builder.to_adapter) }
6
+ end
7
+
8
+ # The default adapter to use.
9
+ #
10
+ # Pass a block to assign the adapter, and invoke without a block to
11
+ # return the configured adapter instance.
12
+ #
13
+ # Flipper.configure do |config|
14
+ # config.adapter # => instance of default Memory adapter
15
+ #
16
+ # # Configure it to use the ActiveRecord adapter
17
+ # config.adapter do
18
+ # require "flipper/adapters/active_record"
19
+ # Flipper::Adapters::ActiveRecord.new
20
+ # end
21
+ #
22
+ # config.adapter # => instance of ActiveRecord adapter
23
+ # end
24
+ #
25
+ def adapter(&block)
26
+ if block_given?
27
+ @builder.store(block)
28
+ else
29
+ @builder.to_adapter
30
+ end
31
+ end
32
+
33
+ # An adapter to use to augment the primary storage adapter. See `AdapterBuilder#use`
34
+ if RUBY_VERSION >= '3.0'
35
+ def use(klass, *args, **kwargs, &block)
36
+ @builder.use(klass, *args, **kwargs, &block)
37
+ end
38
+ else
39
+ def use(klass, *args, &block)
40
+ @builder.use(klass, *args, &block)
41
+ end
5
42
  end
6
43
 
7
44
  # Controls the default instance for flipper. When used with a block it
@@ -9,15 +46,15 @@ module Flipper
9
46
  # without a block, it performs a block invocation and returns the result.
10
47
  #
11
48
  # configuration = Flipper::Configuration.new
12
- # configuration.default # => raises DefaultNotSet error.
49
+ # configuration.default # => Flipper::DSL instance using Memory adapter
13
50
  #
14
- # # sets the default block to generate a new instance using Memory adapter
51
+ # # sets the default block to generate a new instance using ActiveRecord adapter
15
52
  # configuration.default do
16
- # require "flipper/adapters/memory"
17
- # Flipper.new(Flipper::Adapters::Memory.new)
53
+ # require "flipper/adapters/active_record"
54
+ # Flipper.new(Flipper::Adapters::ActiveRecord.new)
18
55
  # end
19
56
  #
20
- # configuration.default # => Flipper::DSL instance using Memory adapter
57
+ # configuration.default # => Flipper::DSL instance using ActiveRecord adapter
21
58
  #
22
59
  # Returns result of default block invocation if called without block. If
23
60
  # called with block, assigns the default block.
@@ -28,5 +65,15 @@ module Flipper
28
65
  @default.call
29
66
  end
30
67
  end
68
+
69
+ def statsd
70
+ require 'flipper/instrumentation/statsd_subscriber'
71
+ Flipper::Instrumentation::StatsdSubscriber.client
72
+ end
73
+
74
+ def statsd=(client)
75
+ require "flipper/instrumentation/statsd"
76
+ Flipper::Instrumentation::StatsdSubscriber.client = client
77
+ end
31
78
  end
32
79
  end
data/lib/flipper/dsl.rb CHANGED
@@ -10,17 +10,19 @@ module Flipper
10
10
  # Private: What is being used to instrument all the things.
11
11
  attr_reader :instrumenter
12
12
 
13
- def_delegators :@adapter, :memoize=, :memoizing?
13
+ def_delegators :@adapter, :memoize=, :memoizing?, :import, :export, :adapter_stack
14
14
 
15
15
  # Public: Returns a new instance of the DSL.
16
16
  #
17
17
  # adapter - The adapter that this DSL instance should use.
18
18
  # options - The Hash of options.
19
19
  # :instrumenter - What should be used to instrument all the things.
20
+ # :memoize - Should adapter be wrapped by memoize adapter or not.
20
21
  def initialize(adapter, options = {})
21
22
  @instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
22
- memoized = Adapters::Memoizable.new(adapter)
23
- @adapter = memoized
23
+ memoize = options.fetch(:memoize, true)
24
+ adapter = Adapters::Memoizable.new(adapter) if memoize
25
+ @adapter = adapter
24
26
  @memoized_features = {}
25
27
  end
26
28
 
@@ -44,6 +46,25 @@ module Flipper
44
46
  feature(name).enable(*args)
45
47
  end
46
48
 
49
+ # Public: Enable a feature for an expression.
50
+ #
51
+ # name - The String or Symbol name of the feature.
52
+ # expression - a Flipper::Expression instance or a Hash.
53
+ #
54
+ # Returns result of Feature#enable.
55
+ def enable_expression(name, expression)
56
+ feature(name).enable_expression(expression)
57
+ end
58
+
59
+ # Public: Add an expression to a feature.
60
+ #
61
+ # expression - an expression or Hash that can be converted to an expression.
62
+ #
63
+ # Returns result of enable.
64
+ def add_expression(name, expression)
65
+ feature(name).add_expression(expression)
66
+ end
67
+
47
68
  # Public: Enable a feature for an actor.
48
69
  #
49
70
  # name - The String or Symbol name of the feature.
@@ -98,6 +119,24 @@ module Flipper
98
119
  feature(name).disable(*args)
99
120
  end
100
121
 
122
+ # Public: Disable expression for feature.
123
+ #
124
+ # name - The String or Symbol name of the feature.
125
+ #
126
+ # Returns result of Feature#disable.
127
+ def disable_expression(name)
128
+ feature(name).disable_expression
129
+ end
130
+
131
+ # Public: Remove an expression from a feature.
132
+ #
133
+ # expression - an Expression or Hash that can be converted to an expression.
134
+ #
135
+ # Returns result of enable.
136
+ def remove_expression(name, expression)
137
+ feature(name).remove_expression(expression)
138
+ end
139
+
101
140
  # Public: Disable a feature for an actor.
102
141
  #
103
142
  # name - The String or Symbol name of the feature.
@@ -208,22 +247,6 @@ module Flipper
208
247
  # Returns an instance of Flipper::Feature.
209
248
  alias_method :[], :feature
210
249
 
211
- # Public: Shortcut for getting a boolean type instance.
212
- #
213
- # value - The true or false value for the boolean.
214
- #
215
- # Returns a Flipper::Types::Boolean instance.
216
- def boolean(value = true)
217
- Types::Boolean.new(value)
218
- end
219
-
220
- # Public: Even shorter shortcut for getting a boolean type instance.
221
- #
222
- # value - The true or false value for the boolean.
223
- #
224
- # Returns a Flipper::Types::Boolean instance.
225
- alias_method :bool, :boolean
226
-
227
250
  # Public: Access a flipper group by name.
228
251
  #
229
252
  # name - The String or Symbol name of the feature.
@@ -233,35 +256,14 @@ module Flipper
233
256
  Flipper.group(name)
234
257
  end
235
258
 
236
- # Public: Wraps an object as a flipper actor.
237
- #
238
- # thing - The object that you would like to wrap.
239
- #
240
- # Returns an instance of Flipper::Types::Actor.
241
- # Raises ArgumentError if thing does not respond to `flipper_id`.
242
- def actor(thing)
243
- Types::Actor.new(thing)
244
- end
245
-
246
- # Public: Shortcut for getting a percentage of time instance.
259
+ # Public: Gets the expression for the feature.
247
260
  #
248
- # number - The percentage of time that should be enabled.
249
- #
250
- # Returns Flipper::Types::PercentageOfTime.
251
- def time(number)
252
- Types::PercentageOfTime.new(number)
253
- end
254
- alias_method :percentage_of_time, :time
255
-
256
- # Public: Shortcut for getting a percentage of actors instance.
257
- #
258
- # number - The percentage of actors that should be enabled.
261
+ # name - The String or Symbol name of the feature.
259
262
  #
260
- # Returns Flipper::Types::PercentageOfActors.
261
- def actors(number)
262
- Types::PercentageOfActors.new(number)
263
+ # Returns an instance of Flipper::Expression.
264
+ def expression(name)
265
+ feature(name).expression
263
266
  end
264
- alias_method :percentage_of_actors, :actors
265
267
 
266
268
  # Public: Returns a Set of the known features for this adapter.
267
269
  #
@@ -270,8 +272,17 @@ module Flipper
270
272
  adapter.features.map { |name| feature(name) }.to_set
271
273
  end
272
274
 
273
- def import(flipper)
274
- adapter.import(flipper.adapter)
275
+ # Public: Does this adapter support writes or not.
276
+ def read_only?
277
+ adapter.read_only?
278
+ end
279
+
280
+ # Cloud DSL method that does nothing for open source version.
281
+ def sync
282
+ end
283
+
284
+ # Cloud DSL method that does nothing for open source version.
285
+ def sync_secret
275
286
  end
276
287
  end
277
288
  end
@@ -0,0 +1,102 @@
1
+ module Flipper
2
+ class Engine < Rails::Engine
3
+ def self.default_strict_value
4
+ value = ENV["FLIPPER_STRICT"]
5
+ if value.in?(["warn", "raise", "noop"])
6
+ value.to_sym
7
+ elsif value
8
+ Typecast.to_boolean(value) ? :raise : false
9
+ elsif Rails.env.production?
10
+ false
11
+ else
12
+ # Warn in development for now. Future versions may default to :raise in development and test
13
+ Rails.env.development? && :warn
14
+ end
15
+ end
16
+
17
+ paths["config/routes.rb"] = ["lib/flipper/cloud/routes.rb"]
18
+
19
+ config.before_configuration do
20
+ config.flipper = ActiveSupport::OrderedOptions.new.update(
21
+ env_key: ENV.fetch('FLIPPER_ENV_KEY', 'flipper'),
22
+ memoize: ENV.fetch('FLIPPER_MEMOIZE', 'true').casecmp('true').zero?,
23
+ preload: ENV.fetch('FLIPPER_PRELOAD', 'true').casecmp('true').zero?,
24
+ instrumenter: ENV.fetch('FLIPPER_INSTRUMENTER', 'ActiveSupport::Notifications').constantize,
25
+ log: ENV.fetch('FLIPPER_LOG', 'true').casecmp('true').zero?,
26
+ cloud_path: "_flipper",
27
+ strict: default_strict_value,
28
+ actor_limit: ENV["FLIPPER_ACTOR_LIMIT"]&.to_i || 100,
29
+ test_help: Flipper::Typecast.to_boolean(ENV["FLIPPER_TEST_HELP"] || Rails.env.test?),
30
+ )
31
+ end
32
+
33
+ initializer "flipper.properties" do
34
+ ActiveSupport.on_load(:active_record) do
35
+ require "flipper/model/active_record"
36
+ ActiveRecord::Base.include Flipper::Model::ActiveRecord
37
+ end
38
+ end
39
+
40
+ initializer "flipper.default", before: :load_config_initializers do |app|
41
+ # Load cloud secrets from Rails credentials
42
+ ENV["FLIPPER_CLOUD_TOKEN"] ||= app.credentials.dig(:flipper, :cloud_token)
43
+ ENV["FLIPPER_CLOUD_SYNC_SECRET"] ||= app.credentials.dig(:flipper, :cloud_sync_secret)
44
+
45
+ require 'flipper/cloud' if cloud?
46
+
47
+ Flipper.configure do |config|
48
+ config.default do
49
+ if cloud?
50
+ Flipper::Cloud.new(
51
+ local_adapter: config.adapter,
52
+ instrumenter: app.config.flipper.instrumenter
53
+ )
54
+ else
55
+ Flipper.new(config.adapter, instrumenter: app.config.flipper.instrumenter)
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ initializer "flipper.log", after: :load_config_initializers do |app|
62
+ flipper = app.config.flipper
63
+
64
+ if flipper.log && flipper.instrumenter == ActiveSupport::Notifications
65
+ require "flipper/instrumentation/log_subscriber"
66
+ end
67
+ end
68
+
69
+ initializer "flipper.adapters", after: :load_config_initializers do |app|
70
+ flipper = app.config.flipper
71
+
72
+ Flipper.configure do |config|
73
+ config.use Flipper::Adapters::Strict, flipper.strict if flipper.strict
74
+ config.use Flipper::Adapters::ActorLimit, flipper.actor_limit if flipper.actor_limit
75
+ end
76
+ end
77
+
78
+ initializer "flipper.memoizer", after: :load_config_initializers do |app|
79
+ flipper = app.config.flipper
80
+
81
+ if flipper.memoize
82
+ app.middleware.use Flipper::Middleware::Memoizer, {
83
+ env_key: flipper.env_key,
84
+ preload: flipper.preload,
85
+ if: flipper.memoize.respond_to?(:call) ? flipper.memoize : nil
86
+ }
87
+ end
88
+ end
89
+
90
+ initializer "flipper.test" do |app|
91
+ require "flipper/test_help" if app.config.flipper.test_help
92
+ end
93
+
94
+ def cloud?
95
+ !!ENV["FLIPPER_CLOUD_TOKEN"]
96
+ end
97
+
98
+ def self.deprecated_rails_version?
99
+ Gem::Version.new(Rails.version) < Gem::Version.new(Flipper::NEXT_REQUIRED_RAILS_VERSION)
100
+ end
101
+ end
102
+ end
@@ -2,26 +2,16 @@ module Flipper
2
2
  # Top level error that all other errors inherit from.
3
3
  class Error < StandardError; end
4
4
 
5
- # Raised when gate can not be found for a thing.
5
+ # Raised when gate can not be found for an actor.
6
6
  class GateNotFound < Error
7
- def initialize(thing)
8
- super "Could not find gate for #{thing.inspect}"
7
+ def initialize(actor)
8
+ super "Could not find gate for #{actor.inspect}"
9
9
  end
10
10
  end
11
11
 
12
12
  # Raised when attempting to declare a group name that has already been used.
13
13
  class DuplicateGroup < Error; end
14
14
 
15
- # Raised when default instance not configured but there is an attempt to
16
- # use it.
17
- class DefaultNotSet < Flipper::Error
18
- def initialize(message = nil)
19
- default = "Default flipper instance not configured. See " \
20
- "Flipper.configure for how to configure the default instance."
21
- super(message || default)
22
- end
23
- end
24
-
25
15
  # Raised when an invalid value is set to a configuration property
26
16
  class InvalidConfigurationValue < Flipper::Error
27
17
  def initialize(message = nil)
@@ -29,12 +19,4 @@ module Flipper
29
19
  super(message || default)
30
20
  end
31
21
  end
32
-
33
- # Raised when accessing a configuration property that has been deprecated
34
- class ConfigurationDeprecated < Flipper::Error
35
- def initialize(message = nil)
36
- default = "The configuration property has been deprecated"
37
- super(message || default)
38
- end
39
- end
40
22
  end
@@ -0,0 +1,24 @@
1
+ module Flipper
2
+ class Export
3
+ attr_reader :contents, :format, :version
4
+
5
+ def initialize(contents:, format: :json, version: 1)
6
+ @contents = contents
7
+ @format = format
8
+ @version = version
9
+ end
10
+
11
+ def features
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def adapter
16
+ @adapter ||= Flipper::Adapters::Memory.new(features)
17
+ end
18
+
19
+ def eql?(other)
20
+ self.class.eql?(other.class) && @contents == other.contents && @format == other.format && @version == other.version
21
+ end
22
+ alias_method :==, :eql?
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ require "flipper/exporters/json/v1"
2
+
3
+ module Flipper
4
+ module Exporter
5
+ extend self
6
+
7
+ FORMATTERS = {
8
+ json: {
9
+ 1 => Flipper::Exporters::Json::V1,
10
+ }
11
+ }.freeze
12
+
13
+ def build(format: :json, version: 1)
14
+ FORMATTERS.fetch(format).fetch(version).new
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ require "flipper/export"
2
+ require "flipper/typecast"
3
+
4
+ module Flipper
5
+ module Exporters
6
+ module Json
7
+ # Raised when the contents of the export are not valid.
8
+ class InvalidError < StandardError; end
9
+ class JsonError < InvalidError; end
10
+
11
+ # Internal: JSON export class that knows how to build features hash
12
+ # from data.
13
+ class Export < ::Flipper::Export
14
+ def initialize(contents:, version: 1)
15
+ super contents: contents, version: version, format: :json
16
+ end
17
+
18
+ # Public: The features hash identical to calling get_all on adapter.
19
+ def features
20
+ @features ||= begin
21
+ features = Typecast.from_json(contents).fetch("features")
22
+ Typecast.features_hash(features)
23
+ rescue JSON::ParserError
24
+ raise JsonError
25
+ rescue
26
+ raise InvalidError
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ require "json"
2
+ require "flipper/exporters/json/export"
3
+
4
+ module Flipper
5
+ module Exporters
6
+ module Json
7
+ class V1
8
+ VERSION = 1
9
+
10
+ def call(adapter)
11
+ features = adapter.get_all
12
+
13
+ # Convert sets to arrays for json
14
+ features.each do |feature_key, gates|
15
+ gates.each do |key, value|
16
+ case value
17
+ when Set
18
+ features[feature_key][key] = value.to_a
19
+ end
20
+ end
21
+ end
22
+
23
+ json = Typecast.to_json({
24
+ version: VERSION,
25
+ features: features,
26
+ })
27
+
28
+ Json::Export.new(contents: json, version: VERSION)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,73 @@
1
+ module Flipper
2
+ class Expression
3
+ module Builder
4
+ def build(object)
5
+ Expression.build(object)
6
+ end
7
+
8
+ def add(*expressions)
9
+ group? ? build(name => args + expressions.flatten) : any.add(*expressions)
10
+ end
11
+
12
+ def remove(*expressions)
13
+ group? ? build(name => args - expressions.flatten) : any.remove(*expressions)
14
+ end
15
+
16
+ def any
17
+ any? ? self : build({ Any: [self] })
18
+ end
19
+
20
+ def all
21
+ all? ? self : build({ All: [self] })
22
+ end
23
+
24
+ def equal(object)
25
+ build({ Equal: [self, object] })
26
+ end
27
+ alias eq equal
28
+
29
+ def not_equal(object)
30
+ build({ NotEqual: [self, object] })
31
+ end
32
+ alias neq not_equal
33
+
34
+ def greater_than(object)
35
+ build({ GreaterThan: [self, object] })
36
+ end
37
+ alias gt greater_than
38
+
39
+ def greater_than_or_equal_to(object)
40
+ build({ GreaterThanOrEqualTo: [self, object] })
41
+ end
42
+ alias gte greater_than_or_equal_to
43
+ alias greater_than_or_equal greater_than_or_equal_to
44
+
45
+ def less_than(object)
46
+ build({ LessThan: [self, object] })
47
+ end
48
+ alias lt less_than
49
+
50
+ def less_than_or_equal_to(object)
51
+ build({ LessThanOrEqualTo: [self, object] })
52
+ end
53
+ alias lte less_than_or_equal_to
54
+ alias less_than_or_equal less_than_or_equal_to
55
+
56
+ def percentage_of_actors(object)
57
+ build({ PercentageOfActors: [self, build(object)] })
58
+ end
59
+
60
+ def any?
61
+ is_a?(Expression) && function == Expressions::Any
62
+ end
63
+
64
+ def all?
65
+ is_a?(Expression) && function == Expressions::All
66
+ end
67
+
68
+ def group?
69
+ any? || all?
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,25 @@
1
+ module Flipper
2
+ class Expression
3
+ # Public: A constant value like a "string", Number (1, 3.5), Boolean (true, false).
4
+ #
5
+ # Implements the same interface as Expression
6
+ class Constant
7
+ include Expression::Builder
8
+
9
+ attr_reader :value
10
+
11
+ def initialize(value)
12
+ @value = value
13
+ end
14
+
15
+ def evaluate(_ = nil)
16
+ value
17
+ end
18
+
19
+ def eql?(other)
20
+ other.is_a?(self.class) && other.value == value
21
+ end
22
+ alias_method :==, :eql?
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,71 @@
1
+ require "flipper/expression/builder"
2
+ require "flipper/expression/constant"
3
+
4
+ module Flipper
5
+ class Expression
6
+ include Builder
7
+
8
+ def self.build(object)
9
+ return object if object.is_a?(self) || object.is_a?(Constant)
10
+
11
+ case object
12
+ when Hash
13
+ name = object.keys.first
14
+ args = object.values.first
15
+ unless name
16
+ raise ArgumentError, "#{object.inspect} cannot be converted into an expression"
17
+ end
18
+
19
+ new(name, Array(args).map { |o| build(o) })
20
+ when String, Numeric, FalseClass, TrueClass
21
+ Expression::Constant.new(object)
22
+ when Symbol
23
+ Expression::Constant.new(object.to_s)
24
+ else
25
+ raise ArgumentError, "#{object.inspect} cannot be converted into an expression"
26
+ end
27
+ end
28
+
29
+ # Use #build
30
+ private_class_method :new
31
+
32
+ attr_reader :name, :function, :args
33
+
34
+ def initialize(name, args = [])
35
+ @name = name.to_s
36
+ @function = Expressions.const_get(name)
37
+ @args = args
38
+ end
39
+
40
+ def evaluate(context = {})
41
+ if call_with_context?
42
+ function.call(*args.map {|arg| arg.evaluate(context) }, context: context)
43
+ else
44
+ function.call(*args.map {|arg| arg.evaluate(context) })
45
+ end
46
+ end
47
+
48
+ def eql?(other)
49
+ other.is_a?(self.class) && @function == other.function && @args == other.args
50
+ end
51
+ alias_method :==, :eql?
52
+
53
+ def value
54
+ {
55
+ name => args.map(&:value)
56
+ }
57
+ end
58
+
59
+ private
60
+
61
+ def call_with_context?
62
+ function.method(:call).parameters.any? do |type, name|
63
+ name == :context && [:key, :keyreq].include?(type)
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ Dir[File.join(File.dirname(__FILE__), 'expressions', '*.rb')].sort.each do |file|
70
+ require "flipper/expressions/#{File.basename(file, '.rb')}"
71
+ end
@@ -0,0 +1,9 @@
1
+ module Flipper
2
+ module Expressions
3
+ class All
4
+ def self.call(*args)
5
+ args.all?
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Flipper
2
+ module Expressions
3
+ class Any
4
+ def self.call(*args)
5
+ args.any?
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Flipper
2
+ module Expressions
3
+ class Boolean
4
+ def self.call(value)
5
+ Flipper::Typecast.to_boolean(value)
6
+ end
7
+ end
8
+ end
9
+ end