flipper 0.22.0 → 0.28.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 (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