flipper 0.7.5 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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