flipper 0.7.5 → 0.8.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +12 -0
  3. data/Gemfile +1 -0
  4. data/Rakefile +9 -4
  5. data/docs/Optimization.md +8 -2
  6. data/lib/flipper/adapters/instrumented.rb +21 -17
  7. data/lib/flipper/adapters/memoizable.rb +20 -13
  8. data/lib/flipper/adapters/memory.rb +1 -1
  9. data/lib/flipper/adapters/operation_logger.rb +49 -12
  10. data/lib/flipper/adapters/pstore.rb +1 -1
  11. data/lib/flipper/adapters/read_only.rb +51 -0
  12. data/lib/flipper/dsl.rb +9 -0
  13. data/lib/flipper/feature.rb +8 -20
  14. data/lib/flipper/gate.rb +0 -4
  15. data/lib/flipper/gate_values.rb +4 -2
  16. data/lib/flipper/gates/actor.rb +1 -1
  17. data/lib/flipper/gates/boolean.rb +1 -1
  18. data/lib/flipper/gates/group.rb +1 -1
  19. data/lib/flipper/gates/percentage_of_actors.rb +3 -5
  20. data/lib/flipper/gates/percentage_of_time.rb +2 -3
  21. data/lib/flipper/instrumentation/log_subscriber.rb +0 -28
  22. data/lib/flipper/instrumentation/subscriber.rb +0 -22
  23. data/lib/flipper/middleware/memoizer.rb +2 -3
  24. data/lib/flipper/spec/shared_adapter_specs.rb +32 -0
  25. data/lib/flipper/test/shared_adapter_test.rb +249 -0
  26. data/lib/flipper/typecast.rb +1 -1
  27. data/lib/flipper/types/group.rb +1 -1
  28. data/lib/flipper/version.rb +1 -1
  29. data/spec/flipper/adapters/instrumented_spec.rb +6 -0
  30. data/spec/flipper/adapters/memoizable_spec.rb +6 -0
  31. data/spec/flipper/adapters/read_only_spec.rb +94 -0
  32. data/spec/flipper/dsl_spec.rb +14 -2
  33. data/spec/flipper/feature_spec.rb +11 -0
  34. data/spec/flipper/instrumentation/log_subscriber_spec.rb +0 -10
  35. data/spec/flipper/instrumentation/metriks_subscriber_spec.rb +0 -10
  36. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +0 -10
  37. data/spec/flipper/types/group_spec.rb +14 -0
  38. data/spec/helper.rb +4 -0
  39. data/spec/integration_spec.rb +11 -0
  40. data/spec/support/spec_helpers.rb +4 -0
  41. data/test/adapters/memory_test.rb +10 -0
  42. data/test/adapters/pstore_test.rb +17 -0
  43. data/test/helper.rb +0 -2
  44. data/test/test_helper.rb +6 -0
  45. metadata +12 -4
  46. data/lib/flipper/adapters/decorator.rb +0 -11
  47. data/lib/flipper/decorator.rb +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a196a39494974789034ad6b513ee2f18573909fb
4
- data.tar.gz: 04f3f2db1fa374a7f00220721e48f31f0362b535
3
+ metadata.gz: afbd6c749fed154fc4f131d9a100b6f475d5963c
4
+ data.tar.gz: 28a2fb5f3bf64365f003399cc6287e823da4ae23
5
5
  SHA512:
6
- metadata.gz: fc05f220c23ce4bff918312d62652b6598fe2cab2f0280c300a6508828681dbe84190003cdaf9c2216daa093a4e28c8dd1a7366645a2d62dd6a7aa332751f01a
7
- data.tar.gz: f0f2642874e663cc1b778ab90d1646f625f8e79cf5bfad016148114599b5f0d1befba6c92f72b0b59febe0d6321c5230a270e4f35b59f215d00ce100bafd4a2d
6
+ metadata.gz: 9edcf82bb9e5e600ff797c726aef6bb59c96f9ce86a5ccdc085048553c7d7112b45eb8cdf37782660f0b7d793a2f1abe4a2886d186aac06b721e80ef917bbc18
7
+ data.tar.gz: c0f9f0df3475ba5adee9467512fce9580d32b9398497389173cc9fb12ed5d853d9e26c6e8e4d1c98f8cfd5a4dcc3d61014bd5f366f4af0f2fd1466b432eb5852
@@ -1,3 +1,15 @@
1
+ ## 0.8
2
+
3
+ * removed Flipper::Decorator and Flipper::Adapters::Decorator in favor of just calling methods on wrapped adapter
4
+ * fix bug where certain versions of AR left off quotes for key column which caused issues with MySQL https://github.com/jnunemaker/flipper/issues/120
5
+ * fix bug where AR would store multiple gate values for percentage gates for each enable/disable and then nondeterministically pick one on read (https://github.com/jnunemaker/flipper/pull/122 and https://github.com/jnunemaker/flipper/pull/124)
6
+ * added readonly adapter (https://github.com/jnunemaker/flipper/pull/111)
7
+ * flipper groups now match for truthy values rather than explicitly only true (https://github.com/jnunemaker/flipper/issues/110)
8
+ * removed gate operation instrumentation (https://github.com/jnunemaker/flipper/commit/32f14ed1fb25c64961b23c6be3dc6773143a06c8); I don't think it was useful and never found myself instrumenting it in reality
9
+ * initial implementation of flipper api - very limited functionality right now (get/delete feature, boolean gate for feature) but more is on the way
10
+ * made it easy to remove a feature (https://github.com/jnunemaker/flipper/pull/126)
11
+ * add minitest shared tests for adapters that work the same as the shared specs for rspec (https://github.com/jnunemaker/flipper/pull/127)
12
+
1
13
  ## 0.7.5
2
14
 
3
15
  * support for rails 5 beta/ rack 2 alpha
data/Gemfile CHANGED
@@ -14,6 +14,7 @@ gem 'rspec', '~> 3.0'
14
14
  gem 'rack-test', '~> 0.6.3'
15
15
  gem 'sqlite3', '~> 1.3.11'
16
16
  gem 'rails', "~> #{ENV["RAILS_VERSION"] || '4.2.5'}"
17
+ gem 'minitest', '~> 5.8.0'
17
18
 
18
19
  # for active support tests in test/ and only needed for ruby 2.2.x
19
20
  gem 'test-unit', '~> 3.0'
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env rake
2
2
  $LOAD_PATH.push File.expand_path("../lib", __FILE__)
3
+ require 'rake/testtask'
3
4
  require "flipper/version"
4
5
 
5
6
  # gem install pkg/*.gem
@@ -35,10 +36,14 @@ namespace :spec do
35
36
  end
36
37
  end
37
38
 
38
- task :default => :spec
39
+ Rake::TestTask.new do |t|
40
+ t.libs = ['lib', 'test']
41
+ t.pattern = "test/**/*_test.rb"
42
+ end
39
43
 
40
- task :test do
41
- sh "bundle exec ruby -Itest test/generators/flipper/active_record_generator_test.rb"
44
+ Rake::TestTask.new(:shared_test) do |t|
45
+ t.libs = ['lib', 'test']
46
+ t.pattern = "lib/flipper/shared/test/**_test.rb"
42
47
  end
43
48
 
44
- task :default => :test
49
+ task :default => [:spec, :test, :shared_test]
@@ -18,10 +18,16 @@ If you set your flipper instance up in an initializer, you can pass a block to t
18
18
 
19
19
  ```ruby
20
20
  # config/initializers/flipper.rb
21
- $flipper = Flipper.new(...)
21
+ module MyRailsApp
22
+ def self.flipper
23
+ @flipper ||= Flipper.new(...)
24
+ end
25
+ end
22
26
 
23
27
  # config/application.rb
24
- config.middleware.use Flipper::Middleware::Memoizer, lambda { $flipper }
28
+ config.middleware.use Flipper::Middleware::Memoizer, lambda {
29
+ MyRailsApp.flipper
30
+ }
25
31
  ```
26
32
 
27
33
  **Note**: Be sure that the middleware is high enough up in your stack that all feature checks are wrapped.
@@ -1,17 +1,21 @@
1
- require 'flipper/adapters/decorator'
2
1
  require 'flipper/instrumenters/noop'
3
2
 
4
3
  module Flipper
5
4
  module Adapters
6
5
  # Internal: Adapter that wraps another adapter and instruments all adapter
7
6
  # operations. Used by flipper dsl to provide instrumentatin for flipper.
8
- class Instrumented < Decorator
7
+ class Instrumented
8
+ include ::Flipper::Adapter
9
+
9
10
  # Private: The name of instrumentation events.
10
11
  InstrumentationName = "adapter_operation.#{InstrumentationNamespace}"
11
12
 
12
13
  # Private: What is used to instrument all the things.
13
14
  attr_reader :instrumenter
14
15
 
16
+ # Public: The name of the adapter.
17
+ attr_reader :name
18
+
15
19
  # Internal: Initializes a new adapter instance.
16
20
  #
17
21
  # adapter - Vanilla adapter instance to wrap.
@@ -20,7 +24,7 @@ module Flipper
20
24
  # :instrumenter - What to use to instrument all the things.
21
25
  #
22
26
  def initialize(adapter, options = {})
23
- super(adapter)
27
+ @adapter = adapter
24
28
  @name = :instrumented
25
29
  @instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
26
30
  end
@@ -29,11 +33,11 @@ module Flipper
29
33
  def features
30
34
  payload = {
31
35
  :operation => :features,
32
- :adapter_name => name,
36
+ :adapter_name => @adapter.name,
33
37
  }
34
38
 
35
39
  @instrumenter.instrument(InstrumentationName, payload) { |payload|
36
- payload[:result] = super
40
+ payload[:result] = @adapter.features
37
41
  }
38
42
  end
39
43
 
@@ -41,12 +45,12 @@ module Flipper
41
45
  def add(feature)
42
46
  payload = {
43
47
  :operation => :add,
44
- :adapter_name => name,
48
+ :adapter_name => @adapter.name,
45
49
  :feature_name => feature.name,
46
50
  }
47
51
 
48
52
  @instrumenter.instrument(InstrumentationName, payload) { |payload|
49
- payload[:result] = super
53
+ payload[:result] = @adapter.add(feature)
50
54
  }
51
55
  end
52
56
 
@@ -54,12 +58,12 @@ module Flipper
54
58
  def remove(feature)
55
59
  payload = {
56
60
  :operation => :remove,
57
- :adapter_name => name,
61
+ :adapter_name => @adapter.name,
58
62
  :feature_name => feature.name,
59
63
  }
60
64
 
61
65
  @instrumenter.instrument(InstrumentationName, payload) { |payload|
62
- payload[:result] = super
66
+ payload[:result] = @adapter.remove(feature)
63
67
  }
64
68
  end
65
69
 
@@ -67,12 +71,12 @@ module Flipper
67
71
  def clear(feature)
68
72
  payload = {
69
73
  :operation => :clear,
70
- :adapter_name => name,
74
+ :adapter_name => @adapter.name,
71
75
  :feature_name => feature.name,
72
76
  }
73
77
 
74
78
  @instrumenter.instrument(InstrumentationName, payload) { |payload|
75
- payload[:result] = super
79
+ payload[:result] = @adapter.clear(feature)
76
80
  }
77
81
  end
78
82
 
@@ -80,12 +84,12 @@ module Flipper
80
84
  def get(feature)
81
85
  payload = {
82
86
  :operation => :get,
83
- :adapter_name => name,
87
+ :adapter_name => @adapter.name,
84
88
  :feature_name => feature.name,
85
89
  }
86
90
 
87
91
  @instrumenter.instrument(InstrumentationName, payload) { |payload|
88
- payload[:result] = super
92
+ payload[:result] = @adapter.get(feature)
89
93
  }
90
94
  end
91
95
 
@@ -93,13 +97,13 @@ module Flipper
93
97
  def enable(feature, gate, thing)
94
98
  payload = {
95
99
  :operation => :enable,
96
- :adapter_name => name,
100
+ :adapter_name => @adapter.name,
97
101
  :feature_name => feature.name,
98
102
  :gate_name => gate.name,
99
103
  }
100
104
 
101
105
  @instrumenter.instrument(InstrumentationName, payload) { |payload|
102
- payload[:result] = super
106
+ payload[:result] = @adapter.enable(feature, gate, thing)
103
107
  }
104
108
  end
105
109
 
@@ -107,13 +111,13 @@ module Flipper
107
111
  def disable(feature, gate, thing)
108
112
  payload = {
109
113
  :operation => :disable,
110
- :adapter_name => name,
114
+ :adapter_name => @adapter.name,
111
115
  :feature_name => feature.name,
112
116
  :gate_name => gate.name,
113
117
  }
114
118
 
115
119
  @instrumenter.instrument(InstrumentationName, payload) { |payload|
116
- payload[:result] = super
120
+ payload[:result] = @adapter.disable(feature, gate, thing)
117
121
  }
118
122
  end
119
123
  end
@@ -1,19 +1,26 @@
1
- require 'flipper/adapters/decorator'
2
-
3
1
  module Flipper
4
2
  module Adapters
5
3
  # Internal: Adapter that wraps another adapter with the ability to memoize
6
4
  # adapter calls in memory. Used by flipper dsl and the memoizer middleware
7
5
  # to make it possible to memoize adapter calls for the duration of a request.
8
- class Memoizable < Decorator
6
+ class Memoizable
7
+ include ::Flipper::Adapter
8
+
9
9
  FeaturesKey = :flipper_features
10
10
 
11
11
  # Internal
12
12
  attr_reader :cache
13
13
 
14
+ # Public: The name of the adapter.
15
+ attr_reader :name
16
+
17
+ # Internal: The adapter this adapter is wrapping.
18
+ attr_reader :adapter
19
+
14
20
  # Public
15
21
  def initialize(adapter, cache = nil)
16
- super(adapter)
22
+ @adapter = adapter
23
+ @name = :memoizable
17
24
  @cache = cache || {}
18
25
  @memoize = false
19
26
  end
@@ -22,23 +29,23 @@ module Flipper
22
29
  def features
23
30
  if memoizing?
24
31
  cache.fetch(FeaturesKey) {
25
- cache[FeaturesKey] = super
32
+ cache[FeaturesKey] = @adapter.features
26
33
  }
27
34
  else
28
- super
35
+ @adapter.features
29
36
  end
30
37
  end
31
38
 
32
39
  # Public
33
40
  def add(feature)
34
- result = super
41
+ result = @adapter.add(feature)
35
42
  cache.delete(FeaturesKey) if memoizing?
36
43
  result
37
44
  end
38
45
 
39
46
  # Public
40
47
  def remove(feature)
41
- result = super
48
+ result = @adapter.remove(feature)
42
49
  if memoizing?
43
50
  cache.delete(FeaturesKey)
44
51
  cache.delete(feature)
@@ -48,7 +55,7 @@ module Flipper
48
55
 
49
56
  # Public
50
57
  def clear(feature)
51
- result = super
58
+ result = @adapter.clear(feature)
52
59
  cache.delete(feature) if memoizing?
53
60
  result
54
61
  end
@@ -56,22 +63,22 @@ module Flipper
56
63
  # Public
57
64
  def get(feature)
58
65
  if memoizing?
59
- cache.fetch(feature) { cache[feature] = super }
66
+ cache.fetch(feature) { cache[feature] = @adapter.get(feature) }
60
67
  else
61
- super
68
+ @adapter.get(feature)
62
69
  end
63
70
  end
64
71
 
65
72
  # Public
66
73
  def enable(feature, gate, thing)
67
- result = super
74
+ result = @adapter.enable(feature, gate, thing)
68
75
  cache.delete(feature) if memoizing?
69
76
  result
70
77
  end
71
78
 
72
79
  # Public
73
80
  def disable(feature, gate, thing)
74
- result = super
81
+ result = @adapter.disable(feature, gate, thing)
75
82
  cache.delete(feature) if memoizing?
76
83
  result
77
84
  end
@@ -5,7 +5,7 @@ module Flipper
5
5
  # Public: Adapter for storing everything in memory (ie: Hash).
6
6
  # Useful for tests/specs.
7
7
  class Memory
8
- include Adapter
8
+ include ::Flipper::Adapter
9
9
 
10
10
  FeaturesKey = :flipper_features
11
11
 
@@ -1,11 +1,11 @@
1
- require 'flipper/adapters/decorator'
2
-
3
1
  module Flipper
4
2
  module Adapters
5
3
  # Public: Adapter that wraps another adapter and stores the operations.
6
4
  #
7
5
  # Useful in tests to verify calls and such. Never use outside of testing.
8
- class OperationLogger < Decorator
6
+ class OperationLogger
7
+ include ::Flipper::Adapter
8
+
9
9
  Operation = Struct.new(:type, :args)
10
10
 
11
11
  OperationTypes = [
@@ -21,20 +21,57 @@ module Flipper
21
21
  # Internal: An array of the operations that have happened.
22
22
  attr_reader :operations
23
23
 
24
+ # Internal: The name of the adapter.
25
+ attr_reader :name
26
+
24
27
  # Public
25
28
  def initialize(adapter, operations = nil)
26
- super(adapter)
29
+ @adapter = adapter
30
+ @name = :operation_logger
27
31
  @operations = operations || []
28
32
  end
29
33
 
30
- # Wraps original method with in memory log of operations performed.
31
- OperationTypes.each do |type|
32
- class_eval <<-EOE
33
- def #{type}(*args)
34
- @operations << Operation.new(:#{type}, args)
35
- super
36
- end
37
- EOE
34
+ # Public: The set of known features.
35
+ def features
36
+ @operations << Operation.new(:features, [])
37
+ @adapter.features
38
+ end
39
+
40
+ # Public: Adds a feature to the set of known features.
41
+ def add(feature)
42
+ @operations << Operation.new(:add, [feature])
43
+ @adapter.add(feature)
44
+ end
45
+
46
+ # Public: Removes a feature from the set of known features and clears
47
+ # all the values for the feature.
48
+ def remove(feature)
49
+ @operations << Operation.new(:remove, [feature])
50
+ @adapter.remove(feature)
51
+ end
52
+
53
+ # Public: Clears all the gate values for a feature.
54
+ def clear(feature)
55
+ @operations << Operation.new(:clear, [feature])
56
+ @adapter.clear(feature)
57
+ end
58
+
59
+ # Public
60
+ def get(feature)
61
+ @operations << Operation.new(:get, [feature])
62
+ @adapter.get(feature)
63
+ end
64
+
65
+ # Public
66
+ def enable(feature, gate, thing)
67
+ @operations << Operation.new(:enable, [feature, gate, thing])
68
+ @adapter.enable(feature, gate, thing)
69
+ end
70
+
71
+ # Public
72
+ def disable(feature, gate, thing)
73
+ @operations << Operation.new(:disable, [feature, gate, thing])
74
+ @adapter.disable(feature, gate, thing)
38
75
  end
39
76
 
40
77
  # Public: Count the number of times a certain operation happened.
@@ -6,7 +6,7 @@ module Flipper
6
6
  # Public: Adapter based on Ruby's pstore database. Perfect for when a local
7
7
  # file is good enough for storing features.
8
8
  class PStore
9
- include Adapter
9
+ include ::Flipper::Adapter
10
10
 
11
11
  FeaturesKey = :flipper_features
12
12
 
@@ -0,0 +1,51 @@
1
+ module Flipper
2
+ module Adapters
3
+ # Public: Adapter that wraps another adapter and raises for any writes.
4
+ class ReadOnly
5
+ include ::Flipper::Adapter
6
+
7
+ class WriteAttempted < Error
8
+ def initialize(message = nil)
9
+ super(message || "write attempted while in read only mode")
10
+ end
11
+ end
12
+
13
+ # Internal: The name of the adapter.
14
+ attr_reader :name
15
+
16
+ # Public
17
+ def initialize(adapter)
18
+ @adapter = adapter
19
+ @name = :read_only
20
+ end
21
+
22
+ def features
23
+ @adapter.features
24
+ end
25
+
26
+ def get(feature)
27
+ @adapter.get(feature)
28
+ end
29
+
30
+ def add(feature)
31
+ raise WriteAttempted
32
+ end
33
+
34
+ def remove(feature)
35
+ raise WriteAttempted
36
+ end
37
+
38
+ def clear(feature)
39
+ raise WriteAttempted
40
+ end
41
+
42
+ def enable(feature, gate, thing)
43
+ raise WriteAttempted
44
+ end
45
+
46
+ def disable(feature, gate, thing)
47
+ raise WriteAttempted
48
+ end
49
+ end
50
+ end
51
+ end