this_feature 0.3.0 → 0.4.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: 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: []