this_feature 0.3.0 → 0.4.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: b061ce7c8ed9133983dfc5435b15e70159f92226ecbdd2e96f3796e8e1bc6962
4
- data.tar.gz: 3edd88333694e493d2e3c93bca6d1266a5d17ac9539771e7f434ad2e6d062eb8
3
+ metadata.gz: ac249d0e2b607a1e24264851a2429160e9002634da26cb409f6d9b903e6a6157
4
+ data.tar.gz: 96464767635651ed17b057629e7bcdf2abd5dd6fc82fac20bfec75248d93fe55
5
5
  SHA512:
6
- metadata.gz: b6ece19b22abae62d53094df235f29236a55970a3dd43f6b36818d8c5666181c7cf82326fd5b84c276095e484d39144e91bc186f8f88cb89efd2b0f4008fddd0
7
- data.tar.gz: 1e2fd96239fb9c9f203475a95ed0c92ded460d35093ca97d69766ab72da8ed880868d135533cb034de867a1b2e85b04c5339eb1bf76ae357ec8a08df03143884
6
+ metadata.gz: f8725be53adf91f813519ac778a9e3558c5981d9a5908fc94578508b9f1baa2a607f9d47e4015159726dc18c4670b9f14f87ea844923936ce0f2bf7b114ef110
7
+ data.tar.gz: b3ced52a1340ddc0c8664d92300ca2c13154a75a5e7d1a5767e7fe4f6f1549bc8a8fcd107289252a5e6a25f915df5cf31a2b40f926ae791b608891afaf853b67
data/Gemfile CHANGED
@@ -2,3 +2,4 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec name: "this_feature"
4
4
  gemspec name: "this_feature-adapters-flipper"
5
+ gemspec name: "this_feature-adapters-split_io"
@@ -1,10 +1,14 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- this_feature (0.3.0)
5
- this_feature-adapters-flipper (0.3.0)
4
+ this_feature (0.4.0)
5
+ this_feature-adapters-flipper (0.4.0)
6
6
  flipper (~> 0.16)
7
7
  flipper-active_record (~> 0.16)
8
+ this_feature
9
+ this_feature-adapters-split_io (0.4.0)
10
+ splitclient-rb
11
+ this_feature
8
12
 
9
13
  GEM
10
14
  remote: https://rubygems.org/
@@ -23,20 +27,30 @@ GEM
23
27
  byebug (11.1.2)
24
28
  coderay (1.1.2)
25
29
  concurrent-ruby (1.1.6)
30
+ connection_pool (2.2.3)
26
31
  database_cleaner (1.8.4)
27
32
  database_cleaner-active_record (1.8.0)
28
33
  activerecord
29
34
  database_cleaner (~> 1.8.0)
30
35
  diff-lcs (1.3)
36
+ faraday (1.0.1)
37
+ multipart-post (>= 1.2, < 3)
31
38
  flipper (0.18.0)
32
39
  flipper-active_record (0.18.0)
33
40
  activerecord (>= 5.0, < 7)
34
41
  flipper (~> 0.18.0)
35
42
  gem-release (2.1.1)
43
+ hitimes (1.3.1)
36
44
  i18n (1.8.5)
37
45
  concurrent-ruby (~> 1.0)
46
+ json (2.3.1)
47
+ jwt (2.2.2)
48
+ lru_redux (1.1.0)
38
49
  method_source (1.0.0)
39
50
  minitest (5.14.1)
51
+ multipart-post (2.1.1)
52
+ net-http-persistent (4.0.0)
53
+ connection_pool (~> 2.2)
40
54
  pry (0.13.1)
41
55
  coderay (~> 1.1)
42
56
  method_source (~> 1.0)
@@ -44,6 +58,7 @@ GEM
44
58
  byebug (~> 11.0)
45
59
  pry (~> 0.13.0)
46
60
  rake (13.0.1)
61
+ redis (4.2.1)
47
62
  rspec (3.9.0)
48
63
  rspec-core (~> 3.9.0)
49
64
  rspec-expectations (~> 3.9.0)
@@ -57,6 +72,18 @@ GEM
57
72
  diff-lcs (>= 1.2.0, < 2.0)
58
73
  rspec-support (~> 3.9.0)
59
74
  rspec-support (3.9.2)
75
+ socketry (0.5.1)
76
+ hitimes (~> 1.2)
77
+ splitclient-rb (7.1.3)
78
+ concurrent-ruby (~> 1.0)
79
+ faraday (>= 0.8)
80
+ json (>= 1.8)
81
+ jwt (>= 2.2.1)
82
+ lru_redux
83
+ net-http-persistent (>= 2.9)
84
+ redis (>= 3.2)
85
+ socketry (~> 0.5.1)
86
+ thread_safe (>= 0.3)
60
87
  sqlite3 (1.4.2)
61
88
  thread_safe (0.3.6)
62
89
  tzinfo (1.2.7)
@@ -76,6 +103,7 @@ DEPENDENCIES
76
103
  sqlite3
77
104
  this_feature!
78
105
  this_feature-adapters-flipper!
106
+ this_feature-adapters-split_io!
79
107
 
80
108
  BUNDLED WITH
81
109
  2.1.4
data/README.md CHANGED
@@ -19,42 +19,68 @@ bundle
19
19
  Or install it yourself as:
20
20
 
21
21
  ```sh
22
- gem install feature
22
+ gem install this_feature
23
23
  ```
24
24
 
25
- ## Usage
25
+ ## Configuration
26
+
27
+ ```ruby
28
+ # config/initializers/this_feature.rb
29
+ require 'this_feature'
26
30
 
27
- ### Currently
31
+ ThisFeature.configure do |config|
32
+ config.adapters = [ThisFeature::Adapters::Memory]
33
+ config.default_adapter = config.adapters.first
34
+ end
35
+ ```
28
36
 
29
- Currently, the only available adapter is `Flipper`.
30
- We will update this document when more are added.
37
+ **NOTE**: When searching for the presence of a flag, adapters are queried in order. The default adapter is the fallback adapter used when a flag isn't present in any of the adapters.
31
38
 
32
- To set it up, put this in an initializer file:
39
+
40
+ ### With Flipper
33
41
 
34
42
  ```ruby
35
- ThisFeature.set_adapters([ThisFeature::Adapters::FlipperAdapter])
43
+ # config/initializers/this_feature.rb
44
+ require 'this_feature/adapters/flipper'
45
+
46
+ ThisFeature.configure do |config|
47
+ config.adapters = [ThisFeature::Adapters::Flipper]
48
+ config.default_adapter = config.adapters.first
49
+ end
36
50
  ```
37
51
 
38
- This `set_adapters` will internally call the `.setup` method on the `FlipperAdapter`, which performs the Flipper initialization.
39
52
 
40
- Then you can call `ThisFeature.enabled?("flag name")`.
41
53
 
42
- It will iterate through the adapters until one of them returns `true`/`false`.
54
+ ## Usage
55
+
56
+ ### Flags
57
+ ```ruby
58
+ ThisFeature.flag('flag_name').on? # check if flag is turned on
59
+ ThisFeature.flag('flag_name').off? # check if flag is turned off
60
+ ThisFeature.flag('flag_name').control? # see if the adapter is using the control
61
+ ThisFeature.flag('flag_name').on! # turn on the flag
62
+ ThisFeature.flag('flag_name').off! # turn off the flag
63
+ ```
43
64
 
44
- A context (`User` or `Org`) can be passed in the arguments to `enabled?` as well. `ThisFeature.enabled?(:flag_name, Current.user)`
65
+ ### Context
45
66
 
46
- ### Planned
67
+ You can also pass a context to the flag, many feature flagging systems support this.
47
68
 
48
- Create an initializer file in your Rails app:
69
+ ```ruby
70
+ ThisFeature.flag('flag_name', context: current_user).on?
71
+ ```
49
72
 
50
- `/config/initializers/this_feature.rb`
73
+ ### Data
51
74
 
52
- And set your list of adapters, _ordered by priority_. For example:
75
+ In case context is not sufficient, you can also pass a data hash.
53
76
 
54
77
  ```ruby
55
- ThisFeature.adapters = [SplitIO Flipper]
78
+ ThisFeature.flag('flag_name', context: context, data: { org_id: 1 }).on?
56
79
  ```
57
80
 
81
+ ## TODO: Write documentation for the adapters (creating adapters, using memory adapter, using flipper adapter)
82
+
83
+
58
84
  ## Development
59
85
 
60
86
  The tests are a good reflection of the current development state.
@@ -13,19 +13,19 @@ class ThisFeature
13
13
 
14
14
  def self.adapter_for(flag_name, context: nil, data: {})
15
15
  matching_adapter = adapters.find do |adapter|
16
- adapter.present?(flag_name, context: context, data: data)
16
+ adapter.present?(flag_name)
17
17
  end
18
18
 
19
19
  matching_adapter || configuration.default_adapter
20
20
  end
21
21
 
22
- # Configuration
23
-
24
22
  def self.configuration
25
23
  @configuration ||= Configuration.new
26
24
  end
27
25
 
28
26
  def self.configure
27
+ @configuration = Configuration.new
28
+
29
29
  yield(configuration)
30
30
 
31
31
  configuration.init
@@ -34,5 +34,4 @@ class ThisFeature
34
34
  def self.adapters
35
35
  configuration.adapters
36
36
  end
37
-
38
37
  end
@@ -2,33 +2,25 @@ class ThisFeature
2
2
  module Adapters
3
3
  class Base
4
4
 
5
- def self.setup
5
+ def setup
6
6
  raise UnimplementedError.new(self, __method__)
7
7
  end
8
8
 
9
- def self.present?(flag_name)
9
+ def present?(flag_name)
10
10
  raise UnimplementedError.new(self, __method__)
11
11
  end
12
12
 
13
- def self.on?(flag_name, context: nil, data: {})
13
+ def on?(flag_name, context: nil, data: {})
14
14
  raise UnimplementedError.new(self, __method__)
15
15
  end
16
16
 
17
- def self.off?(flag_name, context: nil, data: {})
18
- raise UnimplementedError.new(self, __method__)
19
- end
20
-
21
- def self.on!(flag_name, context: nil, data: {})
22
- raise UnimplementedError.new(self, __method__)
23
- end
24
-
25
- def self.off!(flag_name, context: nil, data: {})
17
+ def off?(flag_name, context: nil, data: {})
26
18
  raise UnimplementedError.new(self, __method__)
27
19
  end
28
20
 
29
21
  # OPTIONAL method
30
22
  # check to see if a control is being used
31
- def self.control?(flag_name, context: nil, data: {})
23
+ def control?(flag_name, context: nil, data: {})
32
24
  false
33
25
  end
34
26
  end
@@ -4,31 +4,27 @@ require "flipper/adapters/active_record"
4
4
  class ThisFeature
5
5
  module Adapters
6
6
  class Flipper < Base
7
+ attr_reader :client
7
8
 
8
- def self.setup(flipper = nil)
9
- return @flipper = flipper unless flipper.nil?
10
-
11
- @flipper = ::Flipper
9
+ def initialize(client: nil)
10
+ @client = client || default_flipper_adapter
11
+ end
12
12
 
13
- ::Flipper.configure do |config|
14
- config.default do
15
- adapter = ::Flipper::Adapters::ActiveRecord.new
16
- ::Flipper.new(adapter)
17
- end
18
- end
13
+ def present?(flag_name)
14
+ client[flag_name].exist?
19
15
  end
20
16
 
21
- def self.present?(flag_name)
22
- flipper[flag_name].exist?
17
+ def control?(flag_name, **kwargs)
18
+ !present?(flag_name)
23
19
  end
24
20
 
25
- def self.on?(flag_name, context: nil, data: {})
21
+ def on?(flag_name, context: nil, data: {})
26
22
  return unless present?(flag_name)
27
23
 
28
- flipper[flag_name].enabled?(*[context].compact)
24
+ client[flag_name].enabled?(*[context].compact)
29
25
  end
30
26
 
31
- def self.off?(flag_name, context: nil, data: {})
27
+ def off?(flag_name, context: nil, data: {})
32
28
  on_result = on?(flag_name, context: context)
33
29
 
34
30
  return if on_result.nil?
@@ -36,16 +32,16 @@ class ThisFeature
36
32
  !on_result
37
33
  end
38
34
 
39
- def self.on!(flag_name, context: nil, data: {})
40
- flipper[flag_name].enable(*[context].compact)
41
- end
35
+ private
42
36
 
43
- def self.off!(flag_name, context: nil, data: {})
44
- flipper[flag_name].disable(*[context].compact)
45
- end
46
-
47
- def self.flipper
48
- @flipper
37
+ def default_flipper_adapter
38
+ ::Flipper.configure do |config|
39
+ config.default do
40
+ adapter = ::Flipper::Adapters::ActiveRecord.new
41
+ ::Flipper.new(adapter)
42
+ end
43
+ end
44
+ ::Flipper
49
45
  end
50
46
  end
51
47
  end
@@ -2,20 +2,19 @@ class ThisFeature
2
2
  module Adapters
3
3
  class Memory < Base
4
4
 
5
- def self.setup(context_id_method: :id)
6
- @context_id_method = context_id_method
5
+ def initialize(context_key_method: nil)
6
+ @context_key_method = context_key_method
7
7
  end
8
8
 
9
- def self.clear
9
+ def clear
10
10
  storage.clear
11
11
  end
12
12
 
13
- def self.present?(flag_name)
13
+ def present?(flag_name)
14
14
  !storage[flag_name].nil?
15
15
  end
16
16
 
17
- def self.on?(flag_name, context: nil, data: {})
18
- # binding.pry
17
+ def on?(flag_name, context: nil, data: {})
19
18
  return unless present?(flag_name)
20
19
 
21
20
  flag_data = storage[flag_name]
@@ -25,10 +24,10 @@ class ThisFeature
25
24
 
26
25
  flag_data[:contexts] ||= {}
27
26
 
28
- !!flag_data[:contexts][context.send(@context_id_method)]
27
+ !!flag_data[:contexts][context_key(context)]
29
28
  end
30
29
 
31
- def self.off?(flag_name, context: nil, data: {})
30
+ def off?(flag_name, context: nil, data: {})
32
31
  on_result = on?(flag_name, context: context)
33
32
 
34
33
  return if on_result.nil?
@@ -36,27 +35,37 @@ class ThisFeature
36
35
  !on_result
37
36
  end
38
37
 
39
- def self.on!(flag_name, context: nil, data: {})
38
+ def on!(flag_name, context: nil, data: {})
40
39
  storage[flag_name] ||= {}
41
40
 
42
41
  return storage[flag_name][:global] = true if context.nil?
43
42
 
44
43
  storage[flag_name][:contexts] ||= {}
45
- storage[flag_name][:contexts][context.send(@context_id_method)] = true
44
+ storage[flag_name][:contexts][context_key(context)] = true
46
45
  end
47
46
 
48
- def self.off!(flag_name, context: nil, data: {})
47
+ def off!(flag_name, context: nil, data: {})
49
48
  storage[flag_name] ||= {}
50
49
 
51
50
  return storage[flag_name][:global] = false if context.nil?
52
51
 
53
52
  storage[flag_name][:contexts] ||= {}
54
- storage[flag_name][:contexts][context.send(@context_id_method)] = false
53
+ storage[flag_name][:contexts][context_key(context)] = false
55
54
  end
56
55
 
57
- def self.storage
56
+ def storage
58
57
  @storage ||= {}
59
58
  end
59
+
60
+ private
61
+
62
+ attr_reader :context_key_method
63
+
64
+ def context_key(context)
65
+ return context if context_key_method.nil?
66
+
67
+ context.send(context_key_method)
68
+ end
60
69
  end
61
70
  end
62
71
  end
@@ -0,0 +1,52 @@
1
+ require 'splitclient-rb'
2
+
3
+ class ThisFeature
4
+ module Adapters
5
+ class SplitIo < Base
6
+ # used as treatment key when none is given, it's required by split
7
+ UNDEFINED_KEY = 'undefined_key'
8
+
9
+ def initialize(client: nil)
10
+ @client = client || default_split_client
11
+
12
+ @client.block_until_ready
13
+ end
14
+
15
+ def present?(flag_name)
16
+ !control?(flag_name)
17
+ end
18
+
19
+ def control?(flag_name, context: UNDEFINED_KEY, data: {})
20
+ treatment(flag_name, context: context, data: data).eql?('control_treatment')
21
+ end
22
+
23
+ def on?(flag_name, context: UNDEFINED_KEY, data: {})
24
+ treatment(flag_name, context: context, data: data).eql?('on')
25
+ end
26
+
27
+ def off?(flag_name, context: UNDEFINED_KEY, data: {})
28
+ treatment(flag_name, context: context, data: data).eql?('off')
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :client
34
+
35
+ 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)
45
+ end
46
+
47
+ def default_split_client
48
+ SplitIoClient::SplitFactory.new(ENV.fetch('SPLIT_IO_AUTH_KEY')).client
49
+ end
50
+ end
51
+ end
52
+ end
@@ -5,13 +5,11 @@ class ThisFeature
5
5
 
6
6
  def init
7
7
  validate_adapters!
8
-
9
- adapters.each(&:setup)
10
8
  end
11
9
 
12
10
  def validate_adapters!
13
11
  adapters.each do |adapter|
14
- raise BadAdapterError.new(adapter) unless adapter < Adapters::Base
12
+ raise BadAdapterError.new(adapter) unless adapter.class < Adapters::Base
15
13
  end
16
14
  end
17
15
 
@@ -3,14 +3,14 @@ class ThisFeature
3
3
  class Error < StandardError; end
4
4
 
5
5
  class UnimplementedError < Error
6
- def initialize(klass, fn_name)
7
- super("class #{klass.name} doesnt implement method .#{fn_name}")
6
+ def initialize(adapter_instance, fn_name)
7
+ super("class #{adapter_instance.class.name} doesnt implement method .#{fn_name}")
8
8
  end
9
9
  end
10
10
 
11
11
  class BadAdapterError < Error
12
- def initialize(adapter)
13
- super("adapter #{adapter.name} doesn't inherit from ThisFeature::Adapters::Base")
12
+ def initialize(adapter_instance)
13
+ super("adapter #{adapter_instance.class.name} doesn't inherit from #{ThisFeature::Adapters::Base.name}")
14
14
  end
15
15
  end
16
16
 
@@ -20,13 +20,5 @@ class ThisFeature
20
20
  def control?
21
21
  adapter.control?(flag_name, context: context, data: data)
22
22
  end
23
-
24
- def on!
25
- adapter.on!(flag_name, context: context, data: data)
26
- end
27
-
28
- def off!
29
- adapter.off!(flag_name, context: context, data: data)
30
- end
31
23
  end
32
24
  end
@@ -1,3 +1,3 @@
1
1
  class ThisFeature
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -18,6 +18,7 @@ Gem::Specification.new do |s|
18
18
  s.platform = Gem::Platform::RUBY
19
19
  s.require_paths = ['lib']
20
20
 
21
+ s.add_runtime_dependency "this_feature"
21
22
  s.add_runtime_dependency "flipper", "~> 0.16"
22
23
  s.add_runtime_dependency "flipper-active_record", "~> 0.16"
23
24
  end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ $:.unshift File.expand_path('../lib', __FILE__)
4
+
5
+ require 'this_feature/version'
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = 'this_feature-adapters-split_io'
9
+ s.version = ThisFeature::VERSION
10
+ s.authors = ['Max Pleaner', 'Matt Fong']
11
+ s.email = ['maxpleaner@gmail.com', 'matthewjf@gmail.com']
12
+ s.homepage = 'http://hover.to'
13
+ s.licenses = ['MIT']
14
+ s.summary = '[summary]'
15
+ s.description = '[description]'
16
+
17
+ s.files = Dir.glob('{bin/*,lib/**/*,[A-Z]*}')
18
+ s.platform = Gem::Platform::RUBY
19
+ s.require_paths = ['lib']
20
+
21
+ s.add_runtime_dependency "this_feature"
22
+ s.add_runtime_dependency "splitclient-rb"
23
+ end
@@ -1,5 +1,7 @@
1
- lib = File.expand_path("lib", __dir__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
1
+ # encoding: utf-8
2
+
3
+ $:.unshift File.expand_path('../lib', __FILE__)
4
+
3
5
  require "this_feature/version"
4
6
 
5
7
  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.3.0
4
+ version: 0.4.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-08-26 00:00:00.000000000 Z
11
+ date: 2020-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -132,18 +132,20 @@ files:
132
132
  - lib/this_feature/adapters/base.rb
133
133
  - lib/this_feature/adapters/flipper.rb
134
134
  - lib/this_feature/adapters/memory.rb
135
+ - lib/this_feature/adapters/split_io.rb
135
136
  - lib/this_feature/configuration.rb
136
137
  - lib/this_feature/errors.rb
137
138
  - lib/this_feature/flag.rb
138
139
  - lib/this_feature/version.rb
139
140
  - this_feature-adapters-flipper.gemspec
141
+ - this_feature-adapters-split_io.gemspec
140
142
  - this_feature.gemspec
141
143
  homepage: https://github.com/hoverinc/this_feature
142
144
  licenses: []
143
145
  metadata:
144
146
  homepage_uri: https://github.com/hoverinc/this_feature
145
147
  source_code_uri: https://github.com/hoverinc/this_feature
146
- post_install_message:
148
+ post_install_message:
147
149
  rdoc_options: []
148
150
  require_paths:
149
151
  - lib
@@ -159,7 +161,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
159
161
  version: '0'
160
162
  requirements: []
161
163
  rubygems_version: 3.1.2
162
- signing_key:
164
+ signing_key:
163
165
  specification_version: 4
164
166
  summary: Feature flag control
165
167
  test_files: []