flipper 0.26.2 → 0.28.3

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +61 -0
  3. data/Gemfile +2 -3
  4. data/benchmark/enabled_multiple_actors_ips.rb +20 -0
  5. data/examples/api/basic.ru +3 -4
  6. data/examples/api/custom_memoized.ru +3 -4
  7. data/examples/api/memoized.ru +3 -4
  8. data/examples/dsl.rb +3 -3
  9. data/examples/enabled_for_actor.rb +4 -2
  10. data/examples/mirroring.rb +59 -0
  11. data/lib/flipper/adapter.rb +23 -7
  12. data/lib/flipper/adapters/http.rb +11 -3
  13. data/lib/flipper/adapters/instrumented.rb +25 -2
  14. data/lib/flipper/adapters/memoizable.rb +19 -2
  15. data/lib/flipper/adapters/memory.rb +40 -16
  16. data/lib/flipper/adapters/operation_logger.rb +16 -3
  17. data/lib/flipper/dsl.rb +5 -9
  18. data/lib/flipper/errors.rb +3 -3
  19. data/lib/flipper/export.rb +26 -0
  20. data/lib/flipper/exporter.rb +17 -0
  21. data/lib/flipper/exporters/json/export.rb +32 -0
  22. data/lib/flipper/exporters/json/v1.rb +33 -0
  23. data/lib/flipper/feature.rb +12 -10
  24. data/lib/flipper/feature_check_context.rb +8 -4
  25. data/lib/flipper/gate.rb +12 -11
  26. data/lib/flipper/gates/actor.rb +11 -8
  27. data/lib/flipper/gates/group.rb +4 -2
  28. data/lib/flipper/gates/percentage_of_actors.rb +4 -5
  29. data/lib/flipper/identifier.rb +2 -2
  30. data/lib/flipper/instrumentation/log_subscriber.rb +24 -5
  31. data/lib/flipper/instrumentation/subscriber.rb +8 -1
  32. data/lib/flipper/poller.rb +1 -1
  33. data/lib/flipper/spec/shared_adapter_specs.rb +23 -0
  34. data/lib/flipper/test/shared_adapter_test.rb +24 -0
  35. data/lib/flipper/typecast.rb +17 -0
  36. data/lib/flipper/types/actor.rb +13 -13
  37. data/lib/flipper/types/group.rb +4 -4
  38. data/lib/flipper/version.rb +1 -1
  39. data/lib/flipper.rb +5 -4
  40. data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
  41. data/spec/flipper/adapter_spec.rb +29 -2
  42. data/spec/flipper/adapters/http_spec.rb +25 -3
  43. data/spec/flipper/adapters/instrumented_spec.rb +28 -10
  44. data/spec/flipper/adapters/memoizable_spec.rb +30 -10
  45. data/spec/flipper/adapters/memory_spec.rb +11 -2
  46. data/spec/flipper/adapters/operation_logger_spec.rb +29 -10
  47. data/spec/flipper/dsl_spec.rb +25 -8
  48. data/spec/flipper/export_spec.rb +13 -0
  49. data/spec/flipper/exporter_spec.rb +16 -0
  50. data/spec/flipper/exporters/json/export_spec.rb +60 -0
  51. data/spec/flipper/exporters/json/v1_spec.rb +33 -0
  52. data/spec/flipper/feature_check_context_spec.rb +5 -5
  53. data/spec/flipper/feature_spec.rb +76 -32
  54. data/spec/flipper/gates/boolean_spec.rb +1 -1
  55. data/spec/flipper/gates/group_spec.rb +2 -3
  56. data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -5
  57. data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
  58. data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -5
  59. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +10 -0
  60. data/spec/flipper/typecast_spec.rb +79 -0
  61. data/spec/flipper/types/actor_spec.rb +45 -45
  62. data/spec/flipper/types/group_spec.rb +2 -2
  63. data/spec/flipper_integration_spec.rb +62 -50
  64. data/spec/flipper_spec.rb +7 -1
  65. data/spec/support/skippable.rb +18 -0
  66. metadata +20 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 670e45600b4c72208ed69da2792d2d4e1ac11d7a54f19c45c4b448ba6dacc404
4
- data.tar.gz: 56a6ef12c569a7392212953604ed103a1fb187b57d5f827ea141a0d5eafc5ee1
3
+ metadata.gz: 32360e70231522c5c5b41e5e9e889fd0d4df13c59ba223dd5416433d904954e5
4
+ data.tar.gz: 5ec60d72b7b00d4d4bb5458d5746a4df4b89f3656fa9d4c7bfeddb27caae9c81
5
5
  SHA512:
6
- metadata.gz: a09649689708c91afbdc3e9f4eded2ec29ae1b3ac06a98f28197dcc4d3dd65983f73e0e9e44994d78b0672afbea1350ff7256dc12b7e5bb426961bfce858b4f6
7
- data.tar.gz: '08eb67c5e5df45e734e8b6e50c6fd20cfbdf28dbdb8ba1a3e2d2af084250b9cd404fa21cc778989527d245154e05f5abc49aaa12a95b2ef7b12daf4bbea41830'
6
+ metadata.gz: 12e61dc74aa548fe5b266c1d909e650190416d8350adfd12c608a84b16b5ede9cce4b753d74c5fed472506bfb0dd9a3e0e768bcb193f6f751408f9550513774e
7
+ data.tar.gz: a2c0dc70d0fd50848fe9aedc2ed9f8f396dd51f5b604badc1477efd0249104e9fa7276d5161ac95e25992d8b1765eadd491e9a1a1d79640c6644e9fdd69442f2
data/Changelog.md CHANGED
@@ -2,6 +2,67 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 0.28.3
6
+
7
+ * Updated cloud config to ensure that poll adapter ONLY syncs from cloud to local adapter (and never back to cloud). Shouldn't affect anyone other than making things more safe if an incorrect response is received from the cloud poll endpoint. (https://github.com/jnunemaker/flipper/pull/740)
8
+
9
+ ## 0.28.2
10
+
11
+ * UI: fix path to bundled assets when mounted in another Rack app (https://github.com/jnunemaker/flipper/pull/742)
12
+
13
+ ## 0.28.1
14
+
15
+ ### Additions/Changes
16
+
17
+ * Use new method of making logs bold for rails (https://github.com/jnunemaker/flipper/pull/726)
18
+ * Bundle bootstrap, jquery and poppler with the library. (https://github.com/jnunemaker/flipper/pull/731)
19
+
20
+ ## 0.28.0
21
+
22
+ ### Additions/Changes
23
+
24
+ * Allow multiple actors for Flipper.enabled?. Improves performance of feature flags for multiple actors and simplifies code for users of flipper. This likely breaks things for anyone using Flipper internal classes related to actors, but that isn't likely you so you should be fine.
25
+ ```diff
26
+ - [user, user.team, user.org].any? { |actor| Flipper.enabled?(:my_feature, actor) }
27
+ + Flipper.enabled?(:my_feature, user, user.team, user.org)
28
+ ```
29
+ * If you currently use `actor.thing` in a group, you'll need to change it to `actor.actor`.
30
+ ```diff
31
+ - Flipper.register(:our_group) do |actor|
32
+ - actor.thing.is_a?(OurClassName)
33
+ - end
34
+ + Flipper.register(:our_group) do |actor|
35
+ + actor.actor.is_a?(OurClassName)
36
+ + end
37
+ ```
38
+ * If you currently use `context.thing` in a group or elsewhere, you'll need to change it to `context.actors`.
39
+ ```diff
40
+ - Flipper.register(:our_group) do |actor, context|
41
+ - context.thing.is_a?(OurClassName)
42
+ - end
43
+ + Flipper.register(:our_group) do |actor, context|
44
+ + context.actors.any? { |actor| actor.is_a?(OurClassName) }
45
+ + end
46
+ ```
47
+
48
+ ### Deprecations
49
+
50
+ * `:thing` in `enabled?` instrumentation payload. Use `:actors` instead.
51
+ ```diff
52
+ ActiveSupport::Notifications.subscribe('enabled?.feature_operation.flipper') do |name, start, finish, id, payload|
53
+ - payload[:thing]
54
+ + payload[:actors]
55
+ end
56
+ ```
57
+
58
+ ## 0.27.1
59
+
60
+ * Quick fix for missing require of "flipper/version" that was causing issues with some flipper-ui people.
61
+
62
+ ## 0.27.0
63
+
64
+ * Easy Import/Export (https://github.com/jnunemaker/flipper/pull/709). This has some breaking changes but only if you are using flipper internals. If you are just using Flipper.* methods, you'll be fine.
65
+
5
66
  ## 0.26.2
6
67
 
7
68
  * Improve Active Record Adapter get/get_multi/get_all performance by 5-10x when dealing with thousands of gate values (https://github.com/jnunemaker/flipper/pull/707).
data/Gemfile CHANGED
@@ -8,13 +8,12 @@ end
8
8
 
9
9
  gem 'debug'
10
10
  gem 'rake', '~> 12.3.3'
11
- gem 'shotgun', '~> 0.9'
12
11
  gem 'statsd-ruby', '~> 1.2.1'
13
12
  gem 'rspec', '~> 3.0'
14
- gem 'rack-test', '~> 0.6.3'
13
+ gem 'rack-test'
15
14
  gem 'sqlite3', "~> #{ENV['SQLITE3_VERSION'] || '1.4.1'}"
16
15
  gem 'rails', "~> #{ENV['RAILS_VERSION'] || '7.0.0'}"
17
- gem 'minitest', '~> 5.8'
16
+ gem 'minitest', '~> 5.18'
18
17
  gem 'minitest-documentation'
19
18
  gem 'webmock', '~> 3.0'
20
19
  gem 'ice_age'
@@ -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
@@ -1,19 +1,18 @@
1
1
  #
2
2
  # Usage:
3
- # # if you want it to not reload and be really fast
4
3
  # bin/rackup examples/api/basic.ru -p 9999
5
4
  #
6
- # # if you want reloading
7
- # bin/shotgun examples/api/basic.ru -p 9999
8
- #
9
5
  # http://localhost:9999/
10
6
  #
11
7
 
12
8
  require 'bundler/setup'
9
+ require 'rack/reloader'
13
10
  require "flipper/api"
14
11
  require "flipper/adapters/pstore"
15
12
 
16
13
  # You can uncomment this to get some default data:
17
14
  # Flipper.enable :logging
18
15
 
16
+ use Rack::Reloader
17
+
19
18
  run Flipper::Api.app
@@ -1,15 +1,12 @@
1
1
  #
2
2
  # Usage:
3
- # # if you want it to not reload and be really fast
4
3
  # bin/rackup examples/api/custom_memoized.ru -p 9999
5
4
  #
6
- # # if you want reloading
7
- # bin/shotgun examples/api/custom_memoized.ru -p 9999
8
- #
9
5
  # http://localhost:9999/
10
6
  #
11
7
 
12
8
  require 'bundler/setup'
9
+ require 'rack/reloader'
13
10
  require "active_support/notifications"
14
11
  require "flipper/api"
15
12
  require "flipper/adapters/pstore"
@@ -31,6 +28,8 @@ ActiveSupport::Notifications.subscribe(/.*/, ->(*args) {
31
28
  # You can uncomment this to get some default data:
32
29
  # flipper[:logging].enable_percentage_of_time 5
33
30
 
31
+ use Rack::Reloader
32
+
34
33
  run Flipper::Api.app(flipper) { |builder|
35
34
  builder.use Flipper::Middleware::SetupEnv, flipper
36
35
  builder.use Flipper::Middleware::Memoizer, preload: true
@@ -1,15 +1,12 @@
1
1
  #
2
2
  # Usage:
3
- # # if you want it to not reload and be really fast
4
3
  # bin/rackup examples/api/memoized.ru -p 9999
5
4
  #
6
- # # if you want reloading
7
- # bin/shotgun examples/api/memoized.ru -p 9999
8
- #
9
5
  # http://localhost:9999/
10
6
  #
11
7
 
12
8
  require 'bundler/setup'
9
+ require 'rack/reloader'
13
10
  require "active_support/notifications"
14
11
  require "flipper/api"
15
12
  require "flipper/adapters/pstore"
@@ -38,6 +35,8 @@ Flipper.register(:admins) { |actor|
38
35
  # You can uncomment this to get some default data:
39
36
  # Flipper.enable :logging
40
37
 
38
+ use Rack::Reloader
39
+
41
40
  run Flipper::Api.app { |builder|
42
41
  builder.use Flipper::Middleware::Memoizer, preload: true
43
42
  }
data/examples/dsl.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'bundler/setup'
2
2
  require 'flipper'
3
3
 
4
- # create a thing with an identifier
4
+ # create an actor with an identifier
5
5
  class Person < Struct.new(:id)
6
6
  include Flipper::Identifier
7
7
  end
@@ -58,8 +58,8 @@ responds_to_flipper_id = Struct.new(:flipper_id).new(10)
58
58
  puts Flipper.actor(responds_to_flipper_id).inspect
59
59
 
60
60
  # get an instance of an actor using an object
61
- thing = Struct.new(:flipper_id).new(22)
62
- puts Flipper.actor(thing).inspect
61
+ actor = Struct.new(:flipper_id).new(22)
62
+ puts Flipper.actor(actor).inspect
63
63
 
64
64
  # register a top level group
65
65
  admins = Flipper.register(:admins) { |actor|
@@ -31,5 +31,7 @@ Flipper.enable_percentage_of_actors :pro_stats, 50
31
31
  Flipper.enable_group :tweets, :admins
32
32
  Flipper.enable_actor :posts, user2
33
33
 
34
- pp Flipper.features.select { |feature| feature.enabled?(user1) }.map(&:name)
35
- pp Flipper.features.select { |feature| feature.enabled?(user2) }.map(&:name)
34
+ pp Flipper.features.select { |feature| feature.enabled?(user1) }.map(&:name).sort
35
+ pp Flipper.features.select { |feature| feature.enabled?(user2) }.map(&:name).sort
36
+ pp Flipper.features.select { |feature| feature.enabled?(user1, user2) }.map(&:name).sort
37
+ pp Flipper.features.select { |feature| feature.enabled?([user2, user1]) }.map(&:name).sort
@@ -0,0 +1,59 @@
1
+ require 'bundler/setup'
2
+ require_relative 'active_record/ar_setup'
3
+ require 'flipper'
4
+ require 'flipper/adapters/redis'
5
+ require 'flipper/adapters/active_record'
6
+
7
+ # Say you have production...
8
+ production_adapter = Flipper::Adapters::Memory.new
9
+ production = Flipper.new(production_adapter)
10
+
11
+ # And production has some stuff enabled...
12
+ production.enable(:search)
13
+ production.enable_percentage_of_time(:verbose_logging, 5)
14
+ production.enable_percentage_of_actors(:new_feature, 5)
15
+ production.enable_actor(:issues, Flipper::Actor.new('1'))
16
+ production.enable_actor(:issues, Flipper::Actor.new('2'))
17
+ production.enable_group(:request_tracing, :staff)
18
+
19
+ # And you would like to mirror production to staging...
20
+ staging_adapter = Flipper::Adapters::Memory.new
21
+ staging = Flipper.new(staging_adapter)
22
+ staging_export = staging.export
23
+
24
+ puts "Here is the state of the world for staging and production..."
25
+ puts "Staging"
26
+ staging.features.each do |feature|
27
+ pp feature: feature.key, values: feature.gate_values
28
+ end
29
+ puts "Production"
30
+ production.features.each do |feature|
31
+ pp feature: feature.key, values: feature.gate_values
32
+ end
33
+
34
+ # NOTE: This wipes active record clean and copies features/gates from redis into active record.
35
+ puts "Mirroring production to staging..."
36
+ staging.import(production.export)
37
+ puts "Staging is now identical to Production."
38
+
39
+ puts "Staging"
40
+ staging.features.each do |feature|
41
+ pp feature: feature.key, values: feature.gate_values
42
+ end
43
+ puts "Production"
44
+ production.features.each do |feature|
45
+ pp feature: feature.key, values: feature.gate_values
46
+ end
47
+
48
+ puts "Restoring staging to original state..."
49
+ staging.import(staging_export)
50
+ puts "Staging restored."
51
+
52
+ puts "Staging"
53
+ staging.features.each do |feature|
54
+ pp feature: feature.key, values: feature.gate_values
55
+ end
56
+ puts "Production"
57
+ production.features.each do |feature|
58
+ pp feature: feature.key, values: feature.gate_values
59
+ end
@@ -1,7 +1,3 @@
1
- require "set"
2
- require "flipper/feature"
3
- require "flipper/adapters/sync/synchronizer"
4
-
5
1
  module Flipper
6
2
  # Adding a module include so we have some hooks for stuff down the road
7
3
  module Adapter
@@ -20,6 +16,11 @@ module Flipper
20
16
  percentage_of_time: nil,
21
17
  }
22
18
  end
19
+
20
+ def from(source)
21
+ return source if source.is_a?(Flipper::Adapter)
22
+ source.adapter
23
+ end
23
24
  end
24
25
 
25
26
  # Public: Get all features and gate values in one call. Defaults to one call
@@ -43,9 +44,19 @@ module Flipper
43
44
 
44
45
  # Public: Ensure that adapter is in sync with source adapter provided.
45
46
  #
46
- # Returns result of Synchronizer#call.
47
- def import(source_adapter)
48
- Adapters::Sync::Synchronizer.new(self, source_adapter, raise: true).call
47
+ # source - The source dsl, adapter or export to import.
48
+ #
49
+ # Returns true if successful.
50
+ def import(source)
51
+ Adapters::Sync::Synchronizer.new(self, self.class.from(source), raise: true).call
52
+ true
53
+ end
54
+
55
+ # Public: Exports the adapter in a given format for a given format version.
56
+ #
57
+ # Returns a Flipper::Export instance.
58
+ def export(format: :json, version: 1)
59
+ Flipper::Exporter.build(format: format, version: version).call(self)
49
60
  end
50
61
 
51
62
  # Public: Default config for a feature's gate values.
@@ -54,3 +65,8 @@ module Flipper
54
65
  end
55
66
  end
56
67
  end
68
+
69
+ require "set"
70
+ require "flipper/exporter"
71
+ require "flipper/feature"
72
+ require "flipper/adapters/sync/synchronizer"
@@ -39,7 +39,7 @@ module Flipper
39
39
 
40
40
  def get_multi(features)
41
41
  csv_keys = features.map(&:key).join(',')
42
- response = @client.get("/features?keys=#{csv_keys}")
42
+ response = @client.get("/features?keys=#{csv_keys}&exclude_gate_names=true")
43
43
  raise Error, response unless response.is_a?(Net::HTTPOK)
44
44
 
45
45
  parsed_response = JSON.parse(response.body)
@@ -57,7 +57,7 @@ module Flipper
57
57
  end
58
58
 
59
59
  def get_all
60
- response = @client.get("/features")
60
+ response = @client.get("/features?exclude_gate_names=true")
61
61
  raise Error, response unless response.is_a?(Net::HTTPOK)
62
62
 
63
63
  parsed_response = JSON.parse(response.body)
@@ -76,7 +76,7 @@ module Flipper
76
76
  end
77
77
 
78
78
  def features
79
- response = @client.get('/features')
79
+ response = @client.get('/features?exclude_gate_names=true')
80
80
  raise Error, response unless response.is_a?(Net::HTTPOK)
81
81
 
82
82
  parsed_response = JSON.parse(response.body)
@@ -123,6 +123,14 @@ module Flipper
123
123
  true
124
124
  end
125
125
 
126
+ def import(source)
127
+ adapter = self.class.from(source)
128
+ export = adapter.export(format: :json, version: 1)
129
+ response = @client.post("/import", export.contents)
130
+ raise Error, response unless response.is_a?(Net::HTTPNoContent)
131
+ true
132
+ end
133
+
126
134
  private
127
135
 
128
136
  def request_body_for_gate(gate, value)
@@ -4,7 +4,7 @@ module Flipper
4
4
  module Adapters
5
5
  # Internal: Adapter that wraps another adapter and instruments all adapter
6
6
  # operations.
7
- class Instrumented < SimpleDelegator
7
+ class Instrumented
8
8
  include ::Flipper::Adapter
9
9
 
10
10
  # Private: The name of instrumentation events.
@@ -24,7 +24,6 @@ module Flipper
24
24
  # :instrumenter - What to use to instrument all the things.
25
25
  #
26
26
  def initialize(adapter, options = {})
27
- super(adapter)
28
27
  @adapter = adapter
29
28
  @name = :instrumented
30
29
  @instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
@@ -146,6 +145,30 @@ module Flipper
146
145
  payload[:result] = @adapter.disable(feature, gate, thing)
147
146
  end
148
147
  end
148
+
149
+ def import(source)
150
+ default_payload = {
151
+ operation: :import,
152
+ adapter_name: @adapter.name,
153
+ }
154
+
155
+ @instrumenter.instrument(InstrumentationName, default_payload) do |payload|
156
+ payload[:result] = @adapter.import(source)
157
+ end
158
+ end
159
+
160
+ def export(format: :json, version: 1)
161
+ default_payload = {
162
+ operation: :export,
163
+ adapter_name: @adapter.name,
164
+ format: format,
165
+ version: version,
166
+ }
167
+
168
+ @instrumenter.instrument(InstrumentationName, default_payload) do |payload|
169
+ payload[:result] = @adapter.export(format: format, version: version)
170
+ end
171
+ end
149
172
  end
150
173
  end
151
174
  end
@@ -5,7 +5,7 @@ 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
11
  FeaturesKey = :flipper_features
@@ -27,7 +27,6 @@ module Flipper
27
27
 
28
28
  # Public
29
29
  def initialize(adapter, cache = nil)
30
- super(adapter)
31
30
  @adapter = adapter
32
31
  @name = :memoizable
33
32
  @cache = cache || {}
@@ -128,6 +127,14 @@ module Flipper
128
127
  @adapter.disable(feature, gate, thing).tap { expire_feature(feature) }
129
128
  end
130
129
 
130
+ def import(source)
131
+ @adapter.import(source).tap { cache.clear if memoizing? }
132
+ end
133
+
134
+ def export(format: :json, version: 1)
135
+ @adapter.export(format: format, version: version)
136
+ end
137
+
131
138
  # Internal: Turns local caching on/off.
132
139
  #
133
140
  # value - The Boolean that decides if local caching is on.
@@ -141,6 +148,16 @@ module Flipper
141
148
  !!@memoize
142
149
  end
143
150
 
151
+ if RUBY_VERSION >= '3.0'
152
+ def method_missing(name, *args, **kwargs, &block)
153
+ @adapter.send name, *args, **kwargs, &block
154
+ end
155
+ else
156
+ def method_missing(name, *args, &block)
157
+ @adapter.send name, *args, &block
158
+ end
159
+ end
160
+
144
161
  private
145
162
 
146
163
  def key_for(key)
@@ -1,4 +1,5 @@
1
- require 'concurrent/atomic/read_write_lock'
1
+ require "flipper/adapter"
2
+ require "flipper/typecast"
2
3
 
3
4
  module Flipper
4
5
  module Adapters
@@ -13,43 +14,44 @@ module Flipper
13
14
  attr_reader :name
14
15
 
15
16
  # Public
16
- def initialize(source = nil)
17
- @source = Hash.new.update(source || {})
17
+ def initialize(source = nil, threadsafe: true)
18
+ @source = Typecast.features_hash(source)
18
19
  @name = :memory
19
- @lock = Concurrent::ReadWriteLock.new
20
+ @lock = Mutex.new if threadsafe
21
+ reset
20
22
  end
21
23
 
22
24
  # Public: The set of known features.
23
25
  def features
24
- @lock.with_read_lock { @source.keys }.to_set
26
+ synchronize { @source.keys }.to_set
25
27
  end
26
28
 
27
29
  # Public: Adds a feature to the set of known features.
28
30
  def add(feature)
29
- @lock.with_write_lock { @source[feature.key] ||= default_config }
31
+ synchronize { @source[feature.key] ||= default_config }
30
32
  true
31
33
  end
32
34
 
33
35
  # Public: Removes a feature from the set of known features and clears
34
36
  # all the values for the feature.
35
37
  def remove(feature)
36
- @lock.with_write_lock { @source.delete(feature.key) }
38
+ synchronize { @source.delete(feature.key) }
37
39
  true
38
40
  end
39
41
 
40
42
  # Public: Clears all the gate values for a feature.
41
43
  def clear(feature)
42
- @lock.with_write_lock { @source[feature.key] = default_config }
44
+ synchronize { @source[feature.key] = default_config }
43
45
  true
44
46
  end
45
47
 
46
48
  # Public
47
49
  def get(feature)
48
- @lock.with_read_lock { @source[feature.key] } || default_config
50
+ synchronize { @source[feature.key] } || default_config
49
51
  end
50
52
 
51
53
  def get_multi(features)
52
- @lock.with_read_lock do
54
+ synchronize do
53
55
  result = {}
54
56
  features.each do |feature|
55
57
  result[feature.key] = @source[feature.key] || default_config
@@ -59,12 +61,12 @@ module Flipper
59
61
  end
60
62
 
61
63
  def get_all
62
- @lock.with_read_lock { @source.to_h }
64
+ synchronize { Typecast.features_hash(@source) }
63
65
  end
64
66
 
65
67
  # Public
66
68
  def enable(feature, gate, thing)
67
- @lock.with_write_lock do
69
+ synchronize do
68
70
  @source[feature.key] ||= default_config
69
71
 
70
72
  case gate.data_type
@@ -85,7 +87,7 @@ module Flipper
85
87
 
86
88
  # Public
87
89
  def disable(feature, gate, thing)
88
- @lock.with_write_lock do
90
+ synchronize do
89
91
  @source[feature.key] ||= default_config
90
92
 
91
93
  case gate.data_type
@@ -113,9 +115,31 @@ module Flipper
113
115
  end
114
116
 
115
117
  # Public: a more efficient implementation of import for this adapter
116
- def import(source_adapter)
117
- get_all = source_adapter.get_all
118
- @lock.with_write_lock { @source.replace(get_all) }
118
+ def import(source)
119
+ adapter = self.class.from(source)
120
+ get_all = Typecast.features_hash(adapter.get_all)
121
+ synchronize { @source.replace(get_all) }
122
+ true
123
+ end
124
+
125
+ private
126
+
127
+ def reset
128
+ @pid = Process.pid
129
+ @lock&.unlock if @lock&.locked?
130
+ end
131
+
132
+ def forked?
133
+ @pid != Process.pid
134
+ end
135
+
136
+ def synchronize(&block)
137
+ if @lock
138
+ reset if forked?
139
+ @lock.synchronize(&block)
140
+ else
141
+ block.call
142
+ end
119
143
  end
120
144
  end
121
145
  end
@@ -5,8 +5,8 @@ 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
9
+ include Flipper::Adapter
10
10
 
11
11
  class Operation
12
12
  attr_reader :type, :args
@@ -18,6 +18,8 @@ module Flipper
18
18
  end
19
19
 
20
20
  OperationTypes = [
21
+ :import,
22
+ :export,
21
23
  :features,
22
24
  :add,
23
25
  :remove,
@@ -37,7 +39,6 @@ module Flipper
37
39
 
38
40
  # Public
39
41
  def initialize(adapter, operations = nil)
40
- super(adapter)
41
42
  @adapter = adapter
42
43
  @name = :operation_logger
43
44
  @operations = operations || []
@@ -98,6 +99,18 @@ module Flipper
98
99
  @adapter.disable(feature, gate, thing)
99
100
  end
100
101
 
102
+ # Public
103
+ def import(source)
104
+ @operations << Operation.new(:import, [source])
105
+ @adapter.import(source)
106
+ end
107
+
108
+ # Public
109
+ def export(format: :json, version: 1)
110
+ @operations << Operation.new(:export, [format, version])
111
+ @adapter.export(format: format, version: version)
112
+ end
113
+
101
114
  # Public: Count the number of times a certain operation happened.
102
115
  def count(type)
103
116
  type(type).size
data/lib/flipper/dsl.rb CHANGED
@@ -10,7 +10,7 @@ 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
14
14
 
15
15
  # Public: Returns a new instance of the DSL.
16
16
  #
@@ -237,12 +237,12 @@ module Flipper
237
237
 
238
238
  # Public: Wraps an object as a flipper actor.
239
239
  #
240
- # thing - The object that you would like to wrap.
240
+ # actor - The object that you would like to wrap.
241
241
  #
242
242
  # Returns an instance of Flipper::Types::Actor.
243
- # Raises ArgumentError if thing does not respond to `flipper_id`.
244
- def actor(thing)
245
- Types::Actor.new(thing)
243
+ # Raises ArgumentError if actor does not respond to `flipper_id`.
244
+ def actor(actor)
245
+ Types::Actor.new(actor)
246
246
  end
247
247
 
248
248
  # Public: Shortcut for getting a percentage of time instance.
@@ -272,10 +272,6 @@ module Flipper
272
272
  adapter.features.map { |name| feature(name) }.to_set
273
273
  end
274
274
 
275
- def import(flipper)
276
- adapter.import(flipper.adapter)
277
- end
278
-
279
275
  # Cloud DSL method that does nothing for open source version.
280
276
  def sync
281
277
  end