eight_ball 1.0.0 → 2.2.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: 4e72251f479d9b7709d48d560535bb28b72692a7893e2de3d196967eb4915509
4
- data.tar.gz: 72721d1bb8447908b7241fa1545fe4d59073e2a951982f231de1e32bf11c6d85
3
+ metadata.gz: 521486fba75fc1996224ec260d22ed00860955da71941256452cb910adb3539d
4
+ data.tar.gz: 2c905caa109755024226a5ae088c9731ee7349930e423bf76a5d96de057e2b2c
5
5
  SHA512:
6
- metadata.gz: bc07aa302ce37b33da32b8943c4dfae7b7db392d4e028f148500ff95a667999c07cdf0fcd0bd09676e6349f9f20ebc1457d515b72e1dca27c72318e4fb1bc929
7
- data.tar.gz: e34699163e898d44ad6d7d832470785b9208383b08a089f5badec06b16bd55929d5bc1410ec1748921852ed1c465b84ad60b175ba66a26014a6b13829deb6657
6
+ metadata.gz: b9265bc7abf5060158f14ba9b42e9945a618fc4fb94c6f97b946228927fa37f8638fc5e9f014340aa4e03698edf00df39d24f3ef58bd2087dda0f15162ea59a3
7
+ data.tar.gz: c55f4e5fc008e92bc1b70a6c5d02efe5af600c59834aa35bbf850ccab0fd2d89c427ee16ca1931bc781009e4cc57b0ed6ac358f8632d81f55520d0aac0dccddc
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
@@ -3,11 +3,11 @@ language: ruby
3
3
  cache: bundler
4
4
  rvm:
5
5
  - 2.5.0
6
- before_install: gem install bundler -v 1.17.1
6
+ before_install: gem install bundler -v 1.17.2
7
7
  deploy:
8
8
  provider: rubygems
9
9
  api_key:
10
- secure: t7jNoz9Ct19jfGikW2lxK39aNPbvbFTwkhvVQ4ZlSAn+0iUWSmcTDFnh6MiSJFi9PEGvH3AUIrs6s7p6vhWUB8ll3S1UpC+34izUQZjUUHssJ+tf+A9q27OtX/O9PUsoOkHiq8/3F8SRT1OqyKb9+DR6i/M9MpHgWKLVEmXq3MXnGin1TeNJ70Hy2hakwbH/LdprrfT0LUohLZYzVFEodBPXRWeplJSAnJlyTrSIfIZV9e716L0F3HbXYY6pE5PJkFUSWwzAyO0Z73PJVV5+VsHt10AbHIJzPqf/qsLtC4K4SICWO7Xtnyuu9erg0HjG+y2D3AHbMklP/vJk4y1J2m6aJEzJ4VUVYfbVbkomPV0ol6alPmd6rCth8FQSZ5IFulwfp3qcsvSxS1BDhKl1yDC1di6n5MH9YNndy6puy6VTuuz4lqCya4WA1aBeummb9WsH2CNIws+rLneoH7PsMz6DCx9YhxbfZkh8h0WXRUgl86Ir1RbKbhR4+b3P1MFart55ipJ/tWMtyWOdJvjgakEyR/QMUEkWFMCOHuQd+Iv3PGT5God9YV6L01X/26BIfiHcTZTi2f5RDSyy6vIaOMROLKKU+heK3CGWvemA24uuJbyH9b3QujypCrR7wLRYvfGHeHunFbFH4HHZj+aedvtxzyJ004kNsia2y1TSw6c=
10
+ secure: fHXG5wBNcGRqpznLCCHfgwKEL1UXf0CNjou+HUdLJgLDwHaJro5TxM/c9z0Pnb9OJE0KIydVaIKh3iX0VtH1gS4m3ZztSpb5tnu4kkwAfdqG6QXVyubatxL/NQKTZSv6IzUjK/SC1eUePFMUu64Kx6voe8B1ZwoAGVf6L+atektcGb0PhZ24ogNFQaNUC3QJVPYyVKHLD8hZv6GVtxHE/xF6yiVhSZ4Mz2vqD4hDMQlkASnFzhhGvBk22aboc9Z0cBeYd2CRICJfyMyjUSmM2cFaeq1IsMJ6p2THoeeHYu8dIE7iiPMh2hMBD+ok+O+5f/8T3MpOdnpaLuq0u/ZkGBjg8oe4TCEQ67IZdIZnsRYEtwqJl//0Tkh9qW6ZZrq9ZHpd8VSXGbJv3eDutytlXNb0YdLXiRrBX72dGH9zheSQHIaBnc5gbSdaTcgqFacdeK3grx1L40SWTVrwBm+Dm88xvGGfPl+Y2sj0dBoPdeZqRfbrztSJivbbbR7XKosyOWQK9LqasPNP5EXjlnJRms6C/15m57RFs0oi/1p1TPAbaLqcp9Xvbwlft6HegCSram9xvd6R9alFgaizS1vLbESOJAQkADA9VN9ERApr38Eum8J6HF2xa/uXn4mHsLXzp5nkYqWb8LRqvxvzAnWL2p553upBTfP4m055jxPGwaM=
11
11
  gem: eight_ball
12
12
  on:
13
13
  tags: true
@@ -1,4 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.2.0]
4
+ - Add `==` to `Feature` and `Conditions`
5
+
6
+ ## [2.1.0]
7
+ - Add `features` parameter to `EightBall.marshall` to allow marshalling any Features, not just the ones
8
+ from the configured Provider.
9
+
10
+ ## [2.0.0]
11
+ - [BREAKING] `Parsers` have been replaced with `Marshallers`, allowing bi-directional conversions
12
+ - Added `EightBall.marshall` as a way to output the Feature list to an external format (e.g. to create a JSON file)
13
+ - Added `EightBall.features` as a shortcut to `EightBall.provider.features`
14
+ - Testing framework has been moved from Minitest to rspec
15
+ - Updated dev dependencies
16
+
17
+ ## [1.0.5]
18
+ Security: Update rake to >= 12.3.3
19
+
20
+ ## [1.0.3]
21
+ Update .travis.yml
22
+
23
+ ## [1.0.2]
24
+ Update .travis.yml
25
+
26
+ ## [1.0.1]
27
+ Security: Update yard 0.9.16 -> 0.9.20
28
+
3
29
  ## [1.0.0]
4
30
  Initial release!
data/Gemfile CHANGED
@@ -1,6 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
4
 
5
5
  # Specify your gem's dependencies in eight_ball.gemspec
6
6
  gemspec
@@ -1,57 +1,64 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- eight_ball (1.0.0)
4
+ eight_ball (2.2.0)
5
+ awrence (~> 1.1)
5
6
  plissken (~> 1.2)
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
9
10
  specs:
10
11
  ansi (1.5.0)
11
- builder (3.2.3)
12
- byebug (10.0.2)
13
- coderay (1.1.2)
14
- docile (1.3.1)
15
- hirb (0.7.3)
12
+ awrence (1.1.1)
13
+ byebug (11.1.3)
14
+ coderay (1.1.3)
15
+ diff-lcs (1.3)
16
+ docile (1.3.2)
16
17
  inch (0.8.0)
17
18
  pry
18
19
  sparkr (>= 0.2.0)
19
20
  term-ansicolor
20
21
  yard (~> 0.9.12)
21
- json (2.1.0)
22
- metaclass (0.0.4)
23
- method_source (0.9.2)
24
- minitest (5.11.3)
25
- minitest-reporters (1.3.5)
26
- ansi
27
- builder
28
- minitest (>= 5.0)
29
- ruby-progressbar
30
- mocha (1.7.0)
31
- metaclass (~> 0.0.1)
32
- plissken (1.2.0)
33
- pry (0.12.2)
34
- coderay (~> 1.1.0)
35
- method_source (~> 0.9.0)
36
- pry-byebug (3.6.0)
37
- byebug (~> 10.0)
38
- pry (~> 0.10)
39
- rake (10.5.0)
40
- ruby-progressbar (1.10.0)
41
- simplecov (0.16.1)
22
+ method_source (1.0.0)
23
+ plissken (1.3.1)
24
+ pry (0.13.1)
25
+ coderay (~> 1.1)
26
+ method_source (~> 1.0)
27
+ pry-byebug (3.9.0)
28
+ byebug (~> 11.0)
29
+ pry (~> 0.13.0)
30
+ rake (13.0.1)
31
+ rspec (3.9.0)
32
+ rspec-core (~> 3.9.0)
33
+ rspec-expectations (~> 3.9.0)
34
+ rspec-mocks (~> 3.9.0)
35
+ rspec-core (3.9.2)
36
+ rspec-support (~> 3.9.3)
37
+ rspec-expectations (3.9.2)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.9.0)
40
+ rspec-mocks (3.9.1)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.9.0)
43
+ rspec-support (3.9.3)
44
+ simplecov (0.18.5)
42
45
  docile (~> 1.1)
43
- json (>= 1.8, < 3)
44
- simplecov-html (~> 0.10.0)
45
- simplecov-console (0.4.2)
46
+ simplecov-html (~> 0.11)
47
+ simplecov-console (0.7.2)
46
48
  ansi
47
- hirb
48
49
  simplecov
49
- simplecov-html (0.10.2)
50
+ terminal-table
51
+ simplecov-html (0.12.2)
50
52
  sparkr (0.4.1)
51
- term-ansicolor (1.7.0)
53
+ sync (0.5.0)
54
+ term-ansicolor (1.7.1)
52
55
  tins (~> 1.0)
53
- tins (1.20.2)
54
- yard (0.9.16)
56
+ terminal-table (1.8.0)
57
+ unicode-display_width (~> 1.1, >= 1.1.1)
58
+ tins (1.25.0)
59
+ sync
60
+ unicode-display_width (1.7.0)
61
+ yard (0.9.25)
55
62
 
56
63
  PLATFORMS
57
64
  ruby
@@ -60,13 +67,11 @@ DEPENDENCIES
60
67
  bundler (~> 1.17)
61
68
  eight_ball!
62
69
  inch (~> 0.8)
63
- minitest (~> 5.0)
64
- minitest-reporters (~> 1.3)
65
- mocha (~> 1.7)
66
70
  pry-byebug (~> 3.6)
67
- rake (~> 10.0)
71
+ rake (~> 13.0)
72
+ rspec (~> 3.9.0)
68
73
  simplecov (~> 0.16)
69
74
  simplecov-console (~> 0.4)
70
75
 
71
76
  BUNDLED WITH
72
- 1.17.1
77
+ 1.17.2
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # EightBall
2
- [![Build Status](https://travis-ci.com/rewindio/eight_ball.svg?token=2toDwh2UkVcJs8RE5coA&branch=dev)](https://travis-ci.com/rewindio/eight_ball)
2
+ [![Build Status](https://travis-ci.org/rewindio/eight_ball.svg?branch=dev)](https://travis-ci.org/rewindio/eight_ball)
3
3
 
4
4
  EightBall is a feature toggle querying gem
5
5
 
@@ -42,8 +42,8 @@ json_input = %(
42
42
  )
43
43
 
44
44
  # Transform the JSON into a list of Features
45
- parser = EightBall::Parsers::Json.new
46
- features = parser.parse json_input
45
+ marshaller = EightBall::Marshallers::Json.new
46
+ features = marshaller.unmarshall json_input
47
47
 
48
48
  # Tell EightBall about these Features
49
49
  EightBall.provider = EightBall::Providers::Static.new features
@@ -76,7 +76,7 @@ A Condition must either be `true` or `false`. It describes when a Feature is ena
76
76
  A Provider is able to give EightBall the list of Features it needs to answer queries.
77
77
 
78
78
  **Supported Providers**
79
- - [HTTP](lib/eight_ball/providers/http.rb): Connect to a URL and use the given Parser to convert the response into a list of Features.
79
+ - [HTTP](lib/eight_ball/providers/http.rb): Connect to a URL and use the given Marshaller to convert the response into a list of Features.
80
80
  - [Static](lib/eight_ball/providers/static.rb): Once initialized with a list of Features, always provides that same list of Features.
81
81
 
82
82
  #### RefreshPolicies
@@ -85,15 +85,15 @@ Some Providers are able to automatically "refresh" their list of Features using
85
85
  **Supported RefreshPolicies**
86
86
  - [Interval](lib/eight_ball/providers/refresh_policies/interval.rb): The data is considered fresh for a given number of seconds, after which it is considered stale and should be refreshed.
87
87
 
88
- ### Parser
89
- A Parser converts the given input to an array of Features.
88
+ ### Marshallers
89
+ A Marshaller converts Features to and from another format.
90
90
 
91
- **Supported Parsers**
92
- - [JSON](lib/eight_ball/parsers/json.rb)
91
+ **Supported Marshaller**
92
+ - [JSON](lib/eight_ball/marshallers/json.rb)
93
93
 
94
94
  ## Development
95
95
 
96
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
96
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
97
97
 
98
98
  To install this gem onto your local machine, run `bundle exec rake install`.
99
99
 
data/Rakefile CHANGED
@@ -1,10 +1,9 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
1
+ require 'bundler/gem_tasks'
3
2
 
4
- Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList["test/**/*_test.rb"]
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ rescue LoadError
8
7
  end
9
8
 
10
- task :default => :test
9
+ task default: :spec
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- lib = File.expand_path('../lib', __FILE__)
3
+ lib = File.expand_path('lib', __dir__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require 'eight_ball/version'
6
6
 
@@ -15,22 +15,24 @@ Gem::Specification.new do |spec|
15
15
  spec.homepage = 'https://github.com/rewindio/eight_ball'
16
16
  spec.license = 'MIT'
17
17
 
18
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.required_ruby_version = '>= 2.5.0'
19
+
20
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec|.github|examples)/}) }
19
21
  spec.bindir = 'exe'
20
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
23
  spec.require_paths = ['lib']
22
24
 
23
25
  ### DEPENDENCIES
26
+
27
+ spec.add_dependency 'awrence', '~> 1.1'
24
28
  spec.add_dependency 'plissken', '~> 1.2'
25
29
 
26
30
  # Development
27
31
  spec.add_development_dependency 'bundler', '~> 1.17'
28
32
  spec.add_development_dependency 'inch', '~> 0.8'
29
- spec.add_development_dependency 'minitest', '~> 5.0'
30
- spec.add_development_dependency 'minitest-reporters', '~> 1.3'
31
- spec.add_development_dependency 'mocha', '~> 1.7'
32
33
  spec.add_development_dependency 'pry-byebug', '~> 3.6'
33
- spec.add_development_dependency 'rake', '~> 10.0'
34
+ spec.add_development_dependency 'rake', '~> 13.0'
35
+ spec.add_development_dependency 'rspec', '~> 3.9.0'
34
36
  spec.add_development_dependency 'simplecov', '~> 0.16'
35
37
  spec.add_development_dependency 'simplecov-console', '~> 0.4'
36
38
  end
@@ -3,6 +3,8 @@
3
3
  require 'eight_ball/version'
4
4
  require 'eight_ball/feature'
5
5
 
6
+ require 'eight_ball/configuration_error'
7
+
6
8
  require 'eight_ball/conditions/conditions'
7
9
  require 'eight_ball/conditions/base'
8
10
 
@@ -11,7 +13,7 @@ require 'eight_ball/conditions/list'
11
13
  require 'eight_ball/conditions/never'
12
14
  require 'eight_ball/conditions/range'
13
15
 
14
- require 'eight_ball/parsers/json'
16
+ require 'eight_ball/marshallers/json'
15
17
 
16
18
  require 'eight_ball/providers/http'
17
19
  require 'eight_ball/providers/static'
@@ -33,6 +35,24 @@ module EightBall
33
35
  @provider = provider
34
36
  end
35
37
 
38
+ # Gets the {EightBall::Providers Provider} instance
39
+ # EightBall is configured to use
40
+ #
41
+ # @return {EightBall::Providers Provider}
42
+ def self.provider
43
+ @provider
44
+ end
45
+
46
+ # Serves as a shortcut to access the {EightBall::Feature Features} available
47
+ # on the configured {EightBall::Providers Provider}
48
+ #
49
+ # @return [Array<EightBall::Feature>]
50
+ def self.features
51
+ raise EightBall::ConfigurationError, 'No Provider has been configured; there can be no features. Please see "EightBall.provider="' unless provider
52
+
53
+ provider.features
54
+ end
55
+
36
56
  # "EightBall, is the feature named 'NewFeature' enabled?"
37
57
  #
38
58
  # @return whether or not the {EightBall::Feature} is enabled.
@@ -45,7 +65,7 @@ module EightBall
45
65
  # @example
46
66
  # EightBall.enabled? 'feature1', account_id: 1
47
67
  def self.enabled?(name, parameters = {})
48
- feature = @provider.features.find { |f| f.name == name }
68
+ feature = provider.features.find { |f| f.name == name }
49
69
  return false unless feature
50
70
 
51
71
  feature.enabled? parameters
@@ -69,7 +89,7 @@ module EightBall
69
89
  # Yields to the given block of code if the {EightBall::Feature} is enabled.
70
90
  #
71
91
  # @return [nil] if block is yielded to
72
- # @return [false] if {EightBall::Feature} is disabled
92
+ # @return [false] if no block is given
73
93
  #
74
94
  # @param name [String] The name of the {EightBall::Feature}.
75
95
  # @param parameters [Hash] The parameters the {EightBall::Conditions} of this
@@ -85,9 +105,28 @@ module EightBall
85
105
  yield if enabled? name, parameters
86
106
  end
87
107
 
108
+ # Marshalls the {EightBall::Feature Features}. This can be useful for
109
+ # converting the data to, e.g., a JSON file.
110
+ #
111
+ # If a {EightBall::Marshallers Marshaller} is provided, use it.
112
+ #
113
+ # If no {EightBall::Marshallers Marshaller} is provided, uses the same
114
+ # Marshaller that the Provider is configured with.
115
+ #
116
+ # If the {EightBall::Providers Provider} does not expose a
117
+ # {EightBall::Marshallers Marshaller}, this will default to the
118
+ # {EightBall::Marshallers::Json JSON Marshaller}.
119
+ def self.marshall(marshaller = nil, features = EightBall.features)
120
+ marshaller ||=
121
+ (provider.respond_to?(:marshaller) && provider.marshaller) ||
122
+ EightBall::Marshallers::Json.new
123
+
124
+ marshaller.marshall features
125
+ end
126
+
88
127
  def self.logger
89
128
  @logger ||= Logger.new(STDOUT).tap do |log|
90
- log.progname = self.name
129
+ log.progname = name
91
130
  end
92
131
  end
93
132
 
@@ -4,7 +4,7 @@ module EightBall::Conditions
4
4
  class Base
5
5
  attr_reader :parameter
6
6
 
7
- def initialize(options = [])
7
+ def initialize(_options = [])
8
8
  @parameter = nil
9
9
  end
10
10
 
@@ -12,11 +12,25 @@ module EightBall::Conditions
12
12
  raise 'You can never satisfy the Base condition'
13
13
  end
14
14
 
15
+ def ==(other)
16
+ other.class == self.class && other.state == state
17
+ end
18
+ alias eql? ==
19
+
20
+ def hash
21
+ state.hash
22
+ end
23
+
15
24
  protected
16
25
 
26
+ def state
27
+ [@parameter]
28
+ end
29
+
17
30
  def parameter=(parameter)
18
31
  return if parameter.nil?
19
- @parameter = parameter.gsub(/(.)([A-Z])/,'\1_\2').downcase
32
+
33
+ @parameter = parameter.gsub(/(.)([A-Z])/, '\1_\2').downcase
20
34
  end
21
35
  end
22
36
  end
@@ -31,5 +31,11 @@ module EightBall::Conditions
31
31
  def satisfied?(value)
32
32
  values.include? value
33
33
  end
34
+
35
+ protected
36
+
37
+ def state
38
+ super + [@values.sort]
39
+ end
34
40
  end
35
41
  end
@@ -44,5 +44,11 @@ module EightBall::Conditions
44
44
  def satisfied?(value)
45
45
  value >= min && value <= max
46
46
  end
47
+
48
+ protected
49
+
50
+ def state
51
+ super + [@min, @max]
52
+ end
47
53
  end
48
54
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EightBall
4
+ class ConfigurationError < StandardError
5
+ def initialize(msg = 'An invalid configuration was detected')
6
+ super
7
+ end
8
+ end
9
+ end
@@ -48,6 +48,15 @@ module EightBall
48
48
  any_satisfied?(@enabled_for, parameters) && !any_satisfied?(@disabled_for, parameters)
49
49
  end
50
50
 
51
+ def ==(other)
52
+ name == other.name &&
53
+ enabled_for.size == other.enabled_for.size &&
54
+ enabled_for.all? { |condition| other.enabled_for.any? { |other_condition| condition == other_condition } } &&
55
+ disabled_for.size == other.disabled_for.size &&
56
+ disabled_for.all? { |condition| other.disabled_for.any? { |other_condition| condition == other_condition } }
57
+ end
58
+ alias eql? ==
59
+
51
60
  private
52
61
 
53
62
  def any_satisfied?(conditions, parameters)
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'awrence'
4
+ require 'json'
5
+ require 'plissken'
6
+
7
+ # A JSON marshaller can convert back and forth between JSON and a list of {EightBall::Feature Features}
8
+ # The JSON produced will be pretty-printed, as it is assumed the output will be written to a file.
9
+ #
10
+ # When converting from JSON, the top-level JSON element must be an array and
11
+ # its keys must use camel-case; this will be converted to snake-case by EightBall
12
+ # in order to adhere to both JSON and Ruby standards.
13
+ #
14
+ # Below are some examples of valid JSON:
15
+ #
16
+ # @example A single {EightBall::Feature} is enabled for accounts 1-5 as well as region Europe
17
+ # [{
18
+ # "name": "Feature1",
19
+ # "enabledFor": [{
20
+ # "type": "range",
21
+ # "parameter": "accountId",
22
+ # "min": 1,
23
+ # "max": 5
24
+ # }, {
25
+ # "type": "list",
26
+ # "parameter": "regionName",
27
+ # "values": ["Europe"]
28
+ # }]
29
+ # }]
30
+ #
31
+ # @example A single {EightBall::Feature} is disabled completely using the {EightBall::Conditions::Always Always} condition
32
+ # [{
33
+ # "name": "Feature1",
34
+ # "disabledFor": [{
35
+ # "type": "always"
36
+ # }]
37
+ # }]
38
+ module EightBall::Marshallers
39
+ class Json
40
+ # Convert the given {EightBall::Feature Features} into a JSON array.
41
+ #
42
+ # @param [Array<EightBall::Feature>] features The {EightBall::Feature Features} to convert.
43
+ # @return [String] The resulting JSON string.
44
+ #
45
+ # @example
46
+ # json_string = <Read from somewhere>
47
+ #
48
+ # marshaller = EightBall::Marshallers::Json.new
49
+ # marshaller.marshall [Array<EightBall::Feature>] => json
50
+ def marshall(features)
51
+ JSON.generate(features.map { |feature| feature_to_hash(feature).to_camelback_keys })
52
+ end
53
+
54
+ # Convert the given JSON into a list of {EightBall::Feature Features}.
55
+ #
56
+ # @param [String] json The JSON string to convert.
57
+ # @return [Array<EightBall::Feature>] The parsed {EightBall::Feature Features}
58
+ #
59
+ # @example
60
+ # json_string = <Read from somewhere>
61
+ #
62
+ # marshaller = EightBall::Marshallers::Json.new
63
+ # marshaller.unmarshall json_string => [Features]
64
+ def unmarshall(json)
65
+ parsed = JSON.parse(json, symbolize_names: true).to_snake_keys
66
+
67
+ raise ArgumentError, 'JSON input was not an array' unless parsed.is_a? Array
68
+
69
+ parsed.map do |feature|
70
+ enabled_for = create_conditions_from_json feature[:enabled_for]
71
+ disabled_for = create_conditions_from_json feature[:disabled_for]
72
+
73
+ EightBall::Feature.new feature[:name], enabled_for, disabled_for
74
+ end
75
+ rescue JSON::ParserError => e
76
+ EightBall.logger.error { "Failed to parse JSON: #{e.message}" }
77
+ []
78
+ end
79
+
80
+ private
81
+
82
+ def feature_to_hash(feature)
83
+ hash = {
84
+ name: feature.name
85
+ }
86
+
87
+ hash[:enabled_for] = feature.enabled_for.map { |condition| condition_to_hash(condition) } unless feature.enabled_for.empty?
88
+ hash[:disabled_for] = feature.disabled_for.map { |condition| condition_to_hash(condition) } unless feature.disabled_for.empty?
89
+
90
+ hash
91
+ end
92
+
93
+ def condition_to_hash(condition)
94
+ hash = {
95
+ type: condition.class.name.split('::').last.downcase
96
+ }
97
+ condition.instance_variables.each do |var|
98
+ next unless condition.instance_variable_get(var)
99
+
100
+ hash[var.to_s.delete('@')] = condition.instance_variable_get(var)
101
+ end
102
+
103
+ hash
104
+ end
105
+
106
+ def create_conditions_from_json(json_conditions)
107
+ return [] unless json_conditions&.is_a?(Array)
108
+
109
+ json_conditions.map do |condition|
110
+ condition_class = EightBall::Conditions.by_name condition[:type]
111
+ condition_class.new condition
112
+ end
113
+ end
114
+ end
115
+ end
@@ -1,22 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'net/http'
4
+
3
5
  module EightBall::Providers
4
6
  # An HTTP Provider will make a GET request to a given URI, and convert
5
7
  # the response into an array of {EightBall::Feature Features} using the
6
- # given {EightBall::Parsers Parser}.
8
+ # given {EightBall::Marshallers Marshaller}.
7
9
  #
8
10
  # The {EightBall::Feature Features} will be automatically kept up to date
9
11
  # according to the given {EightBall::Providers::RefreshPolicies RefreshPolicy}.
10
12
  class Http
11
13
  SUPPORTED_SCHEMES = %w[http https].freeze
12
14
 
15
+ attr_reader :marshaller
16
+
13
17
  # @param uri [String] The URI to GET the {EightBall::Feature Features} from.
14
18
  # @param options [Hash] The options to create the Provider with.
15
19
  #
16
- # @option options [EightBall::Parsers] :parser
17
- # The {EightBall::Parsers Parser} used to convert the response to an array
20
+ # @option options [EightBall::Marshallers] :marshaller
21
+ # The {EightBall::Marshallers Marshaller} used to convert the response to an array
18
22
  # of {EightBall::Feature Features}. Defaults to an instance of
19
- # {EightBall::Parsers::Json}
23
+ # {EightBall::Marshallers::Json}
20
24
  #
21
25
  # @option options [EightBall::Providers::RefreshPolicies] :refresh_policy
22
26
  # The {EightBall::Providers::RefreshPolicies Policy} used to determine
@@ -34,7 +38,7 @@ module EightBall::Providers
34
38
 
35
39
  @uri = URI.parse uri
36
40
 
37
- @parser = options[:parser] || EightBall::Parsers::Json.new
41
+ @marshaller = options[:marshaller] || EightBall::Marshallers::Json.new
38
42
  @policy = options[:refresh_policy] || EightBall::Providers::RefreshPolicies::Interval.new
39
43
  end
40
44
 
@@ -48,8 +52,8 @@ module EightBall::Providers
48
52
  private
49
53
 
50
54
  def fetch
51
- @features = @parser.parse Net::HTTP.get(@uri)
52
- rescue => e
55
+ @features = @marshaller.unmarshall Net::HTTP.get(@uri)
56
+ rescue StandardError => e
53
57
  EightBall.logger.error { "Failed to fetch data from #{@uri}: #{e.message}" }
54
58
  @features = []
55
59
  end
@@ -7,7 +7,7 @@ module EightBall::Providers::RefreshPolicies
7
7
  # amount of time, after which it is considered stale and should be refreshed.
8
8
  class Interval
9
9
  SECONDS_IN_A_DAY = 86_400
10
-
10
+
11
11
  # Creates a new instance of an Interval RefreshPolicy.
12
12
  #
13
13
  # @param seconds [Integer] The number of seconds the data is considered fresh.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EightBall
4
- VERSION = '1.0.0'
4
+ VERSION = '2.2.0'
5
5
  end
metadata CHANGED
@@ -1,127 +1,113 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eight_ball
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rewind.io
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-12-06 00:00:00.000000000 Z
11
+ date: 2020-06-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: plissken
14
+ name: awrence
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.2'
19
+ version: '1.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.2'
27
- - !ruby/object:Gem::Dependency
28
- name: bundler
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '1.17'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '1.17'
26
+ version: '1.1'
41
27
  - !ruby/object:Gem::Dependency
42
- name: inch
28
+ name: plissken
43
29
  requirement: !ruby/object:Gem::Requirement
44
30
  requirements:
45
31
  - - "~>"
46
32
  - !ruby/object:Gem::Version
47
- version: '0.8'
48
- type: :development
33
+ version: '1.2'
34
+ type: :runtime
49
35
  prerelease: false
50
36
  version_requirements: !ruby/object:Gem::Requirement
51
37
  requirements:
52
38
  - - "~>"
53
39
  - !ruby/object:Gem::Version
54
- version: '0.8'
40
+ version: '1.2'
55
41
  - !ruby/object:Gem::Dependency
56
- name: minitest
42
+ name: bundler
57
43
  requirement: !ruby/object:Gem::Requirement
58
44
  requirements:
59
45
  - - "~>"
60
46
  - !ruby/object:Gem::Version
61
- version: '5.0'
47
+ version: '1.17'
62
48
  type: :development
63
49
  prerelease: false
64
50
  version_requirements: !ruby/object:Gem::Requirement
65
51
  requirements:
66
52
  - - "~>"
67
53
  - !ruby/object:Gem::Version
68
- version: '5.0'
54
+ version: '1.17'
69
55
  - !ruby/object:Gem::Dependency
70
- name: minitest-reporters
56
+ name: inch
71
57
  requirement: !ruby/object:Gem::Requirement
72
58
  requirements:
73
59
  - - "~>"
74
60
  - !ruby/object:Gem::Version
75
- version: '1.3'
61
+ version: '0.8'
76
62
  type: :development
77
63
  prerelease: false
78
64
  version_requirements: !ruby/object:Gem::Requirement
79
65
  requirements:
80
66
  - - "~>"
81
67
  - !ruby/object:Gem::Version
82
- version: '1.3'
68
+ version: '0.8'
83
69
  - !ruby/object:Gem::Dependency
84
- name: mocha
70
+ name: pry-byebug
85
71
  requirement: !ruby/object:Gem::Requirement
86
72
  requirements:
87
73
  - - "~>"
88
74
  - !ruby/object:Gem::Version
89
- version: '1.7'
75
+ version: '3.6'
90
76
  type: :development
91
77
  prerelease: false
92
78
  version_requirements: !ruby/object:Gem::Requirement
93
79
  requirements:
94
80
  - - "~>"
95
81
  - !ruby/object:Gem::Version
96
- version: '1.7'
82
+ version: '3.6'
97
83
  - !ruby/object:Gem::Dependency
98
- name: pry-byebug
84
+ name: rake
99
85
  requirement: !ruby/object:Gem::Requirement
100
86
  requirements:
101
87
  - - "~>"
102
88
  - !ruby/object:Gem::Version
103
- version: '3.6'
89
+ version: '13.0'
104
90
  type: :development
105
91
  prerelease: false
106
92
  version_requirements: !ruby/object:Gem::Requirement
107
93
  requirements:
108
94
  - - "~>"
109
95
  - !ruby/object:Gem::Version
110
- version: '3.6'
96
+ version: '13.0'
111
97
  - !ruby/object:Gem::Dependency
112
- name: rake
98
+ name: rspec
113
99
  requirement: !ruby/object:Gem::Requirement
114
100
  requirements:
115
101
  - - "~>"
116
102
  - !ruby/object:Gem::Version
117
- version: '10.0'
103
+ version: 3.9.0
118
104
  type: :development
119
105
  prerelease: false
120
106
  version_requirements: !ruby/object:Gem::Requirement
121
107
  requirements:
122
108
  - - "~>"
123
109
  - !ruby/object:Gem::Version
124
- version: '10.0'
110
+ version: 3.9.0
125
111
  - !ruby/object:Gem::Dependency
126
112
  name: simplecov
127
113
  requirement: !ruby/object:Gem::Requirement
@@ -157,8 +143,8 @@ executables: []
157
143
  extensions: []
158
144
  extra_rdoc_files: []
159
145
  files:
160
- - ".github/CODEOWNERS"
161
146
  - ".gitignore"
147
+ - ".rspec"
162
148
  - ".travis.yml"
163
149
  - CHANGELOG.md
164
150
  - Gemfile
@@ -169,8 +155,6 @@ files:
169
155
  - bin/console
170
156
  - bin/setup
171
157
  - eight_ball.gemspec
172
- - examples/http_provider.rb
173
- - examples/static_provider.rb
174
158
  - lib/eight_ball.rb
175
159
  - lib/eight_ball/conditions/always.rb
176
160
  - lib/eight_ball/conditions/base.rb
@@ -178,8 +162,9 @@ files:
178
162
  - lib/eight_ball/conditions/list.rb
179
163
  - lib/eight_ball/conditions/never.rb
180
164
  - lib/eight_ball/conditions/range.rb
165
+ - lib/eight_ball/configuration_error.rb
181
166
  - lib/eight_ball/feature.rb
182
- - lib/eight_ball/parsers/json.rb
167
+ - lib/eight_ball/marshallers/json.rb
183
168
  - lib/eight_ball/providers/http.rb
184
169
  - lib/eight_ball/providers/refresh_policies/interval.rb
185
170
  - lib/eight_ball/providers/static.rb
@@ -196,7 +181,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
196
181
  requirements:
197
182
  - - ">="
198
183
  - !ruby/object:Gem::Version
199
- version: '0'
184
+ version: 2.5.0
200
185
  required_rubygems_version: !ruby/object:Gem::Requirement
201
186
  requirements:
202
187
  - - ">="
@@ -204,7 +189,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
204
189
  version: '0'
205
190
  requirements: []
206
191
  rubyforge_project:
207
- rubygems_version: 2.7.8
192
+ rubygems_version: 2.7.7
208
193
  signing_key:
209
194
  specification_version: 4
210
195
  summary: The most cost efficient way to flag features
@@ -1,7 +0,0 @@
1
- # Each line is a file pattern followed by one or more owners.
2
-
3
- # These owners will be the default owners for everything in
4
- # the repo. Unless a later match takes precedence, members of
5
- # @rewindio/codeowners will be requested for review when someone
6
- # opens a pull request.
7
- * @MarcMorinville-Rewind @jhuff078 @smandegar
@@ -1,8 +0,0 @@
1
- require 'eight_ball'
2
-
3
- # Tell EightBall about the Features
4
- EightBall.provider = EightBall::Providers::Http.new '<YOUR URL HERE>'
5
-
6
- # Away you go
7
- EightBall.enabled? 'Feature1', account_id: 4 # true
8
- EightBall.enabled? 'Feature1', account_id: 2 # false
@@ -1,13 +0,0 @@
1
- require 'eight_ball'
2
-
3
- # Create a Feature programatically
4
- enabled_for = EightBall::Conditions::Range.new min: 1, max: 10, parameter: 'account_id'
5
- disabled_for = EightBall::Conditions::List.new values: [2, 3]
6
- feature = EightBall::Feature.new 'Feature1', enabled_for, disabled_for
7
-
8
- # Tell EightBall about the Features
9
- EightBall.provider = EightBall::Providers::Static.new feature
10
-
11
- # Away you go
12
- EightBall.enabled? 'Feature1', account_id: 4 # true
13
- EightBall.enabled? 'Feature1', account_id: 2 # false
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'plissken'
4
-
5
- module EightBall::Parsers
6
- # A JSON parser will parse JSON into a list of {EightBall::Feature Features}.
7
- # The top-level JSON element must be an array and must use camel-case;
8
- # this will be converted to snake-case by EightBall.
9
- #
10
- # Below are some examples of valid JSON:
11
- #
12
- # @example A single {EightBall::Feature} is enabled for accounts 1-5 as well as region Europe
13
- # [{
14
- # "name": "Feature1",
15
- # "enabledFor": [{
16
- # "type": "range",
17
- # "parameter": "accountId",
18
- # "min": 1,
19
- # "max": 5
20
- # }, {
21
- # "type": "list",
22
- # "parameter": "regionName",
23
- # "values": ["Europe"]
24
- # }]
25
- # }]
26
- #
27
- # @example A single {EightBall::Feature} is disabled completely using the {EightBall::Conditions::Always Always} condition
28
- # [{
29
- # "name": "Feature1",
30
- # "disabledFor": [{
31
- # "type": "always"
32
- # }]
33
- # }]
34
- class Json
35
- # Convert the JSON into a list of {EightBall::Feature Features}.
36
- #
37
- # @param [String] json The JSON string to parse.
38
- # @return [Array<EightBall::Feature>] The parsed {EightBall::Feature Features}
39
- #
40
- # @example
41
- # json_string = <Read from somewhere>
42
- #
43
- # parser = EightBall::Parsers::Json.new
44
- # parser.parse json_string => [Features]
45
- def parse(json)
46
- parsed = JSON.parse(json, :symbolize_names => true).to_snake_keys
47
-
48
- raise ArgumentError, 'JSON input was not an array' unless parsed.is_a? Array
49
-
50
- parsed.map do |feature|
51
- enabled_for = create_conditions feature[:enabled_for]
52
- disabled_for = create_conditions feature[:disabled_for]
53
-
54
- EightBall::Feature.new feature[:name], enabled_for, disabled_for
55
- end
56
- rescue JSON::ParserError => e
57
- EightBall.logger.error { "Failed to parse JSON: #{e.message}" }
58
- []
59
- end
60
-
61
- private
62
-
63
- def create_conditions(json_conditions)
64
- return [] unless json_conditions && json_conditions.is_a?(Array)
65
-
66
- json_conditions.map do |condition|
67
- condition_class = EightBall::Conditions.by_name condition[:type]
68
- condition_class.new condition
69
- end
70
- end
71
- end
72
- end