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 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: []