this_feature 0.4.0 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,41 @@
1
+ # ThisFeature - Flipper Adapter
2
+
3
+ ## Installation
4
+
5
+ ```ruby
6
+ gem 'this_feature-adapters-flipper
7
+ ```
8
+
9
+ ## Configuration
10
+
11
+ ```ruby
12
+ # config/initializers/this_feature.rb
13
+ require 'this_feature'
14
+ require 'this_feature/adapters/flipper'
15
+
16
+ ThisFeature.configure do |config|
17
+ adapter = ThisFeature::Adapters::Flipper.new
18
+ config.adapters = [adapter]
19
+ config.default_adapter = adapter
20
+ end
21
+ ```
22
+
23
+ An existing Flipper client can be optionally passed to the initializer:
24
+
25
+ ```
26
+ ThisFeature::Adapters::Flipper.new(client: my_existing_client)
27
+ ```
28
+
29
+
30
+ ## API
31
+
32
+ The Flipper adapter supports the public API of `ThisFeature`.
33
+
34
+ The `context` argument is supported, but not `data`.
35
+
36
+ Read the following notes as well:
37
+
38
+ - **on?** / **off?**: Under the hood, calls `flipper_id` method on the `context`, if one was given.
39
+ - **control?** / **present?**: Flipper doesn't have a concept of "control", so we just implement it as `!present?`
40
+
41
+ It is possible to support `on!` and `off!` from Flipper but that's not implemented yet.
@@ -0,0 +1,88 @@
1
+ # ThisFeature - Memory Adapter
2
+
3
+ ## Synopsis
4
+
5
+ Under the hood, the memory adapter stores data in a dictionary like so:
6
+
7
+ ```json
8
+ {
9
+ some_flag_name: {
10
+ global: false,
11
+ contexts: {
12
+ User1: true,
13
+ User2: false
14
+ }
15
+ }
16
+ }
17
+ ```
18
+
19
+ Since it doesn't require actual DB lookups, it's faster, and works well for use
20
+ in test suites.
21
+
22
+ ## Installation
23
+
24
+ This adapter is included with the core gem:
25
+
26
+ ```ruby
27
+ gem 'this_feature
28
+ ```
29
+
30
+ ## Configuration
31
+
32
+ ```ruby
33
+ # config/initializers/this_feature.rb
34
+ require 'this_feature'
35
+ require 'this_feature/adapters/memory'
36
+
37
+ ThisFeature.configure do |config|
38
+ config.test_adapter = ThisFeature::Adapters::Memory.new
39
+ config.adapters = [config.test_adapter]
40
+ config.default_adapter = config.test_adapter
41
+ end
42
+ ```
43
+
44
+ The initializer takes an optional `context_key_method` argument. This is only relevant when using `context` -
45
+ it specifies a method name which should be called on the context object to determine its identity.
46
+ For example:
47
+
48
+ ```ruby
49
+ # Say you have this method which you want to use as the "identity"
50
+ # of a context object (e.g. imagine this module is included onto User)
51
+ module FeatureFlaggable
52
+ def this_feature_id
53
+ "#{self.class}-#{self.id}"
54
+ end
55
+ end
56
+
57
+ # Then you would refer to it like so in the initializer:
58
+ ThisFeature::Adapters::Memory.new(context_key_method: :this_feature_id)
59
+ ```
60
+
61
+ If this option is ommitted, then the context object uses its `self` as its "identity".
62
+
63
+ **See below for example of how to use on! and off! from tests**
64
+
65
+ ## API
66
+
67
+ The Memory adapter supports the public API of `ThisFeature`.
68
+
69
+ The `context` argument is supported, but not `data`.
70
+
71
+ Read the following notes as well:
72
+
73
+ - **on?** / **off?**: If passed a `context` argument, uses the aformentioned logic
74
+ (`context_key_method`) to determine how it's handled.
75
+
76
+ - **control?** is not yet implemented
77
+
78
+ We also support two additional methods here that aren't present on the main adapter yet:
79
+
80
+ - **on!** / **off!**
81
+
82
+ Usage example of these:
83
+
84
+ ```ruby
85
+ # If you have configured the in-memory adapter as the default
86
+ ThisFeature.test_adapter.on!(:flag_name, context: user) # with context
87
+ ThisFeature.test_adapter.off!(:flag_name) # without context
88
+ ```
@@ -0,0 +1,35 @@
1
+ # ThisFeature - Split Adapter
2
+
3
+ ## Installation
4
+
5
+ ```ruby
6
+ gem 'this_feature-adapters-split-io
7
+ ```
8
+
9
+ ## Configuration
10
+
11
+ ```ruby
12
+ # config/initializers/this_feature.rb
13
+ require 'this_feature'
14
+ require 'this_feature/adapters/split_io'
15
+
16
+ ThisFeature.configure do |config|
17
+ adapter = ThisFeature::Adapters::SplitIo.new
18
+ config.adapters = [adapter]
19
+ config.default_adapter = adapter
20
+ end
21
+ ```
22
+
23
+ An existing Split client can be optionally passed to the initializer:
24
+
25
+ ```
26
+ ThisFeature::Adapters::SplitIo.new(client: my_existing_client)
27
+ ```
28
+
29
+ ## API
30
+
31
+ The SplitIo adapter supports the public API of `ThisFeature`.
32
+
33
+ Both `context` and `data` are supported.
34
+
35
+ `control` is a native Split feature, so we perform a query to Split to get this info.
@@ -0,0 +1,9 @@
1
+ Look at [lib/this_feature/adapters/base.rb](../lib/this_feature/adapters/base.rb) to see the methods that your class should implement.
2
+
3
+ Make sure your class inherits from `ThisFeature::Adapters::Base` - this is a requirement.
4
+
5
+ You may define a custom `initialize` method - this isn't used by `this_feature` internals because we require an already-constructed instance to be passed into `ThisFeature.configure`.
6
+
7
+ For an example, look at one of the existing adapters: [lib/this_feature/adapters/](../lib/this_feature/adapters/)
8
+
9
+ If you want to include your adapter in our README, just open up a PR.
@@ -34,4 +34,8 @@ class ThisFeature
34
34
  def self.adapters
35
35
  configuration.adapters
36
36
  end
37
+
38
+ def self.test_adapter
39
+ configuration.test_adapter
40
+ end
37
41
  end
@@ -1,11 +1,6 @@
1
1
  class ThisFeature
2
2
  module Adapters
3
3
  class Base
4
-
5
- def setup
6
- raise UnimplementedError.new(self, __method__)
7
- end
8
-
9
4
  def present?(flag_name)
10
5
  raise UnimplementedError.new(self, __method__)
11
6
  end
@@ -19,17 +19,11 @@ class ThisFeature
19
19
  end
20
20
 
21
21
  def on?(flag_name, context: nil, data: {})
22
- return unless present?(flag_name)
23
-
24
22
  client[flag_name].enabled?(*[context].compact)
25
23
  end
26
24
 
27
25
  def off?(flag_name, context: nil, data: {})
28
- on_result = on?(flag_name, context: context)
29
-
30
- return if on_result.nil?
31
-
32
- !on_result
26
+ !on?(flag_name, context: context)
33
27
  end
34
28
 
35
29
  private
@@ -15,7 +15,7 @@ class ThisFeature
15
15
  end
16
16
 
17
17
  def on?(flag_name, context: nil, data: {})
18
- return unless present?(flag_name)
18
+ return false unless present?(flag_name)
19
19
 
20
20
  flag_data = storage[flag_name]
21
21
 
@@ -28,11 +28,7 @@ class ThisFeature
28
28
  end
29
29
 
30
30
  def off?(flag_name, context: nil, data: {})
31
- on_result = on?(flag_name, context: context)
32
-
33
- return if on_result.nil?
34
-
35
- !on_result
31
+ !on?(flag_name, context: context, data: data)
36
32
  end
37
33
 
38
34
  def on!(flag_name, context: nil, data: {})
@@ -17,7 +17,7 @@ class ThisFeature
17
17
  end
18
18
 
19
19
  def control?(flag_name, context: UNDEFINED_KEY, data: {})
20
- treatment(flag_name, context: context, data: data).eql?('control_treatment')
20
+ treatment(flag_name, context: context, data: data).include?('control')
21
21
  end
22
22
 
23
23
  def on?(flag_name, context: UNDEFINED_KEY, data: {})
@@ -1,13 +1,14 @@
1
-
2
1
  class ThisFeature
3
2
  class Configuration
4
- attr_writer :adapters, :default_adapter
3
+ attr_writer :adapters, :default_adapter, :test_adapter
5
4
 
6
5
  def init
7
6
  validate_adapters!
8
7
  end
9
8
 
10
9
  def validate_adapters!
10
+ raise(NoAdaptersError.new) unless adapters.any?
11
+
11
12
  adapters.each do |adapter|
12
13
  raise BadAdapterError.new(adapter) unless adapter.class < Adapters::Base
13
14
  end
@@ -20,5 +21,9 @@ class ThisFeature
20
21
  def default_adapter
21
22
  @default_adapter ||= adapters.first
22
23
  end
24
+
25
+ def test_adapter
26
+ @test_adapter ||= Adapters::Memory.new
27
+ end
23
28
  end
24
29
  end
@@ -14,9 +14,9 @@ class ThisFeature
14
14
  end
15
15
  end
16
16
 
17
- class NoWriteAdapter < Error
17
+ class NoAdaptersError < Error
18
18
  def initialize
19
- super("Use the `ThisFeature.write_adapter=` setter before calling #enable or #disable")
19
+ super("No adapters configured.")
20
20
  end
21
21
  end
22
22
 
@@ -1,3 +1,3 @@
1
1
  class ThisFeature
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.3"
3
3
  end
@@ -1,7 +1,5 @@
1
- # encoding: utf-8
2
-
3
- $:.unshift File.expand_path('../lib', __FILE__)
4
-
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
3
  require "this_feature/version"
6
4
 
7
5
  Gem::Specification.new do |spec|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: this_feature
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Max Pleaner
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-09-11 00:00:00.000000000 Z
11
+ date: 2020-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -127,6 +127,11 @@ files:
127
127
  - Rakefile
128
128
  - bin/console
129
129
  - bin/setup
130
+ - docs/flipper.html
131
+ - docs/flipper.md
132
+ - docs/memory.md
133
+ - docs/splitio.md
134
+ - docs/writing_an_adapter.md
130
135
  - lib/this_feature.rb
131
136
  - lib/this_feature/adapters.rb
132
137
  - lib/this_feature/adapters/base.rb
@@ -145,7 +150,7 @@ licenses: []
145
150
  metadata:
146
151
  homepage_uri: https://github.com/hoverinc/this_feature
147
152
  source_code_uri: https://github.com/hoverinc/this_feature
148
- post_install_message:
153
+ post_install_message:
149
154
  rdoc_options: []
150
155
  require_paths:
151
156
  - lib
@@ -160,8 +165,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
160
165
  - !ruby/object:Gem::Version
161
166
  version: '0'
162
167
  requirements: []
163
- rubygems_version: 3.1.2
164
- signing_key:
168
+ rubygems_version: 3.0.3
169
+ signing_key:
165
170
  specification_version: 4
166
171
  summary: Feature flag control
167
172
  test_files: []