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
@@ -5,39 +5,28 @@ module Flipper
5
5
  # Internal: Adapter that wraps another adapter with the ability to memoize
6
6
  # adapter calls in memory. Used by flipper dsl and the memoizer middleware
7
7
  # to make it possible to memoize adapter calls for the duration of a request.
8
- class Memoizable < SimpleDelegator
8
+ class Memoizable
9
9
  include ::Flipper::Adapter
10
10
 
11
- FeaturesKey = :flipper_features
12
- GetAllKey = :all_memoized
13
-
14
11
  # Internal
15
12
  attr_reader :cache
16
13
 
17
- # Public: The name of the adapter.
18
- attr_reader :name
19
-
20
14
  # Internal: The adapter this adapter is wrapping.
21
15
  attr_reader :adapter
22
16
 
23
- # Private
24
- def self.key_for(key)
25
- "feature/#{key}"
26
- end
27
-
28
17
  # Public
29
18
  def initialize(adapter, cache = nil)
30
- super(adapter)
31
19
  @adapter = adapter
32
- @name = :memoizable
33
20
  @cache = cache || {}
34
21
  @memoize = false
22
+ @features_key = :flipper_features
23
+ @get_all_key = :all_memoized
35
24
  end
36
25
 
37
26
  # Public
38
27
  def features
39
28
  if memoizing?
40
- cache.fetch(FeaturesKey) { cache[FeaturesKey] = @adapter.features }
29
+ cache.fetch(@features_key) { cache[@features_key] = @adapter.features }
41
30
  else
42
31
  @adapter.features
43
32
  end
@@ -45,24 +34,20 @@ module Flipper
45
34
 
46
35
  # Public
47
36
  def add(feature)
48
- result = @adapter.add(feature)
49
- expire_features_set
50
- result
37
+ @adapter.add(feature).tap { expire_features_set }
51
38
  end
52
39
 
53
40
  # Public
54
41
  def remove(feature)
55
- result = @adapter.remove(feature)
56
- expire_features_set
57
- expire_feature(feature)
58
- result
42
+ @adapter.remove(feature).tap do
43
+ expire_features_set
44
+ expire_feature(feature)
45
+ end
59
46
  end
60
47
 
61
48
  # Public
62
49
  def clear(feature)
63
- result = @adapter.clear(feature)
64
- expire_feature(feature)
65
- result
50
+ @adapter.clear(feature).tap { expire_feature(feature) }
66
51
  end
67
52
 
68
53
  # Public
@@ -96,21 +81,21 @@ module Flipper
96
81
  end
97
82
  end
98
83
 
99
- def get_all
84
+ def get_all(**kwargs)
100
85
  if memoizing?
101
86
  response = nil
102
- if cache[GetAllKey]
87
+ if cache[@get_all_key]
103
88
  response = {}
104
- cache[FeaturesKey].each do |key|
89
+ cache[@features_key].each do |key|
105
90
  response[key] = cache[key_for(key)]
106
91
  end
107
92
  else
108
- response = @adapter.get_all
93
+ response = @adapter.get_all(**kwargs)
109
94
  response.each do |key, value|
110
95
  cache[key_for(key)] = value
111
96
  end
112
- cache[FeaturesKey] = response.keys.to_set
113
- cache[GetAllKey] = true
97
+ cache[@features_key] = response.keys.to_set
98
+ cache[@get_all_key] = true
114
99
  end
115
100
 
116
101
  # Ensures that looking up other features that do not exist doesn't
@@ -118,22 +103,31 @@ module Flipper
118
103
  response.default_proc = ->(memo, key) { memo[key] = default_config }
119
104
  response
120
105
  else
121
- @adapter.get_all
106
+ @adapter.get_all(**kwargs)
122
107
  end
123
108
  end
124
109
 
125
110
  # Public
126
111
  def enable(feature, gate, thing)
127
- result = @adapter.enable(feature, gate, thing)
128
- expire_feature(feature)
129
- result
112
+ @adapter.enable(feature, gate, thing).tap { expire_feature(feature) }
130
113
  end
131
114
 
132
115
  # Public
133
116
  def disable(feature, gate, thing)
134
- result = @adapter.disable(feature, gate, thing)
135
- expire_feature(feature)
136
- result
117
+ @adapter.disable(feature, gate, thing).tap { expire_feature(feature) }
118
+ end
119
+
120
+ # Public
121
+ def read_only?
122
+ @adapter.read_only?
123
+ end
124
+
125
+ def import(source)
126
+ @adapter.import(source).tap { cache.clear if memoizing? }
127
+ end
128
+
129
+ def export(format: :json, version: 1)
130
+ @adapter.export(format: format, version: version)
137
131
  end
138
132
 
139
133
  # Internal: Turns local caching on/off.
@@ -149,10 +143,20 @@ module Flipper
149
143
  !!@memoize
150
144
  end
151
145
 
146
+ if RUBY_VERSION >= '3.0'
147
+ def method_missing(name, *args, **kwargs, &block)
148
+ @adapter.send name, *args, **kwargs, &block
149
+ end
150
+ else
151
+ def method_missing(name, *args, &block)
152
+ @adapter.send name, *args, &block
153
+ end
154
+ end
155
+
152
156
  private
153
157
 
154
158
  def key_for(key)
155
- self.class.key_for(key)
159
+ "feature/#{key}"
156
160
  end
157
161
 
158
162
  def expire_feature(feature)
@@ -160,7 +164,7 @@ module Flipper
160
164
  end
161
165
 
162
166
  def expire_features_set
163
- cache.delete(FeaturesKey) if memoizing?
167
+ cache.delete(@features_key) if memoizing?
164
168
  end
165
169
  end
166
170
  end
@@ -1,95 +1,106 @@
1
- require 'set'
1
+ require "flipper/adapter"
2
+ require "flipper/typecast"
2
3
 
3
4
  module Flipper
4
5
  module Adapters
5
- # Public: Adapter for storing everything in memory (ie: Hash).
6
+ # Public: Adapter for storing everything in memory.
6
7
  # Useful for tests/specs.
7
8
  class Memory
8
9
  include ::Flipper::Adapter
9
10
 
10
- FeaturesKey = :features
11
-
12
- # Public: The name of the adapter.
13
- attr_reader :name
14
-
15
11
  # Public
16
- def initialize(source = nil)
17
- @source = source || {}
18
- @name = :memory
12
+ def initialize(source = nil, threadsafe: true)
13
+ @source = Typecast.features_hash(source)
14
+ @lock = Mutex.new if threadsafe
15
+ reset
19
16
  end
20
17
 
21
18
  # Public: The set of known features.
22
19
  def features
23
- read_feature_keys
20
+ synchronize { @source.keys }.to_set
24
21
  end
25
22
 
26
23
  # Public: Adds a feature to the set of known features.
27
24
  def add(feature)
28
- features.add(feature.key)
25
+ synchronize { @source[feature.key] ||= default_config }
29
26
  true
30
27
  end
31
28
 
32
29
  # Public: Removes a feature from the set of known features and clears
33
30
  # all the values for the feature.
34
31
  def remove(feature)
35
- features.delete(feature.name.to_s)
36
- clear(feature)
32
+ synchronize { @source.delete(feature.key) }
37
33
  true
38
34
  end
39
35
 
40
36
  # Public: Clears all the gate values for a feature.
41
37
  def clear(feature)
42
- feature.gates.each do |gate|
43
- delete key(feature, gate)
44
- end
38
+ synchronize { @source[feature.key] = default_config }
45
39
  true
46
40
  end
47
41
 
48
42
  # Public
49
43
  def get(feature)
50
- read_feature(feature)
44
+ synchronize { @source[feature.key] } || default_config
51
45
  end
52
46
 
53
47
  def get_multi(features)
54
- read_many_features(features)
55
- end
56
-
57
- def get_all
58
- features = read_feature_keys.map do |key|
59
- Flipper::Feature.new(key, self)
48
+ synchronize do
49
+ result = {}
50
+ features.each do |feature|
51
+ result[feature.key] = @source[feature.key] || default_config
52
+ end
53
+ result
60
54
  end
55
+ end
61
56
 
62
- read_many_features(features)
57
+ def get_all(**kwargs)
58
+ synchronize { Typecast.features_hash(@source) }
63
59
  end
64
60
 
65
61
  # Public
66
62
  def enable(feature, gate, thing)
67
- case gate.data_type
68
- when :boolean, :integer
69
- write key(feature, gate), thing.value.to_s
70
- when :set
71
- set_add key(feature, gate), thing.value.to_s
72
- else
73
- raise "#{gate} is not supported by this adapter yet"
63
+ synchronize do
64
+ @source[feature.key] ||= default_config
65
+
66
+ case gate.data_type
67
+ when :boolean
68
+ @source[feature.key] = default_config
69
+ @source[feature.key][gate.key] = thing.value.to_s
70
+ when :integer
71
+ @source[feature.key][gate.key] = thing.value.to_s
72
+ when :set
73
+ @source[feature.key][gate.key] << thing.value.to_s
74
+ when :json
75
+ @source[feature.key][gate.key] = thing.value
76
+ else
77
+ raise "#{gate} is not supported by this adapter yet"
78
+ end
79
+
80
+ true
74
81
  end
75
-
76
- true
77
82
  end
78
83
 
79
84
  # Public
80
85
  def disable(feature, gate, thing)
81
- case gate.data_type
82
- when :boolean
83
- clear(feature)
84
- when :integer
85
- write key(feature, gate), thing.value.to_s
86
- when :set
87
- set_delete key(feature, gate), thing.value.to_s
88
- else
89
- raise "#{gate} is not supported by this adapter yet"
86
+ synchronize do
87
+ @source[feature.key] ||= default_config
88
+
89
+ case gate.data_type
90
+ when :boolean
91
+ @source[feature.key] = default_config
92
+ when :integer
93
+ @source[feature.key][gate.key] = thing.value.to_s
94
+ when :set
95
+ @source[feature.key][gate.key].delete thing.value.to_s
96
+ when :json
97
+ @source[feature.key].delete(gate.key)
98
+ else
99
+ raise "#{gate} is not supported by this adapter yet"
100
+ end
101
+
102
+ true
90
103
  end
91
-
92
- true
93
104
  end
94
105
 
95
106
  # Public
@@ -101,79 +112,32 @@ module Flipper
101
112
  "#<#{self.class.name}:#{object_id} #{attributes.join(', ')}>"
102
113
  end
103
114
 
104
- private
105
-
106
- def read_feature_keys
107
- set_members(FeaturesKey)
108
- end
109
-
110
- # Private
111
- def key(feature, gate)
112
- "feature/#{feature.key}/#{gate.key}"
113
- end
114
-
115
- def read_many_features(features)
116
- result = {}
117
- features.each do |feature|
118
- result[feature.key] = read_feature(feature)
119
- end
120
- result
121
- end
122
-
123
- def read_feature(feature)
124
- result = {}
125
-
126
- feature.gates.each do |gate|
127
- result[gate.key] =
128
- case gate.data_type
129
- when :boolean, :integer
130
- read key(feature, gate)
131
- when :set
132
- set_members key(feature, gate)
133
- else
134
- raise "#{gate} is not supported by this adapter yet"
135
- end
136
- end
137
-
138
- result
139
- end
140
-
141
- # Private
142
- def read(key)
143
- @source[key.to_s]
144
- end
145
-
146
- # Private
147
- def write(key, value)
148
- @source[key.to_s] = value.to_s
149
- end
150
-
151
- # Private
152
- def delete(key)
153
- @source.delete(key.to_s)
115
+ # Public: a more efficient implementation of import for this adapter
116
+ def import(source)
117
+ adapter = self.class.from(source)
118
+ get_all = Typecast.features_hash(adapter.get_all)
119
+ synchronize { @source.replace(get_all) }
120
+ true
154
121
  end
155
122
 
156
- # Private
157
- def set_add(key, value)
158
- ensure_set_initialized(key)
159
- @source[key.to_s].add(value.to_s)
160
- end
123
+ private
161
124
 
162
- # Private
163
- def set_delete(key, value)
164
- ensure_set_initialized(key)
165
- @source[key.to_s].delete(value.to_s)
125
+ def reset
126
+ @pid = Process.pid
127
+ @lock&.unlock if @lock&.locked?
166
128
  end
167
129
 
168
- # Private
169
- def set_members(key)
170
- ensure_set_initialized(key)
171
- @source[key.to_s]
130
+ def forked?
131
+ @pid != Process.pid
172
132
  end
173
133
 
174
- # Private
175
- def ensure_set_initialized(key)
176
- @source[key.to_s] ||= Set.new
134
+ def synchronize(&block)
135
+ if @lock
136
+ reset if forked?
137
+ @lock.synchronize(&block)
138
+ else
139
+ block.call
140
+ end
177
141
  end
178
142
  end
179
143
  end
@@ -5,102 +5,34 @@ module Flipper
5
5
  # Public: Adapter that wraps another adapter and stores the operations.
6
6
  #
7
7
  # Useful in tests to verify calls and such. Never use outside of testing.
8
- class OperationLogger < SimpleDelegator
9
- include ::Flipper::Adapter
8
+ class OperationLogger < Wrapper
10
9
 
11
10
  class Operation
12
- attr_reader :type, :args
11
+ attr_reader :type, :args, :kwargs
13
12
 
14
- def initialize(type, args)
13
+ def initialize(type, args, kwargs = {})
15
14
  @type = type
16
15
  @args = args
16
+ @kwargs = kwargs
17
17
  end
18
18
  end
19
19
 
20
- OperationTypes = [
21
- :features,
22
- :add,
23
- :remove,
24
- :clear,
25
- :get,
26
- :get_multi,
27
- :get_all,
28
- :enable,
29
- :disable,
30
- ].freeze
31
-
32
20
  # Internal: An array of the operations that have happened.
33
21
  attr_reader :operations
34
22
 
35
- # Internal: The name of the adapter.
36
- attr_reader :name
37
-
38
23
  # Public
39
24
  def initialize(adapter, operations = nil)
40
25
  super(adapter)
41
- @adapter = adapter
42
- @name = :operation_logger
43
26
  @operations = operations || []
44
27
  end
45
28
 
46
- # Public: The set of known features.
47
- def features
48
- @operations << Operation.new(:features, [])
49
- @adapter.features
50
- end
51
-
52
- # Public: Adds a feature to the set of known features.
53
- def add(feature)
54
- @operations << Operation.new(:add, [feature])
55
- @adapter.add(feature)
56
- end
57
-
58
- # Public: Removes a feature from the set of known features and clears
59
- # all the values for the feature.
60
- def remove(feature)
61
- @operations << Operation.new(:remove, [feature])
62
- @adapter.remove(feature)
63
- end
64
-
65
- # Public: Clears all the gate values for a feature.
66
- def clear(feature)
67
- @operations << Operation.new(:clear, [feature])
68
- @adapter.clear(feature)
69
- end
70
-
71
- # Public
72
- def get(feature)
73
- @operations << Operation.new(:get, [feature])
74
- @adapter.get(feature)
75
- end
76
-
77
- # Public
78
- def get_multi(features)
79
- @operations << Operation.new(:get_multi, [features])
80
- @adapter.get_multi(features)
81
- end
82
-
83
- # Public
84
- def get_all
85
- @operations << Operation.new(:get_all, [])
86
- @adapter.get_all
87
- end
88
-
89
- # Public
90
- def enable(feature, gate, thing)
91
- @operations << Operation.new(:enable, [feature, gate, thing])
92
- @adapter.enable(feature, gate, thing)
93
- end
94
-
95
- # Public
96
- def disable(feature, gate, thing)
97
- @operations << Operation.new(:disable, [feature, gate, thing])
98
- @adapter.disable(feature, gate, thing)
99
- end
100
-
101
29
  # Public: Count the number of times a certain operation happened.
102
- def count(type)
103
- type(type).size
30
+ def count(type = nil)
31
+ if type
32
+ type(type).size
33
+ else
34
+ @operations.size
35
+ end
104
36
  end
105
37
 
106
38
  # Public: Get all operations of a certain type.
@@ -117,6 +49,18 @@ module Flipper
117
49
  def reset
118
50
  @operations.clear
119
51
  end
52
+
53
+ def inspect
54
+ inspect_id = ::Kernel::format "%x", (object_id * 2)
55
+ %(#<#{self.class}:0x#{inspect_id} @name=#{name.inspect}, @operations=#{@operations.inspect}, @adapter=#{@adapter.inspect}>)
56
+ end
57
+
58
+ private
59
+
60
+ def wrap(method, *args, **kwargs, &block)
61
+ @operations << Operation.new(method, args, kwargs)
62
+ block.call
63
+ end
120
64
  end
121
65
  end
122
66
  end
@@ -0,0 +1,2 @@
1
+ warn "DEPRECATION WARNING: Flipper::Adapters::Poll::Poller is deprecated. Use Flipper::Poller instead."
2
+ require 'flipper/adapters/poll'
@@ -0,0 +1,52 @@
1
+ require 'flipper/adapters/sync/synchronizer'
2
+ require 'flipper/poller'
3
+
4
+ module Flipper
5
+ module Adapters
6
+ class Poll
7
+ extend Forwardable
8
+ include ::Flipper::Adapter
9
+
10
+ # Deprecated
11
+ Poller = ::Flipper::Poller
12
+
13
+ attr_reader :adapter, :poller
14
+
15
+ def_delegators :synced_adapter, :features, :get, :get_multi, :get_all, :add, :remove, :clear, :enable, :disable
16
+
17
+ def initialize(poller, adapter)
18
+ @adapter = adapter
19
+ @poller = poller
20
+ @last_synced_at = 0
21
+
22
+ # If the adapter is empty, we need to sync before starting the poller.
23
+ # Yes, this will block the main thread, but that's better than thinking
24
+ # nothing is enabled.
25
+ if adapter.features.empty?
26
+ begin
27
+ @poller.sync
28
+ rescue
29
+ # TODO: Warn here that it's possible that no data has been synced
30
+ # and flags are being evaluated without flag data being present
31
+ # until a sync completes. We rescue to avoid flipper being down
32
+ # causing your processes to crash.
33
+ end
34
+ end
35
+
36
+ @poller.start
37
+ end
38
+
39
+ private
40
+
41
+ def synced_adapter
42
+ @poller.start
43
+ poller_last_synced_at = @poller.last_synced_at.value
44
+ if poller_last_synced_at > @last_synced_at
45
+ Flipper::Adapters::Sync::Synchronizer.new(@adapter, @poller.adapter).call
46
+ @last_synced_at = poller_last_synced_at
47
+ end
48
+ @adapter
49
+ end
50
+ end
51
+ end
52
+ end