this_feature 0.5.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b6fdd8f4258c9cca45bdf31d7370e100eb9175102ba565f3368f23a83edf8d46
4
- data.tar.gz: 79c1cbce63fad4511b3a5828fcc90cecb609db1abf2047c01270f7246c1631be
3
+ metadata.gz: 8812767db56e7467a7439bc73bd243e3829974f9ee8897b85e434a036d0cdff9
4
+ data.tar.gz: b27419213f0bb1954a7d9c221ce315ae2229f2bb27fee13ae92b8987371d7c24
5
5
  SHA512:
6
- metadata.gz: b8cb49d96981b3320399c856fcd5367e54fc478c9af60e4bfacbf6d2a54a990fa89db7344e8e1b342cbb2a764e57c7b6ca3f7dddec0247fa6f6521250f40f565
7
- data.tar.gz: b3804fb6eb9f37a12e2a8a477ed730ca83ed2cdf7d1a419c39b75ba1d9a4d9243051ee292dfdc59a5d68a2cb3a5d901a2606455916a6a0a22f2b73a5aab27c50
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.0)
5
- this_feature-adapters-flipper (0.5.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.5.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.0.3.2)
17
- activesupport (= 6.0.3.2)
18
- activerecord (6.0.3.2)
19
- activemodel (= 6.0.3.2)
20
- activesupport (= 6.0.3.2)
21
- activesupport (6.0.3.2)
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 (>= 0.7, < 2)
24
- minitest (~> 5.1)
25
- tzinfo (~> 1.1)
26
- zeitwerk (~> 2.2, >= 2.2.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.6)
30
- connection_pool (2.2.3)
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.1)
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
- flipper (0.19.0)
39
- flipper-active_record (0.19.0)
40
- activerecord (>= 5.0, < 7)
41
- flipper (~> 0.19.0)
42
- gem-release (2.1.1)
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.5)
59
+ i18n (1.8.9)
45
60
  concurrent-ruby (~> 1.0)
46
- json (2.3.1)
47
- jwt (2.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.1)
65
+ minitest (5.14.4)
51
66
  multipart-post (2.1.1)
52
- net-http-persistent (4.0.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.2.2)
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.1.3)
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 (>= 3.2)
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 (1.2.7)
90
- thread_safe (~> 0.1)
91
- zeitwerk (2.4.0)
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
- Can be used to more easily migrate among providers.
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 having to do a bunch of find-and-replace.
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 is using the control?
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
- The `context` argument is supported, but not `data`.
68
-
69
- Read the following notes as well:
69
+ ### **#on? / #off?**
70
70
 
71
- - **on?** / **off?**: If passed a `context` argument, uses the aformentioned logic
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
+ ```
@@ -1 +1,9 @@
1
- todo
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
- 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,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
- return true if flag_data[:global]
23
- return false if context.nil?
22
+ context_registered = flag_data[:contexts]&.key?(context_key(context))
24
23
 
25
- flag_data[:contexts] ||= {}
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
- on_result = on?(flag_name, context: context)
32
-
33
- return if on_result.nil?
31
+ !on?(flag_name, context: context, data: data)
32
+ end
34
33
 
35
- !on_result
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
- key = if context.nil?
37
- UNDEFINED_KEY
38
- elsif context.respond_to?(:to_s)
39
- context.to_s
40
- else
41
- context
42
- end
43
-
44
- client.get_treatment(key, flag_name, data)
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
@@ -7,6 +7,8 @@ class ThisFeature
7
7
  end
8
8
 
9
9
  def validate_adapters!
10
+ raise(NoAdaptersError.new) unless adapters.any?
11
+
10
12
  adapters.each do |adapter|
11
13
  raise BadAdapterError.new(adapter) unless adapter.class < Adapters::Base
12
14
  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.5.1"
2
+ VERSION = "0.7.0"
3
3
  end
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.5.1
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: 2020-10-02 00:00:00.000000000 Z
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: []