flipper 0.22.0 → 0.28.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +1 -0
  3. data/.github/dependabot.yml +6 -0
  4. data/.github/workflows/ci.yml +26 -20
  5. data/.github/workflows/examples.yml +62 -0
  6. data/.rspec +1 -0
  7. data/.tool-versions +1 -0
  8. data/Changelog.md +152 -3
  9. data/Dockerfile +1 -1
  10. data/Gemfile +9 -6
  11. data/README.md +15 -67
  12. data/Rakefile +4 -2
  13. data/benchmark/enabled_ips.rb +10 -0
  14. data/benchmark/enabled_multiple_actors_ips.rb +20 -0
  15. data/benchmark/enabled_profile.rb +20 -0
  16. data/benchmark/instrumentation_ips.rb +21 -0
  17. data/benchmark/typecast_ips.rb +19 -0
  18. data/docker-compose.yml +37 -34
  19. data/docs/README.md +1 -0
  20. data/docs/images/banner.jpg +0 -0
  21. data/examples/api/basic.ru +3 -4
  22. data/examples/api/custom_memoized.ru +3 -4
  23. data/examples/api/memoized.ru +3 -4
  24. data/examples/dsl.rb +3 -3
  25. data/examples/enabled_for_actor.rb +4 -2
  26. data/examples/instrumentation.rb +1 -0
  27. data/examples/instrumentation_last_accessed_at.rb +38 -0
  28. data/flipper.gemspec +2 -2
  29. data/lib/flipper/actor.rb +4 -0
  30. data/lib/flipper/adapter.rb +23 -7
  31. data/lib/flipper/adapters/dual_write.rb +10 -16
  32. data/lib/flipper/adapters/failover.rb +83 -0
  33. data/lib/flipper/adapters/failsafe.rb +76 -0
  34. data/lib/flipper/adapters/http/client.rb +18 -12
  35. data/lib/flipper/adapters/http/error.rb +19 -1
  36. data/lib/flipper/adapters/http.rb +14 -4
  37. data/lib/flipper/adapters/instrumented.rb +25 -2
  38. data/lib/flipper/adapters/memoizable.rb +27 -18
  39. data/lib/flipper/adapters/memory.rb +56 -39
  40. data/lib/flipper/adapters/operation_logger.rb +16 -3
  41. data/lib/flipper/adapters/poll/poller.rb +2 -0
  42. data/lib/flipper/adapters/poll.rb +39 -0
  43. data/lib/flipper/adapters/pstore.rb +2 -5
  44. data/lib/flipper/adapters/sync/interval_synchronizer.rb +1 -6
  45. data/lib/flipper/adapters/sync/synchronizer.rb +2 -1
  46. data/lib/flipper/adapters/sync.rb +9 -15
  47. data/lib/flipper/dsl.rb +9 -11
  48. data/lib/flipper/errors.rb +3 -20
  49. data/lib/flipper/export.rb +26 -0
  50. data/lib/flipper/exporter.rb +17 -0
  51. data/lib/flipper/exporters/json/export.rb +32 -0
  52. data/lib/flipper/exporters/json/v1.rb +33 -0
  53. data/lib/flipper/feature.rb +32 -26
  54. data/lib/flipper/feature_check_context.rb +10 -6
  55. data/lib/flipper/gate.rb +12 -11
  56. data/lib/flipper/gate_values.rb +0 -16
  57. data/lib/flipper/gates/actor.rb +10 -17
  58. data/lib/flipper/gates/boolean.rb +1 -1
  59. data/lib/flipper/gates/group.rb +5 -7
  60. data/lib/flipper/gates/percentage_of_actors.rb +10 -13
  61. data/lib/flipper/gates/percentage_of_time.rb +1 -2
  62. data/lib/flipper/identifier.rb +2 -2
  63. data/lib/flipper/instrumentation/log_subscriber.rb +7 -3
  64. data/lib/flipper/instrumentation/subscriber.rb +8 -1
  65. data/lib/flipper/instrumenters/memory.rb +6 -2
  66. data/lib/flipper/metadata.rb +1 -1
  67. data/lib/flipper/middleware/memoizer.rb +2 -12
  68. data/lib/flipper/poller.rb +117 -0
  69. data/lib/flipper/railtie.rb +23 -22
  70. data/lib/flipper/spec/shared_adapter_specs.rb +23 -0
  71. data/lib/flipper/test/shared_adapter_test.rb +24 -0
  72. data/lib/flipper/typecast.rb +28 -15
  73. data/lib/flipper/types/actor.rb +19 -13
  74. data/lib/flipper/types/group.rb +12 -5
  75. data/lib/flipper/version.rb +1 -1
  76. data/lib/flipper.rb +6 -4
  77. data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
  78. data/spec/flipper/actor_spec.rb +10 -2
  79. data/spec/flipper/adapter_spec.rb +29 -4
  80. data/spec/flipper/adapters/dual_write_spec.rb +0 -2
  81. data/spec/flipper/adapters/failover_spec.rb +129 -0
  82. data/spec/flipper/adapters/failsafe_spec.rb +58 -0
  83. data/spec/flipper/adapters/http_spec.rb +64 -6
  84. data/spec/flipper/adapters/instrumented_spec.rb +28 -12
  85. data/spec/flipper/adapters/memoizable_spec.rb +30 -12
  86. data/spec/flipper/adapters/memory_spec.rb +3 -4
  87. data/spec/flipper/adapters/operation_logger_spec.rb +29 -12
  88. data/spec/flipper/adapters/pstore_spec.rb +0 -2
  89. data/spec/flipper/adapters/read_only_spec.rb +0 -1
  90. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +0 -1
  91. data/spec/flipper/adapters/sync/interval_synchronizer_spec.rb +4 -5
  92. data/spec/flipper/adapters/sync/synchronizer_spec.rb +0 -1
  93. data/spec/flipper/adapters/sync_spec.rb +0 -2
  94. data/spec/flipper/configuration_spec.rb +0 -1
  95. data/spec/flipper/dsl_spec.rb +38 -12
  96. data/spec/flipper/export_spec.rb +13 -0
  97. data/spec/flipper/exporter_spec.rb +16 -0
  98. data/spec/flipper/exporters/json/export_spec.rb +60 -0
  99. data/spec/flipper/exporters/json/v1_spec.rb +33 -0
  100. data/spec/flipper/feature_check_context_spec.rb +17 -19
  101. data/spec/flipper/feature_spec.rb +76 -33
  102. data/spec/flipper/gate_spec.rb +0 -2
  103. data/spec/flipper/gate_values_spec.rb +2 -34
  104. data/spec/flipper/gates/actor_spec.rb +0 -2
  105. data/spec/flipper/gates/boolean_spec.rb +1 -3
  106. data/spec/flipper/gates/group_spec.rb +2 -5
  107. data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -7
  108. data/spec/flipper/gates/percentage_of_time_spec.rb +2 -4
  109. data/spec/flipper/identifier_spec.rb +0 -1
  110. data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -6
  111. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +10 -1
  112. data/spec/flipper/instrumenters/memory_spec.rb +18 -1
  113. data/spec/flipper/instrumenters/noop_spec.rb +14 -8
  114. data/spec/flipper/middleware/memoizer_spec.rb +0 -23
  115. data/spec/flipper/middleware/setup_env_spec.rb +0 -2
  116. data/spec/flipper/poller_spec.rb +47 -0
  117. data/spec/flipper/railtie_spec.rb +73 -33
  118. data/spec/flipper/registry_spec.rb +0 -1
  119. data/spec/flipper/typecast_spec.rb +82 -4
  120. data/spec/flipper/types/actor_spec.rb +45 -46
  121. data/spec/flipper/types/boolean_spec.rb +0 -1
  122. data/spec/flipper/types/group_spec.rb +2 -3
  123. data/spec/flipper/types/percentage_of_actors_spec.rb +0 -1
  124. data/spec/flipper/types/percentage_of_time_spec.rb +0 -1
  125. data/spec/flipper/types/percentage_spec.rb +0 -1
  126. data/spec/flipper_integration_spec.rb +62 -51
  127. data/spec/flipper_spec.rb +7 -2
  128. data/spec/{helper.rb → spec_helper.rb} +4 -2
  129. data/spec/support/skippable.rb +18 -0
  130. data/spec/support/spec_helpers.rb +2 -6
  131. metadata +63 -19
  132. data/docs/Adapters.md +0 -124
  133. data/docs/Caveats.md +0 -4
  134. data/docs/Gates.md +0 -167
  135. data/docs/Instrumentation.md +0 -27
  136. data/docs/Optimization.md +0 -137
  137. data/docs/api/README.md +0 -884
  138. data/docs/http/README.md +0 -36
  139. data/docs/read-only/README.md +0 -24
@@ -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 || {}
@@ -45,24 +44,20 @@ module Flipper
45
44
 
46
45
  # Public
47
46
  def add(feature)
48
- result = @adapter.add(feature)
49
- expire_features_set
50
- result
47
+ @adapter.add(feature).tap { expire_features_set }
51
48
  end
52
49
 
53
50
  # Public
54
51
  def remove(feature)
55
- result = @adapter.remove(feature)
56
- expire_features_set
57
- expire_feature(feature)
58
- result
52
+ @adapter.remove(feature).tap do
53
+ expire_features_set
54
+ expire_feature(feature)
55
+ end
59
56
  end
60
57
 
61
58
  # Public
62
59
  def clear(feature)
63
- result = @adapter.clear(feature)
64
- expire_feature(feature)
65
- result
60
+ @adapter.clear(feature).tap { expire_feature(feature) }
66
61
  end
67
62
 
68
63
  # Public
@@ -124,16 +119,20 @@ module Flipper
124
119
 
125
120
  # Public
126
121
  def enable(feature, gate, thing)
127
- result = @adapter.enable(feature, gate, thing)
128
- expire_feature(feature)
129
- result
122
+ @adapter.enable(feature, gate, thing).tap { expire_feature(feature) }
130
123
  end
131
124
 
132
125
  # Public
133
126
  def disable(feature, gate, thing)
134
- result = @adapter.disable(feature, gate, thing)
135
- expire_feature(feature)
136
- result
127
+ @adapter.disable(feature, gate, thing).tap { expire_feature(feature) }
128
+ end
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)
137
136
  end
138
137
 
139
138
  # Internal: Turns local caching on/off.
@@ -149,6 +148,16 @@ module Flipper
149
148
  !!@memoize
150
149
  end
151
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
+
152
161
  private
153
162
 
154
163
  def key_for(key)
@@ -1,4 +1,6 @@
1
- require 'set'
1
+ require "flipper/adapter"
2
+ require "flipper/typecast"
3
+ require 'concurrent/atomic/read_write_lock'
2
4
 
3
5
  module Flipper
4
6
  module Adapters
@@ -14,86 +16,93 @@ module Flipper
14
16
 
15
17
  # Public
16
18
  def initialize(source = nil)
17
- @source = source || {}
19
+ @source = Typecast.features_hash(source)
18
20
  @name = :memory
21
+ @lock = Concurrent::ReadWriteLock.new
19
22
  end
20
23
 
21
24
  # Public: The set of known features.
22
25
  def features
23
- @source.keys.to_set
26
+ @lock.with_read_lock { @source.keys }.to_set
24
27
  end
25
28
 
26
29
  # Public: Adds a feature to the set of known features.
27
30
  def add(feature)
28
- @source[feature.key] ||= default_config
31
+ @lock.with_write_lock { @source[feature.key] ||= default_config }
29
32
  true
30
33
  end
31
34
 
32
35
  # Public: Removes a feature from the set of known features and clears
33
36
  # all the values for the feature.
34
37
  def remove(feature)
35
- @source.delete(feature.key)
38
+ @lock.with_write_lock { @source.delete(feature.key) }
36
39
  true
37
40
  end
38
41
 
39
42
  # Public: Clears all the gate values for a feature.
40
43
  def clear(feature)
41
- @source[feature.key] = default_config
44
+ @lock.with_write_lock { @source[feature.key] = default_config }
42
45
  true
43
46
  end
44
47
 
45
48
  # Public
46
49
  def get(feature)
47
- @source[feature.key] || default_config
50
+ @lock.with_read_lock { @source[feature.key] } || default_config
48
51
  end
49
52
 
50
53
  def get_multi(features)
51
- result = {}
52
- features.each do |feature|
53
- result[feature.key] = @source[feature.key] || default_config
54
+ @lock.with_read_lock do
55
+ result = {}
56
+ features.each do |feature|
57
+ result[feature.key] = @source[feature.key] || default_config
58
+ end
59
+ result
54
60
  end
55
- result
56
61
  end
57
62
 
58
63
  def get_all
59
- @source
64
+ @lock.with_read_lock { Typecast.features_hash(@source) }
60
65
  end
61
66
 
62
67
  # Public
63
68
  def enable(feature, gate, thing)
64
- @source[feature.key] ||= default_config
65
-
66
- case gate.data_type
67
- when :boolean
68
- clear(feature)
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
- else
75
- raise "#{gate} is not supported by this adapter yet"
69
+ @lock.with_write_lock do
70
+ @source[feature.key] ||= default_config
71
+
72
+ case gate.data_type
73
+ when :boolean
74
+ @source[feature.key] = default_config
75
+ @source[feature.key][gate.key] = thing.value.to_s
76
+ when :integer
77
+ @source[feature.key][gate.key] = thing.value.to_s
78
+ when :set
79
+ @source[feature.key][gate.key] << thing.value.to_s
80
+ else
81
+ raise "#{gate} is not supported by this adapter yet"
82
+ end
83
+
84
+ true
76
85
  end
77
-
78
- true
79
86
  end
80
87
 
81
88
  # Public
82
89
  def disable(feature, gate, thing)
83
- @source[feature.key] ||= default_config
84
-
85
- case gate.data_type
86
- when :boolean
87
- clear(feature)
88
- when :integer
89
- @source[feature.key][gate.key] = thing.value.to_s
90
- when :set
91
- @source[feature.key][gate.key].delete thing.value.to_s
92
- else
93
- raise "#{gate} is not supported by this adapter yet"
90
+ @lock.with_write_lock do
91
+ @source[feature.key] ||= default_config
92
+
93
+ case gate.data_type
94
+ when :boolean
95
+ @source[feature.key] = default_config
96
+ when :integer
97
+ @source[feature.key][gate.key] = thing.value.to_s
98
+ when :set
99
+ @source[feature.key][gate.key].delete thing.value.to_s
100
+ else
101
+ raise "#{gate} is not supported by this adapter yet"
102
+ end
103
+
104
+ true
94
105
  end
95
-
96
- true
97
106
  end
98
107
 
99
108
  # Public
@@ -104,6 +113,14 @@ module Flipper
104
113
  ]
105
114
  "#<#{self.class.name}:#{object_id} #{attributes.join(', ')}>"
106
115
  end
116
+
117
+ # Public: a more efficient implementation of import for this adapter
118
+ def import(source)
119
+ adapter = self.class.from(source)
120
+ get_all = Typecast.features_hash(adapter.get_all)
121
+ @lock.with_write_lock { @source.replace(get_all) }
122
+ true
123
+ end
107
124
  end
108
125
  end
109
126
  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
@@ -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,39 @@
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
+ # Public: The name of the adapter.
14
+ attr_reader :name, :adapter, :poller
15
+
16
+ def_delegators :synced_adapter, :features, :get, :get_multi, :get_all, :add, :remove, :clear, :enable, :disable
17
+
18
+ def initialize(poller, adapter)
19
+ @name = :poll
20
+ @adapter = adapter
21
+ @poller = poller
22
+ @last_synced_at = 0
23
+ @poller.start
24
+ end
25
+
26
+ private
27
+
28
+ def synced_adapter
29
+ @poller.start
30
+ poller_last_synced_at = @poller.last_synced_at.value
31
+ if poller_last_synced_at > @last_synced_at
32
+ Flipper::Adapters::Sync::Synchronizer.new(@adapter, @poller.adapter).call
33
+ @last_synced_at = poller_last_synced_at
34
+ end
35
+ @adapter
36
+ end
37
+ end
38
+ end
39
+ end
@@ -17,14 +17,11 @@ module Flipper
17
17
  # Public: The path to where the file is stored.
18
18
  attr_reader :path
19
19
 
20
- # Public: PStore's thread_safe option.
21
- attr_reader :thread_safe
22
-
23
20
  # Public
24
- def initialize(path = 'flipper.pstore', thread_safe = false)
21
+ def initialize(path = 'flipper.pstore', thread_safe = true)
22
+ @name = :pstore
25
23
  @path = path
26
24
  @store = ::PStore.new(path, thread_safe)
27
- @name = :pstore
28
25
  end
29
26
 
30
27
  # Public: The set of known features.
@@ -7,11 +7,6 @@ module Flipper
7
7
  # Private: Number of seconds between syncs (default: 10).
8
8
  DEFAULT_INTERVAL = 10
9
9
 
10
- # Private
11
- def self.now
12
- Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
13
- end
14
-
15
10
  # Public: The Float or Integer number of seconds between invocations of
16
11
  # the wrapped synchronizer.
17
12
  attr_reader :interval
@@ -46,7 +41,7 @@ module Flipper
46
41
  end
47
42
 
48
43
  def now
49
- self.class.now
44
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
50
45
  end
51
46
  end
52
47
  end
@@ -37,7 +37,8 @@ module Flipper
37
37
  # Sync all the gate values.
38
38
  remote_get_all.each do |feature_key, remote_gates_hash|
39
39
  feature = Feature.new(feature_key, @local)
40
- local_gates_hash = local_get_all[feature_key] || @local.default_config
40
+ # Check if feature_key is in hash before accessing to prevent unintended hash modification
41
+ local_gates_hash = local_get_all.key?(feature_key) ? local_get_all[feature_key] : @local.default_config
41
42
  local_gate_values = GateValues.new(local_gates_hash)
42
43
  remote_gate_values = GateValues.new(remote_gates_hash)
43
44
  FeatureSynchronizer.new(feature, local_gate_values, remote_gate_values).call
@@ -58,33 +58,27 @@ module Flipper
58
58
  end
59
59
 
60
60
  def add(feature)
61
- result = @remote.add(feature)
62
- @local.add(feature)
63
- result
61
+ @remote.add(feature).tap { @local.add(feature) }
64
62
  end
65
63
 
66
64
  def remove(feature)
67
- result = @remote.remove(feature)
68
- @local.remove(feature)
69
- result
65
+ @remote.remove(feature).tap { @local.remove(feature) }
70
66
  end
71
67
 
72
68
  def clear(feature)
73
- result = @remote.clear(feature)
74
- @local.clear(feature)
75
- result
69
+ @remote.clear(feature).tap { @local.clear(feature) }
76
70
  end
77
71
 
78
72
  def enable(feature, gate, thing)
79
- result = @remote.enable(feature, gate, thing)
80
- @local.enable(feature, gate, thing)
81
- result
73
+ @remote.enable(feature, gate, thing).tap do
74
+ @local.enable(feature, gate, thing)
75
+ end
82
76
  end
83
77
 
84
78
  def disable(feature, gate, thing)
85
- result = @remote.disable(feature, gate, thing)
86
- @local.disable(feature, gate, thing)
87
- result
79
+ @remote.disable(feature, gate, thing).tap do
80
+ @local.disable(feature, gate, thing)
81
+ end
88
82
  end
89
83
 
90
84
  private
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
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
 
@@ -235,12 +237,12 @@ module Flipper
235
237
 
236
238
  # Public: Wraps an object as a flipper actor.
237
239
  #
238
- # thing - The object that you would like to wrap.
240
+ # actor - The object that you would like to wrap.
239
241
  #
240
242
  # 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)
243
+ # Raises ArgumentError if actor does not respond to `flipper_id`.
244
+ def actor(actor)
245
+ Types::Actor.new(actor)
244
246
  end
245
247
 
246
248
  # Public: Shortcut for getting a percentage of time instance.
@@ -270,10 +272,6 @@ 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
- end
276
-
277
275
  # Cloud DSL method that does nothing for open source version.
278
276
  def sync
279
277
  end
@@ -2,25 +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
- warn "Flipper::DefaultNotSet is deprecated and will be removed in 1.0"
20
- super
21
- end
22
- end
23
-
24
15
  # Raised when an invalid value is set to a configuration property
25
16
  class InvalidConfigurationValue < Flipper::Error
26
17
  def initialize(message = nil)
@@ -28,12 +19,4 @@ module Flipper
28
19
  super(message || default)
29
20
  end
30
21
  end
31
-
32
- # Raised when accessing a configuration property that has been deprecated
33
- class ConfigurationDeprecated < Flipper::Error
34
- def initialize(message = nil)
35
- default = "The configuration property has been deprecated"
36
- super(message || default)
37
- end
38
- end
39
22
  end
@@ -0,0 +1,26 @@
1
+ require "flipper/adapters/memory"
2
+
3
+ module Flipper
4
+ class Export
5
+ attr_reader :contents, :format, :version
6
+
7
+ def initialize(contents:, format: :json, version: 1)
8
+ @contents = contents
9
+ @format = format
10
+ @version = version
11
+ end
12
+
13
+ def features
14
+ raise NotImplementedError
15
+ end
16
+
17
+ def adapter
18
+ @adapter ||= Flipper::Adapters::Memory.new(features)
19
+ end
20
+
21
+ def eql?(other)
22
+ self.class.eql?(other.class) && @contents == other.contents && @format == other.format && @version == other.version
23
+ end
24
+ alias_method :==, :eql?
25
+ end
26
+ 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 = JSON.parse(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 = JSON.dump({
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