this_feature 0.5.1 → 0.7.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +48 -32
- data/README.md +30 -6
- data/docs/memory.md +15 -13
- data/docs/writing_an_adapter.md +9 -1
- data/lib/this_feature/adapters/flipper.rb +1 -7
- data/lib/this_feature/adapters/memory.rb +9 -8
- data/lib/this_feature/adapters/split_io.rb +12 -11
- data/lib/this_feature/configuration.rb +2 -0
- data/lib/this_feature/errors.rb +2 -2
- data/lib/this_feature/version.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8812767db56e7467a7439bc73bd243e3829974f9ee8897b85e434a036d0cdff9
|
4
|
+
data.tar.gz: b27419213f0bb1954a7d9c221ce315ae2229f2bb27fee13ae92b8987371d7c24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc7fe3136ed720c6eca4aed7ee10a02faf9e3eca3f39302137f7b375e408bf7be7b6259e5fa6ab7b631d9c929fa6c562c25e697cc2ff061a0d204c982dc45a73
|
7
|
+
data.tar.gz: 6b7e73cead9d55c2c116c9fda04a61c789b2ab8c368d53535958f38f6fe8a7e1191842119e1c469d6d4459a55c4ad722dfdf2e24b9fb64ec95bcbcf5bc1ca36b
|
data/Gemfile.lock
CHANGED
@@ -1,55 +1,70 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
this_feature (0.
|
5
|
-
this_feature-adapters-flipper (0.
|
4
|
+
this_feature (0.7.0)
|
5
|
+
this_feature-adapters-flipper (0.7.0)
|
6
6
|
flipper (~> 0.16)
|
7
7
|
flipper-active_record (~> 0.16)
|
8
8
|
this_feature
|
9
|
-
this_feature-adapters-split_io (0.
|
9
|
+
this_feature-adapters-split_io (0.7.0)
|
10
10
|
splitclient-rb
|
11
11
|
this_feature
|
12
12
|
|
13
13
|
GEM
|
14
14
|
remote: https://rubygems.org/
|
15
15
|
specs:
|
16
|
-
activemodel (6.
|
17
|
-
activesupport (= 6.
|
18
|
-
activerecord (6.
|
19
|
-
activemodel (= 6.
|
20
|
-
activesupport (= 6.
|
21
|
-
activesupport (6.
|
16
|
+
activemodel (6.1.3)
|
17
|
+
activesupport (= 6.1.3)
|
18
|
+
activerecord (6.1.3)
|
19
|
+
activemodel (= 6.1.3)
|
20
|
+
activesupport (= 6.1.3)
|
21
|
+
activesupport (6.1.3)
|
22
22
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
23
|
-
i18n (>=
|
24
|
-
minitest (
|
25
|
-
tzinfo (~>
|
26
|
-
zeitwerk (~> 2.
|
23
|
+
i18n (>= 1.6, < 2)
|
24
|
+
minitest (>= 5.1)
|
25
|
+
tzinfo (~> 2.0)
|
26
|
+
zeitwerk (~> 2.3)
|
27
27
|
byebug (11.1.2)
|
28
28
|
coderay (1.1.2)
|
29
|
-
concurrent-ruby (1.1.
|
30
|
-
connection_pool (2.2.
|
29
|
+
concurrent-ruby (1.1.8)
|
30
|
+
connection_pool (2.2.5)
|
31
31
|
database_cleaner (1.8.4)
|
32
32
|
database_cleaner-active_record (1.8.0)
|
33
33
|
activerecord
|
34
34
|
database_cleaner (~> 1.8.0)
|
35
35
|
diff-lcs (1.3)
|
36
|
-
faraday (1.0
|
36
|
+
faraday (1.5.0)
|
37
|
+
faraday-em_http (~> 1.0)
|
38
|
+
faraday-em_synchrony (~> 1.0)
|
39
|
+
faraday-excon (~> 1.1)
|
40
|
+
faraday-httpclient (~> 1.0.1)
|
41
|
+
faraday-net_http (~> 1.0)
|
42
|
+
faraday-net_http_persistent (~> 1.1)
|
43
|
+
faraday-patron (~> 1.0)
|
37
44
|
multipart-post (>= 1.2, < 3)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
45
|
+
ruby2_keywords (>= 0.0.4)
|
46
|
+
faraday-em_http (1.0.0)
|
47
|
+
faraday-em_synchrony (1.0.0)
|
48
|
+
faraday-excon (1.1.0)
|
49
|
+
faraday-httpclient (1.0.1)
|
50
|
+
faraday-net_http (1.0.1)
|
51
|
+
faraday-net_http_persistent (1.1.0)
|
52
|
+
faraday-patron (1.0.0)
|
53
|
+
flipper (0.22.0)
|
54
|
+
flipper-active_record (0.22.0)
|
55
|
+
activerecord (>= 4.2, < 7)
|
56
|
+
flipper (~> 0.22.0)
|
57
|
+
gem-release (2.2.1)
|
43
58
|
hitimes (1.3.1)
|
44
|
-
i18n (1.8.
|
59
|
+
i18n (1.8.9)
|
45
60
|
concurrent-ruby (~> 1.0)
|
46
|
-
json (2.
|
47
|
-
jwt (2.2.
|
61
|
+
json (2.5.1)
|
62
|
+
jwt (2.2.3)
|
48
63
|
lru_redux (1.1.0)
|
49
64
|
method_source (1.0.0)
|
50
|
-
minitest (5.14.
|
65
|
+
minitest (5.14.4)
|
51
66
|
multipart-post (2.1.1)
|
52
|
-
net-http-persistent (4.0.
|
67
|
+
net-http-persistent (4.0.1)
|
53
68
|
connection_pool (~> 2.2)
|
54
69
|
pry (0.13.1)
|
55
70
|
coderay (~> 1.1)
|
@@ -58,7 +73,7 @@ GEM
|
|
58
73
|
byebug (~> 11.0)
|
59
74
|
pry (~> 0.13.0)
|
60
75
|
rake (13.0.1)
|
61
|
-
redis (4.
|
76
|
+
redis (4.3.1)
|
62
77
|
rspec (3.9.0)
|
63
78
|
rspec-core (~> 3.9.0)
|
64
79
|
rspec-expectations (~> 3.9.0)
|
@@ -72,23 +87,24 @@ GEM
|
|
72
87
|
diff-lcs (>= 1.2.0, < 2.0)
|
73
88
|
rspec-support (~> 3.9.0)
|
74
89
|
rspec-support (3.9.2)
|
90
|
+
ruby2_keywords (0.0.4)
|
75
91
|
socketry (0.5.1)
|
76
92
|
hitimes (~> 1.2)
|
77
|
-
splitclient-rb (7.
|
93
|
+
splitclient-rb (7.2.3)
|
78
94
|
concurrent-ruby (~> 1.0)
|
79
95
|
faraday (>= 0.8)
|
80
96
|
json (>= 1.8)
|
81
97
|
jwt (>= 2.2.1)
|
82
98
|
lru_redux
|
83
99
|
net-http-persistent (>= 2.9)
|
84
|
-
redis (>=
|
100
|
+
redis (>= 4.2.2)
|
85
101
|
socketry (~> 0.5.1)
|
86
102
|
thread_safe (>= 0.3)
|
87
103
|
sqlite3 (1.4.2)
|
88
104
|
thread_safe (0.3.6)
|
89
|
-
tzinfo (
|
90
|
-
|
91
|
-
zeitwerk (2.4.
|
105
|
+
tzinfo (2.0.4)
|
106
|
+
concurrent-ruby (~> 1.0)
|
107
|
+
zeitwerk (2.4.2)
|
92
108
|
|
93
109
|
PLATFORMS
|
94
110
|
ruby
|
data/README.md
CHANGED
@@ -2,17 +2,27 @@
|
|
2
2
|
|
3
3
|
**A common interface to interact with many feature flag providers.**
|
4
4
|
|
5
|
-
|
5
|
+
ThisFeature can be used to more easily migrate from one feature flag service to another
|
6
6
|
|
7
7
|
If your code uses ThisFeature,
|
8
|
-
then you can just swap out the adapter without
|
8
|
+
then you can just swap out the vendor adapter without needing to do a bunch of find-and-replace in your codebase
|
9
|
+
from one vendor's class/method signature to the another's.
|
9
10
|
|
10
11
|
## Installation
|
11
12
|
|
13
|
+
Add ThisFeature to your `Gemfile`:
|
14
|
+
|
12
15
|
```ruby
|
16
|
+
# Gemfile
|
13
17
|
gem 'this_feature'
|
14
18
|
```
|
15
19
|
|
20
|
+
Then from your Rails app's root directory:
|
21
|
+
|
22
|
+
```sh
|
23
|
+
bundle install
|
24
|
+
```
|
25
|
+
|
16
26
|
## Configuration
|
17
27
|
|
18
28
|
```ruby
|
@@ -36,14 +46,13 @@ end
|
|
36
46
|
```ruby
|
37
47
|
ThisFeature.flag('flag_name').on? # is the flag is turned on?
|
38
48
|
ThisFeature.flag('flag_name').off? # is the flag is turned off?
|
39
|
-
ThisFeature.flag('flag_name').control? # is the adapter
|
40
|
-
ThisFeature.flag('flag_name').present? # is the flag set at all?
|
49
|
+
ThisFeature.flag('flag_name').control? # is the adapter using the control?
|
41
50
|
ThisFeature.default_adapter # access the default adapter directly if needed
|
42
51
|
```
|
43
52
|
|
44
53
|
### Context
|
45
54
|
|
46
|
-
You can also pass a context to the flag, many feature flagging systems support this.
|
55
|
+
You can also pass a `context` to the flag, many feature flagging systems support this.
|
47
56
|
|
48
57
|
```ruby
|
49
58
|
ThisFeature.flag('flag_name', context: current_user).on?
|
@@ -51,12 +60,17 @@ ThisFeature.flag('flag_name', context: current_user).on?
|
|
51
60
|
|
52
61
|
### Data
|
53
62
|
|
54
|
-
In case context is not sufficient, you can also pass a data hash.
|
63
|
+
In case `context` is not sufficient, you can also pass a `data` hash.
|
55
64
|
|
56
65
|
```ruby
|
57
66
|
ThisFeature.flag('flag_name', context: context, data: { org_id: 1 }).on?
|
58
67
|
```
|
59
68
|
|
69
|
+
### Avoid Pitfalls
|
70
|
+
|
71
|
+
1. If your flag has context-specific rules (e.g. on for some orgs, off for others), make sure that the code does a context-specific check. `ThisFeature.flag("flag_name").on?` may return true, while `ThisFeature.flag("flag_name", context: Org.first).on?` would return false.
|
72
|
+
2. Related to the previous bullet point, if you are checking whether a flag is "globally enabled" (and thus may be removed from the codebase), do not just use `ThisFeature.flag("flag_name").on?`, it won't tell you the whole story. Go to the vendor console and check whether there are context-specific rules enabled.
|
73
|
+
|
60
74
|
## Available Adapters
|
61
75
|
|
62
76
|
These adapters do behave slightly differently, so make sure to read the following docs:
|
@@ -65,6 +79,16 @@ These adapters do behave slightly differently, so make sure to read the followin
|
|
65
79
|
- [Split.io adapter](./docs/splitio.md)
|
66
80
|
- [Memory adapter](./docs/memory.md) - **designed for use in tests**
|
67
81
|
|
82
|
+
### Needed Adapters
|
83
|
+
|
84
|
+
We'd like to add more adapters for more vendors.
|
85
|
+
If you're using a different backend and write your own adapter,
|
86
|
+
please submit a pull request to upstream that adaptor into this repo.
|
87
|
+
|
88
|
+
- Launch Darkly
|
89
|
+
- YAML files
|
90
|
+
- ...
|
91
|
+
|
68
92
|
## Development
|
69
93
|
|
70
94
|
The tests are a good reflection of the current development state.
|
data/docs/memory.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
Under the hood, the memory adapter stores data in a dictionary like so:
|
6
6
|
|
7
|
-
```
|
7
|
+
```ruby
|
8
8
|
{
|
9
9
|
some_flag_name: {
|
10
10
|
global: false,
|
@@ -41,7 +41,7 @@ ThisFeature.configure do |config|
|
|
41
41
|
end
|
42
42
|
```
|
43
43
|
|
44
|
-
The initializer takes an optional `context_key_method` argument. This is only relevant when using `context` -
|
44
|
+
The initializer takes an optional `context_key_method` argument. This is only relevant when using `context` -
|
45
45
|
it specifies a method name which should be called on the context object to determine its identity.
|
46
46
|
For example:
|
47
47
|
|
@@ -60,27 +60,29 @@ ThisFeature::Adapters::Memory.new(context_key_method: :this_feature_id)
|
|
60
60
|
|
61
61
|
If this option is ommitted, then the context object uses its `self` as its "identity".
|
62
62
|
|
63
|
+
**See below for example of how to use on! and off! from tests**
|
64
|
+
|
63
65
|
## API
|
64
66
|
|
65
67
|
The Memory adapter supports the public API of `ThisFeature`.
|
66
68
|
|
67
|
-
|
68
|
-
|
69
|
-
Read the following notes as well:
|
69
|
+
### **#on? / #off?**
|
70
70
|
|
71
|
-
|
71
|
+
If passed a `context` argument, uses the aformentioned logic
|
72
72
|
(`context_key_method`) to determine how it's handled.
|
73
73
|
|
74
|
-
- **control?** is not yet implemented
|
75
|
-
|
76
|
-
We also support two additional methods here that aren't present on the main adapter yet:
|
77
|
-
|
78
|
-
- **on!** / **off!**
|
79
|
-
|
80
74
|
Usage example of these:
|
81
75
|
|
82
|
-
```
|
76
|
+
```ruby
|
83
77
|
# If you have configured the in-memory adapter as the default
|
84
78
|
ThisFeature.test_adapter.on!(:flag_name, context: user) # with context
|
85
79
|
ThisFeature.test_adapter.off!(:flag_name) # without context
|
86
80
|
```
|
81
|
+
|
82
|
+
### **#clear**
|
83
|
+
|
84
|
+
Since the memory adapter stores flags in memory, it won't automatically get cleaned up in your tests. You can use this method to reset the memory adapter state.
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
ThisFeature.test_adapter.clear
|
88
|
+
```
|
data/docs/writing_an_adapter.md
CHANGED
@@ -1 +1,9 @@
|
|
1
|
-
|
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.
|
@@ -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
|
-
|
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,24 +15,24 @@ 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
|
|
22
|
-
|
23
|
-
return false if context.nil?
|
22
|
+
context_registered = flag_data[:contexts]&.key?(context_key(context))
|
24
23
|
|
25
|
-
flag_data[:
|
24
|
+
return !!flag_data[:global] if !context || (context && !context_registered)
|
26
25
|
|
26
|
+
flag_data[:contexts] ||= {}
|
27
27
|
!!flag_data[:contexts][context_key(context)]
|
28
28
|
end
|
29
29
|
|
30
30
|
def off?(flag_name, context: nil, data: {})
|
31
|
-
|
32
|
-
|
33
|
-
return if on_result.nil?
|
31
|
+
!on?(flag_name, context: context, data: data)
|
32
|
+
end
|
34
33
|
|
35
|
-
|
34
|
+
def control?(flag_name, **kwargs)
|
35
|
+
!present?(flag_name)
|
36
36
|
end
|
37
37
|
|
38
38
|
def on!(flag_name, context: nil, data: {})
|
@@ -62,6 +62,7 @@ class ThisFeature
|
|
62
62
|
attr_reader :context_key_method
|
63
63
|
|
64
64
|
def context_key(context)
|
65
|
+
return nil unless context
|
65
66
|
return context if context_key_method.nil?
|
66
67
|
|
67
68
|
context.send(context_key_method)
|
@@ -6,8 +6,9 @@ class ThisFeature
|
|
6
6
|
# used as treatment key when none is given, it's required by split
|
7
7
|
UNDEFINED_KEY = 'undefined_key'
|
8
8
|
|
9
|
-
def initialize(client: nil)
|
9
|
+
def initialize(client: nil, context_key_method: nil)
|
10
10
|
@client = client || default_split_client
|
11
|
+
@context_key_method = context_key_method
|
11
12
|
|
12
13
|
@client.block_until_ready
|
13
14
|
end
|
@@ -30,18 +31,18 @@ class ThisFeature
|
|
30
31
|
|
31
32
|
private
|
32
33
|
|
33
|
-
attr_reader :client
|
34
|
+
attr_reader :client, :context_key_method
|
34
35
|
|
35
36
|
def treatment(flag_name, context: UNDEFINED_KEY, data: {})
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
37
|
+
client.get_treatment(context_key(context), flag_name, data)
|
38
|
+
end
|
39
|
+
|
40
|
+
def context_key(context)
|
41
|
+
return UNDEFINED_KEY if context.nil? || context.eql?(UNDEFINED_KEY)
|
42
|
+
return context.send(context_key_method) unless context_key_method.nil?
|
43
|
+
return context.to_s if context.respond_to?(:to_s)
|
44
|
+
|
45
|
+
context
|
45
46
|
end
|
46
47
|
|
47
48
|
def default_split_client
|
data/lib/this_feature/errors.rb
CHANGED
@@ -14,9 +14,9 @@ class ThisFeature
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
class
|
17
|
+
class NoAdaptersError < Error
|
18
18
|
def initialize
|
19
|
-
super("
|
19
|
+
super("No adapters configured.")
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
data/lib/this_feature/version.rb
CHANGED
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
|
+
version: 0.7.0
|
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:
|
11
|
+
date: 2021-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -150,7 +150,7 @@ licenses: []
|
|
150
150
|
metadata:
|
151
151
|
homepage_uri: https://github.com/hoverinc/this_feature
|
152
152
|
source_code_uri: https://github.com/hoverinc/this_feature
|
153
|
-
post_install_message:
|
153
|
+
post_install_message:
|
154
154
|
rdoc_options: []
|
155
155
|
require_paths:
|
156
156
|
- lib
|
@@ -166,7 +166,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
166
166
|
version: '0'
|
167
167
|
requirements: []
|
168
168
|
rubygems_version: 3.0.3
|
169
|
-
signing_key:
|
169
|
+
signing_key:
|
170
170
|
specification_version: 4
|
171
171
|
summary: Feature flag control
|
172
172
|
test_files: []
|