flipper 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Guardfile +3 -8
- data/README.md +26 -38
- data/examples/percentage_of_actors.rb +17 -12
- data/examples/percentage_of_random.rb +3 -7
- data/lib/flipper.rb +8 -1
- data/lib/flipper/adapter.rb +2 -208
- data/lib/flipper/adapters/decorator.rb +9 -0
- data/lib/flipper/adapters/instrumented.rb +92 -0
- data/lib/flipper/adapters/memoizable.rb +88 -0
- data/lib/flipper/adapters/memory.rb +89 -7
- data/lib/flipper/adapters/operation_logger.rb +31 -45
- data/lib/flipper/decorator.rb +6 -0
- data/lib/flipper/dsl.rb +29 -2
- data/lib/flipper/feature.rb +83 -49
- data/lib/flipper/gate.rb +24 -41
- data/lib/flipper/gates/actor.rb +24 -24
- data/lib/flipper/gates/boolean.rb +28 -15
- data/lib/flipper/gates/group.rb +25 -34
- data/lib/flipper/gates/percentage_of_actors.rb +21 -13
- data/lib/flipper/gates/percentage_of_random.rb +20 -12
- data/lib/flipper/instrumentation/log_subscriber.rb +14 -22
- data/lib/flipper/middleware/memoizer.rb +23 -0
- data/lib/flipper/spec/shared_adapter_specs.rb +141 -92
- data/lib/flipper/types/boolean.rb +5 -1
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/adapters/instrumented_spec.rb +92 -0
- data/spec/flipper/adapters/memoizable_spec.rb +184 -0
- data/spec/flipper/adapters/memory_spec.rb +1 -11
- data/spec/flipper/adapters/operation_logger_spec.rb +93 -0
- data/spec/flipper/dsl_spec.rb +18 -43
- data/spec/flipper/feature_spec.rb +25 -9
- data/spec/flipper/gate_spec.rb +8 -20
- data/spec/flipper/gates/actor_spec.rb +6 -14
- data/spec/flipper/gates/boolean_spec.rb +80 -13
- data/spec/flipper/gates/group_spec.rb +8 -18
- data/spec/flipper/gates/percentage_of_actors_spec.rb +12 -28
- data/spec/flipper/gates/percentage_of_random_spec.rb +6 -14
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -8
- data/spec/flipper/instrumentation/metriks_subscriber_spec.rb +3 -6
- data/spec/flipper/middleware/{local_cache_spec.rb → memoizer_spec.rb} +25 -55
- data/spec/flipper/types/boolean_spec.rb +13 -3
- data/spec/flipper_spec.rb +7 -0
- data/spec/helper.rb +21 -3
- data/spec/integration_spec.rb +115 -116
- metadata +17 -27
- data/lib/flipper/adapters/memoized.rb +0 -55
- data/lib/flipper/key.rb +0 -38
- data/lib/flipper/middleware/local_cache.rb +0 -36
- data/lib/flipper/toggle.rb +0 -54
- data/lib/flipper/toggles/boolean.rb +0 -54
- data/lib/flipper/toggles/set.rb +0 -25
- data/lib/flipper/toggles/value.rb +0 -25
- data/spec/flipper/adapter_spec.rb +0 -463
- data/spec/flipper/adapters/memoized_spec.rb +0 -93
- data/spec/flipper/key_spec.rb +0 -23
- data/spec/flipper/toggle_spec.rb +0 -22
- data/spec/flipper/toggles/boolean_spec.rb +0 -40
- data/spec/flipper/toggles/set_spec.rb +0 -35
- data/spec/flipper/toggles/value_spec.rb +0 -55
data/Guardfile
CHANGED
@@ -13,13 +13,8 @@ rspec_options = {
|
|
13
13
|
}
|
14
14
|
|
15
15
|
guard 'rspec', rspec_options do
|
16
|
-
watch(%r{^spec/.+_spec\.rb$})
|
17
|
-
watch(%r{^lib/(.+)\.rb$}) {
|
18
|
-
watch(%r{shared_adapter_specs\.rb$}) {
|
19
|
-
[
|
20
|
-
"spec/flipper/adapters/memory_spec.rb",
|
21
|
-
"spec/flipper/adapters/memoized_spec.rb",
|
22
|
-
]
|
23
|
-
}
|
16
|
+
watch(%r{^spec/.+_spec\.rb$}) { "spec" }
|
17
|
+
watch(%r{^lib/(.+)\.rb$}) { "spec" }
|
18
|
+
watch(%r{shared_adapter_specs\.rb$}) { "spec" }
|
24
19
|
watch('spec/helper.rb') { "spec" }
|
25
20
|
end
|
data/README.md
CHANGED
@@ -4,6 +4,20 @@ Feature flipping is the act of enabling or disabling features or parts of your a
|
|
4
4
|
|
5
5
|
The goal of this gem is to make turning features on or off so easy that everyone does it. Whatever your data store, throughput, or experience, feature flipping should be easy and have minimal impact on your application.
|
6
6
|
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'flipper'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself with:
|
18
|
+
|
19
|
+
$ gem install flipper
|
20
|
+
|
7
21
|
## Coming Soon™
|
8
22
|
|
9
23
|
* [Web UI](https://github.com/jnunemaker/flipper-ui) (think resque UI for features toggling/status)
|
@@ -155,56 +169,30 @@ Randomness is not a good idea for enabling new features in the UI. Most of the t
|
|
155
169
|
|
156
170
|
I plan on supporting [in-memory](https://github.com/jnunemaker/flipper/blob/master/lib/flipper/adapters/memory.rb), [Mongo](https://github.com/jnunemaker/flipper-mongo), and [Redis](https://github.com/jnunemaker/flipper-redis) as adapters for flipper. Others are welcome so please let me know if you create one.
|
157
171
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
### Mongo
|
163
|
-
|
164
|
-
Currently, the [mongo adapter](https://github.com/jnunemaker/flipper-mongo) comes in two flavors.
|
165
|
-
|
166
|
-
The [vanilla mongo adapter](https://github.com/jnunemaker/flipper-mongo/blob/master/lib/flipper/adapters/mongo.rb) stores each key in its own document. This means for each gate checked per feature there will be a query to mongo.
|
167
|
-
|
168
|
-
Personally, the adapter I prefer is the [single document adapter](https://github.com/jnunemaker/flipper-mongo/blob/master/lib/flipper/adapters/mongo_single_document.rb), which stores all features and gates in a single document. If you combine this adapter with the [local cache middleware](https://github.com/jnunemaker/flipper/blob/master/lib/flipper/middleware/local_cache.rb), the document will only be queried once per request, which is pretty awesome.
|
169
|
-
|
170
|
-
### Redis
|
171
|
-
|
172
|
-
Redis is great for this type of stuff and it only took a few minutes to implement a [redis adapter](https://github.com/jnunemaker/flipper-redis). The only real problem with redis right now is that automated failover isn't that easy so relying on it for every code path in my app would make me nervous.
|
172
|
+
* [memory adapter](https://github.com/jnunemaker/flipper/blob/master/lib/flipper/adapters/memory.rb) - Great for tests.
|
173
|
+
* [mongo adapter](https://github.com/jnunemaker/flipper-mongo)
|
174
|
+
* [redis adapter](https://github.com/jnunemaker/flipper-redis)
|
175
|
+
* [cassanity adapter](https://github.com/jnunemaker/flipper-cassanity)
|
173
176
|
|
174
177
|
## Optimization
|
175
178
|
|
176
|
-
One optimization that flipper provides is a
|
179
|
+
One optimization that flipper provides is a memoizing middleware. The memoizing middleware ensures that you only make one adapter call per feature per request.
|
177
180
|
|
178
|
-
|
181
|
+
This means if you check the same feature over and over, it will only make one mongo, redis, or whatever call per feature for the length of the request.
|
182
|
+
|
183
|
+
You can use the middleware from a Rails initializer like so:
|
179
184
|
|
180
185
|
```ruby
|
181
|
-
require 'flipper/middleware/
|
186
|
+
require 'flipper/middleware/memoizer'
|
182
187
|
|
183
188
|
# create flipper dsl instance, see above examples for more details
|
184
189
|
flipper = Flipper.new(...)
|
185
190
|
|
186
|
-
#
|
187
|
-
|
188
|
-
# identity map, which this is akin to, and figured it was for a reason.
|
189
|
-
Rails.application.config.middleware.insert_after \
|
190
|
-
ActionDispatch::Callbacks,
|
191
|
-
Flipper::Middleware::LocalCache,
|
192
|
-
flipper
|
191
|
+
# add the middleware
|
192
|
+
Rails.application.config.middleware.use Flipper::Middleware::Memoizer, flipper
|
193
193
|
```
|
194
194
|
|
195
|
-
|
196
|
-
|
197
|
-
Add this line to your application's Gemfile:
|
198
|
-
|
199
|
-
gem 'flipper'
|
200
|
-
|
201
|
-
And then execute:
|
202
|
-
|
203
|
-
$ bundle
|
204
|
-
|
205
|
-
Or install it yourself with:
|
206
|
-
|
207
|
-
$ gem install flipper
|
195
|
+
**Note**: Be sure that the middlware is high enough up in your stack that all feature checks are wrapped.
|
208
196
|
|
209
197
|
## Contributing
|
210
198
|
|
@@ -19,20 +19,25 @@ class User
|
|
19
19
|
alias_method :flipper_id, :id
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
|
-
clooney = User.new(10)
|
22
|
+
total = 10_000
|
24
23
|
|
25
|
-
|
26
|
-
|
24
|
+
# create array of fake users
|
25
|
+
users = (1..total).map { |n| User.new(n) }
|
27
26
|
|
28
|
-
|
29
|
-
stats.enable
|
27
|
+
perform_test = lambda { |number|
|
28
|
+
flipper[:stats].enable flipper.actors(number)
|
30
29
|
|
31
|
-
|
32
|
-
|
30
|
+
enabled = users.map { |user|
|
31
|
+
flipper[:stats].enabled?(user) ? true : nil
|
32
|
+
}.compact
|
33
33
|
|
34
|
-
|
35
|
-
stats.enable(Flipper::Types::PercentageOfActors.new(50))
|
34
|
+
actual = (enabled.size / total.to_f * 100).round(2)
|
36
35
|
|
37
|
-
puts "
|
38
|
-
|
36
|
+
puts "percentage: #{actual.to_s.rjust(6, ' ')} vs #{number.to_s.rjust(3, ' ')}"
|
37
|
+
}
|
38
|
+
|
39
|
+
puts "percentage: Actual vs Hoped For"
|
40
|
+
|
41
|
+
[1, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99, 100].each do |number|
|
42
|
+
perform_test.call number
|
43
|
+
end
|
@@ -14,13 +14,9 @@ perform_test = lambda do |number|
|
|
14
14
|
enabled = []
|
15
15
|
disabled = []
|
16
16
|
|
17
|
-
(1..total).
|
18
|
-
|
19
|
-
|
20
|
-
else
|
21
|
-
disabled << number
|
22
|
-
end
|
23
|
-
end
|
17
|
+
enabled = (1..total).map { |n|
|
18
|
+
logging.enabled? ? true : nil
|
19
|
+
}.compact
|
24
20
|
|
25
21
|
actual = (enabled.size / total.to_f * 100).round(2)
|
26
22
|
|
data/lib/flipper.rb
CHANGED
@@ -30,6 +30,13 @@ module Flipper
|
|
30
30
|
raise DuplicateGroup, %Q{Group #{name.inspect} has already been registered}
|
31
31
|
end
|
32
32
|
|
33
|
+
# Public: Clears the group registry.
|
34
|
+
#
|
35
|
+
# Returns nothing.
|
36
|
+
def self.unregister_groups
|
37
|
+
groups.clear
|
38
|
+
end
|
39
|
+
|
33
40
|
# Internal: Fetches a group by name.
|
34
41
|
#
|
35
42
|
# name - The Symbol name of the group.
|
@@ -57,10 +64,10 @@ module Flipper
|
|
57
64
|
end
|
58
65
|
end
|
59
66
|
|
67
|
+
require 'flipper/adapter'
|
60
68
|
require 'flipper/dsl'
|
61
69
|
require 'flipper/errors'
|
62
70
|
require 'flipper/feature'
|
63
71
|
require 'flipper/gate'
|
64
72
|
require 'flipper/registry'
|
65
|
-
require 'flipper/toggle'
|
66
73
|
require 'flipper/type'
|
data/lib/flipper/adapter.rb
CHANGED
@@ -1,211 +1,5 @@
|
|
1
|
-
require 'flipper/instrumenters/noop'
|
2
|
-
|
3
1
|
module Flipper
|
4
|
-
|
5
|
-
|
6
|
-
# the adapter.
|
7
|
-
#
|
8
|
-
# So what is this local cache crap?
|
9
|
-
#
|
10
|
-
# The main goal of the local cache is to prevent multiple queries to an
|
11
|
-
# adapter for the same key for a given amount of time (per request, per
|
12
|
-
# background job, etc.).
|
13
|
-
#
|
14
|
-
# To facilitate with this, there is an included local cache middleware
|
15
|
-
# that enables local caching for the length of a web request. The local
|
16
|
-
# cache is enabled and cleared before each request and cleared and reset
|
17
|
-
# to original value after each request.
|
18
|
-
#
|
19
|
-
# Examples
|
20
|
-
#
|
21
|
-
# To see an example adapter that this would wrap, checkout the [memory
|
22
|
-
# adapter included with flipper](https://github.com/jnunemaker/flipper/blob/master/lib/flipper/adapters/memory.rb).
|
23
|
-
class Adapter
|
24
|
-
# Private: The name of instrumentation events.
|
25
|
-
InstrumentationName = "adapter_operation.#{InstrumentationNamespace}"
|
26
|
-
|
27
|
-
# Private: The name of the key that stores the set of known features.
|
28
|
-
FeaturesKey = 'features'
|
29
|
-
|
30
|
-
# Internal: Wraps vanilla adapter instance for use internally in flipper.
|
31
|
-
#
|
32
|
-
# object - Either an instance of Flipper::Adapter or a vanilla adapter instance
|
33
|
-
#
|
34
|
-
# Examples
|
35
|
-
#
|
36
|
-
# adapter = Flipper::Adapters::Memory.new
|
37
|
-
# instance = Flipper::Adapter.new(adapter)
|
38
|
-
#
|
39
|
-
# Flipper::Adapter.wrap(instance)
|
40
|
-
# # => Flipper::Adapter instance
|
41
|
-
#
|
42
|
-
# Flipper::Adapter.wrap(adapter)
|
43
|
-
# # => Flipper::Adapter instance
|
44
|
-
#
|
45
|
-
# Returns Flipper::Adapter instance
|
46
|
-
def self.wrap(object, options = {})
|
47
|
-
if object.is_a?(Flipper::Adapter)
|
48
|
-
object
|
49
|
-
else
|
50
|
-
new(object, options)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
# Private: What adapter is being wrapped and will ultimately be used.
|
55
|
-
attr_reader :adapter
|
56
|
-
|
57
|
-
# Private: The name of the adapter. Based on the class name.
|
58
|
-
attr_reader :name
|
59
|
-
|
60
|
-
# Private: What is used to store the local cache.
|
61
|
-
attr_reader :local_cache
|
62
|
-
|
63
|
-
# Private: What is used to instrument all the things.
|
64
|
-
attr_reader :instrumenter
|
65
|
-
|
66
|
-
# Internal: Initializes a new adapter instance.
|
67
|
-
#
|
68
|
-
# adapter - Vanilla adapter instance to wrap. Just needs to respond to
|
69
|
-
# read, write, delete, set_members, set_add, and set_delete.
|
70
|
-
#
|
71
|
-
# options - The Hash of options.
|
72
|
-
# :local_cache - Where to store the local cache data (default: {}).
|
73
|
-
# Must respond to fetch(key, block), delete(key)
|
74
|
-
# and clear.
|
75
|
-
# :instrumenter - What to use to instrument all the things.
|
76
|
-
#
|
77
|
-
def initialize(adapter, options = {})
|
78
|
-
@adapter = adapter
|
79
|
-
@name = adapter.class.name.split('::').last.downcase.to_sym
|
80
|
-
@local_cache = options[:local_cache] || {}
|
81
|
-
@instrumenter = options.fetch(:instrumenter, Flipper::Instrumenters::Noop)
|
82
|
-
end
|
83
|
-
|
84
|
-
# Public: Turns local caching on/off.
|
85
|
-
#
|
86
|
-
# value - The Boolean that decides if local caching is on.
|
87
|
-
def use_local_cache=(value)
|
88
|
-
local_cache.clear
|
89
|
-
@use_local_cache = value
|
90
|
-
end
|
91
|
-
|
92
|
-
# Public: Returns true for using local cache, false for not.
|
93
|
-
def using_local_cache?
|
94
|
-
@use_local_cache == true
|
95
|
-
end
|
96
|
-
|
97
|
-
# Public: Reads a key.
|
98
|
-
def read(key)
|
99
|
-
perform_read(:read, key)
|
100
|
-
end
|
101
|
-
|
102
|
-
# Public: Set a key to a value.
|
103
|
-
def write(key, value)
|
104
|
-
perform_update(:write, key, value.to_s)
|
105
|
-
end
|
106
|
-
|
107
|
-
# Public: Deletes a key.
|
108
|
-
def delete(key)
|
109
|
-
perform_delete(:delete, key)
|
110
|
-
end
|
111
|
-
|
112
|
-
# Public: Returns the members of a set.
|
113
|
-
def set_members(key)
|
114
|
-
perform_read(:set_members, key)
|
115
|
-
end
|
116
|
-
|
117
|
-
# Public: Adds a value to a set.
|
118
|
-
def set_add(key, value)
|
119
|
-
perform_update(:set_add, key, value.to_s)
|
120
|
-
end
|
121
|
-
|
122
|
-
# Public: Deletes a value from a set.
|
123
|
-
def set_delete(key, value)
|
124
|
-
perform_update(:set_delete, key, value.to_s)
|
125
|
-
end
|
126
|
-
|
127
|
-
# Public: Determines equality for an adapter instance when compared to
|
128
|
-
# another object.
|
129
|
-
def eql?(other)
|
130
|
-
self.class.eql?(other.class) && adapter == other.adapter
|
131
|
-
end
|
132
|
-
alias_method :==, :eql?
|
133
|
-
|
134
|
-
# Public: Returns all the features that the adapter knows of.
|
135
|
-
def features
|
136
|
-
set_members(FeaturesKey)
|
137
|
-
end
|
138
|
-
|
139
|
-
# Internal: Adds a known feature to the set of features.
|
140
|
-
def feature_add(name)
|
141
|
-
set_add(FeaturesKey, name.to_s)
|
142
|
-
end
|
143
|
-
|
144
|
-
# Public: Pretty string version for debugging.
|
145
|
-
def inspect
|
146
|
-
attributes = [
|
147
|
-
"name=#{name.inspect}",
|
148
|
-
"use_local_cache=#{@use_local_cache.inspect}"
|
149
|
-
]
|
150
|
-
"#<#{self.class.name}:#{object_id} #{attributes.join(', ')}>"
|
151
|
-
end
|
152
|
-
|
153
|
-
# Private
|
154
|
-
def perform_read(operation, key)
|
155
|
-
if using_local_cache?
|
156
|
-
local_cache.fetch(key.to_s) {
|
157
|
-
local_cache[key.to_s] = @adapter.send(operation, key)
|
158
|
-
}
|
159
|
-
else
|
160
|
-
payload = {
|
161
|
-
:key => key,
|
162
|
-
:operation => operation,
|
163
|
-
:adapter_name => @name,
|
164
|
-
}
|
165
|
-
|
166
|
-
@instrumenter.instrument(InstrumentationName, payload) { |payload|
|
167
|
-
payload[:result] = @adapter.send(operation, key)
|
168
|
-
}
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
# Private
|
173
|
-
def perform_update(operation, key, value)
|
174
|
-
payload = {
|
175
|
-
:key => key,
|
176
|
-
:value => value,
|
177
|
-
:operation => operation,
|
178
|
-
:adapter_name => @name,
|
179
|
-
}
|
180
|
-
|
181
|
-
result = @instrumenter.instrument(InstrumentationName, payload) { |payload|
|
182
|
-
payload[:result] = @adapter.send(operation, key, value)
|
183
|
-
}
|
184
|
-
|
185
|
-
if using_local_cache?
|
186
|
-
local_cache.delete(key.to_s)
|
187
|
-
end
|
188
|
-
|
189
|
-
result
|
190
|
-
end
|
191
|
-
|
192
|
-
# Private
|
193
|
-
def perform_delete(operation, key)
|
194
|
-
payload = {
|
195
|
-
:key => key,
|
196
|
-
:operation => operation,
|
197
|
-
:adapter_name => @name,
|
198
|
-
}
|
199
|
-
|
200
|
-
result = @instrumenter.instrument(InstrumentationName, payload) { |payload|
|
201
|
-
payload[:result] = @adapter.send(operation, key)
|
202
|
-
}
|
203
|
-
|
204
|
-
if using_local_cache?
|
205
|
-
local_cache.delete(key.to_s)
|
206
|
-
end
|
207
|
-
|
208
|
-
result
|
209
|
-
end
|
2
|
+
module Adapter
|
3
|
+
# adding a module include so we have some hooks for stuff down the road
|
210
4
|
end
|
211
5
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'flipper/adapters/decorator'
|
2
|
+
require 'flipper/instrumenters/noop'
|
3
|
+
|
4
|
+
module Flipper
|
5
|
+
module Adapters
|
6
|
+
class Instrumented < Decorator
|
7
|
+
# Private: The name of instrumentation events.
|
8
|
+
InstrumentationName = "adapter_operation.#{InstrumentationNamespace}"
|
9
|
+
|
10
|
+
# Private: What is used to instrument all the things.
|
11
|
+
attr_reader :instrumenter
|
12
|
+
|
13
|
+
# Internal: Initializes a new adapter instance.
|
14
|
+
#
|
15
|
+
# adapter - Vanilla adapter instance to wrap.
|
16
|
+
#
|
17
|
+
# options - The Hash of options.
|
18
|
+
# :instrumenter - What to use to instrument all the things.
|
19
|
+
#
|
20
|
+
def initialize(adapter, options = {})
|
21
|
+
super(adapter)
|
22
|
+
@name = :instrumented
|
23
|
+
@instrumenter = options.fetch(:instrumenter, Flipper::Instrumenters::Noop)
|
24
|
+
end
|
25
|
+
|
26
|
+
def get(feature)
|
27
|
+
payload = {
|
28
|
+
:operation => :get,
|
29
|
+
:adapter_name => name,
|
30
|
+
:feature_name => feature.name,
|
31
|
+
}
|
32
|
+
|
33
|
+
@instrumenter.instrument(InstrumentationName, payload) { |payload|
|
34
|
+
payload[:result] = super
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Enable feature gate for thing.
|
39
|
+
def enable(feature, gate, thing)
|
40
|
+
payload = {
|
41
|
+
:operation => :enable,
|
42
|
+
:adapter_name => name,
|
43
|
+
:feature_name => feature.name,
|
44
|
+
:gate_name => gate.name,
|
45
|
+
}
|
46
|
+
|
47
|
+
@instrumenter.instrument(InstrumentationName, payload) { |payload|
|
48
|
+
payload[:result] = super
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
# Public: Disable feature gate for thing.
|
53
|
+
def disable(feature, gate, thing)
|
54
|
+
payload = {
|
55
|
+
:operation => :disable,
|
56
|
+
:adapter_name => name,
|
57
|
+
:feature_name => feature.name,
|
58
|
+
:gate_name => gate.name,
|
59
|
+
}
|
60
|
+
|
61
|
+
@instrumenter.instrument(InstrumentationName, payload) { |payload|
|
62
|
+
payload[:result] = super
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
# Public: Returns all the features that the adapter knows of.
|
67
|
+
def features
|
68
|
+
payload = {
|
69
|
+
:operation => :features,
|
70
|
+
:adapter_name => name,
|
71
|
+
}
|
72
|
+
|
73
|
+
@instrumenter.instrument(InstrumentationName, payload) { |payload|
|
74
|
+
payload[:result] = super
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
# Internal: Adds a known feature to the set of features.
|
79
|
+
def add(feature)
|
80
|
+
payload = {
|
81
|
+
:operation => :add,
|
82
|
+
:adapter_name => name,
|
83
|
+
:feature_name => feature.name,
|
84
|
+
}
|
85
|
+
|
86
|
+
@instrumenter.instrument(InstrumentationName, payload) { |payload|
|
87
|
+
payload[:result] = super
|
88
|
+
}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|