rollout 2.4.1 → 2.4.6

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
- SHA1:
3
- metadata.gz: be3151ff1e21e7693a5b5be187e4db45cf4eb5d3
4
- data.tar.gz: 4bf397113ea44c2a522b4929c2030ca1aa756fab
2
+ SHA256:
3
+ metadata.gz: f3e6e9521ed03059b5b673616e985f89231d77d1ca42d8fa7ff00797ef906768
4
+ data.tar.gz: ac66f6f5f0da7e03bbf506d09f62d34e6afd7f1ea09b248b5193d74f3b6a720e
5
5
  SHA512:
6
- metadata.gz: 579ebe41258df73bf78477cba47da1529302c560e246f03a1bf4c652e93518a1de7e7053ec9665f3eefc94f04e21015b48a568dbdd1951d44ce23775aa967673
7
- data.tar.gz: 34fee387845b0cd8df9f1f593d900b8bb22de23acc2aee4777e99672a5caac1c78122ca020f032453e786d607bb7d905e65dbf807cbb8b796beafabba98fa6c2
6
+ metadata.gz: ccebf1c2024c332cd921a49b400a7ee8e9a5c0ce95cdf59f5e89fb302bef08684cc04ffae30c952e14ffd5acc97d583871ee590aebe6aa2c519cc416afc17b9f
7
+ data.tar.gz: fe0a400d1f694a6aefb5b1e7d89afb64a8cb28c07a1f510d01655d724bfb97d5b38e7c60e40669aec5fded5581e760549b53564078018cad92c968b6900753ee
@@ -0,0 +1,95 @@
1
+ version: 2.1
2
+
3
+ workflows:
4
+ main:
5
+ jobs:
6
+ - ruby27
7
+ - ruby26
8
+ - ruby25
9
+ - ruby24
10
+ - ruby23
11
+
12
+ executors:
13
+ ruby27:
14
+ docker:
15
+ - image: circleci/ruby:2.7
16
+ - image: circleci/redis:alpine
17
+ ruby26:
18
+ docker:
19
+ - image: circleci/ruby:2.6
20
+ - image: circleci/redis:alpine
21
+ ruby25:
22
+ docker:
23
+ - image: circleci/ruby:2.5
24
+ - image: circleci/redis:alpine
25
+ ruby24:
26
+ docker:
27
+ - image: circleci/ruby:2.4
28
+ - image: circleci/redis:alpine
29
+ ruby23:
30
+ docker:
31
+ - image: circleci/ruby:2.3
32
+ - image: circleci/redis:alpine
33
+
34
+ commands:
35
+ test:
36
+ steps:
37
+ - restore_cache:
38
+ keys:
39
+ - bundler-{{ checksum "Gemfile.lock" }}
40
+
41
+ - run:
42
+ name: Bundle Install
43
+ command: bundle check --path vendor/bundle || bundle install
44
+
45
+ - save_cache:
46
+ key: bundler-{{ checksum "Gemfile.lock" }}
47
+ paths:
48
+ - vendor/bundle
49
+
50
+ - run:
51
+ name: Run rspec
52
+ command: |
53
+ bundle exec rspec --format documentation --format RspecJunitFormatter --out test_results/rspec.xml
54
+
55
+ jobs:
56
+ ruby27:
57
+ executor: ruby27
58
+ steps:
59
+ - checkout
60
+ - test
61
+
62
+ - run:
63
+ name: Report Test Coverage
64
+ command: |
65
+ wget https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 -O cc-test-reporter
66
+ chmod +x cc-test-reporter
67
+ ./cc-test-reporter format-coverage -t simplecov -o coverage/codeclimate.json coverage/.resultset.json
68
+ ./cc-test-reporter upload-coverage -i coverage/codeclimate.json
69
+
70
+ - store_test_results:
71
+ path: test_results
72
+
73
+ ruby26:
74
+ executor: ruby26
75
+ steps:
76
+ - checkout
77
+ - test
78
+
79
+ ruby25:
80
+ executor: ruby25
81
+ steps:
82
+ - checkout
83
+ - test
84
+
85
+ ruby24:
86
+ executor: ruby24
87
+ steps:
88
+ - checkout
89
+ - test
90
+
91
+ ruby23:
92
+ executor: ruby23
93
+ steps:
94
+ - checkout
95
+ - test
data/.gitignore CHANGED
@@ -19,5 +19,6 @@ rdoc
19
19
  pkg
20
20
  *.gem
21
21
  gemfiles/*.lock
22
+ .rspec_status
22
23
 
23
24
  ## PROJECT::SPECIFIC
@@ -0,0 +1,11 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'spec/**/*'
4
+
5
+ Metrics/LineLength:
6
+ Max: 120
7
+ Metrics/MethodLength:
8
+ Max: 20
9
+
10
+ Style/TrailingCommaInArguments:
11
+ EnforcedStyleForMultiline: comma
@@ -4,13 +4,13 @@ sudo: false
4
4
  services:
5
5
  - redis-server
6
6
  rvm:
7
- - 2.4.1
8
- - 2.3.1
7
+ - 2.6
8
+ - 2.5
9
+ - 2.4
10
+ - 2.3
9
11
  - 2.2
10
12
  - 2.1
11
- - 2.0.0
12
- - 1.9.3
13
- - jruby-19mode
13
+ - 2.0
14
14
  env:
15
15
  - USE_REAL_REDIS=true
16
16
  gemfile:
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
2
4
 
3
5
  gemspec
data/README.md CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  Fast feature flags based on Redis.
4
4
 
5
- [![Build Status](https://travis-ci.org/fetlife/rollout.svg?branch=master)](https://travis-ci.org/fetlife/rollout)
6
- [![Code Climate](https://codeclimate.com/github/FetLife/rollout/badges/gpa.svg)](https://codeclimate.com/github/fetlife/rollout)
7
- [![Test Coverage](https://codeclimate.com/github/FetLife/rollout/badges/coverage.svg)](https://codeclimate.com/github/fetlife/rollout/coverage)
8
- [![Dependency Status](https://gemnasium.com/FetLife/rollout.svg)](https://gemnasium.com/fetlife/rollout)
5
+ [![Gem Version](https://badge.fury.io/rb/rollout.svg)](https://badge.fury.io/rb/rollout)
6
+ [![CircleCI](https://circleci.com/gh/fetlife/rollout.svg?style=svg)](https://circleci.com/gh/fetlife/rollout)
7
+ [![Code Climate](https://codeclimate.com/github/FetLife/rollout/badges/gpa.svg)](https://codeclimate.com/github/FetLife/rollout)
8
+ [![Test Coverage](https://codeclimate.com/github/FetLife/rollout/badges/coverage.svg)](https://codeclimate.com/github/FetLife/rollout/coverage)
9
9
 
10
10
  ## Install it
11
11
 
@@ -20,10 +20,18 @@ Initialize a rollout object. I assign it to a global var.
20
20
  ```ruby
21
21
  require 'redis'
22
22
 
23
- $redis = Redis.new
23
+ $redis = Redis.new
24
24
  $rollout = Rollout.new($redis)
25
25
  ```
26
26
 
27
+ or even simpler
28
+
29
+ ```ruby
30
+ require 'redis'
31
+ $rollout = Rollout.new(Redis.current) # Will use REDIS_URL env var or default redis url
32
+ ```
33
+
34
+
27
35
  Update data specific to a feature:
28
36
 
29
37
  ```ruby
@@ -71,6 +79,9 @@ Deactivate groups like this:
71
79
  $rollout.deactivate_group(:chat, :all)
72
80
  ```
73
81
 
82
+ Groups need to be defined every time your app starts. The logic is not persisted
83
+ anywhere.
84
+
74
85
  ## Specific Users
75
86
 
76
87
  You might want to let a specific user into a beta test or something. If that
@@ -98,7 +109,7 @@ $rollout.activate_percentage(:chat, 20)
98
109
  The algorithm for determining which users get let in is this:
99
110
 
100
111
  ```ruby
101
- CRC32(user.id) % 100_000 < percentage * 1_000
112
+ CRC32(user.id) < (2**32 - 1) / 100.0 * percentage
102
113
  ```
103
114
 
104
115
  So, for 20%, users 0, 1, 10, 11, 20, 21, etc would be allowed in. Those users
@@ -176,6 +187,7 @@ This example would use the "development:feature:chat:groups" key.
176
187
  * Python: https://github.com/asenchi/proclaim
177
188
  * PHP: https://github.com/opensoft/rollout
178
189
  * Clojure: https://github.com/yeller/shoutout
190
+ * Perl: https://metacpan.org/pod/Toggle
179
191
 
180
192
 
181
193
  ## Contributors
data/Rakefile CHANGED
@@ -1,9 +1,7 @@
1
- begin
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
3
2
 
4
- RSpec::Core::RakeTask.new(:spec)
3
+ require 'rspec/core/rake_task'
5
4
 
6
- task default: :spec
7
- rescue LoadError
8
- # no rspec available
9
- end
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task default: :spec
@@ -1,139 +1,18 @@
1
- require "rollout/version"
2
- require "zlib"
3
- require "set"
4
- require "json"
1
+ # frozen_string_literal: true
5
2
 
6
- class Rollout
7
- class Feature
8
- attr_accessor :groups, :users, :percentage, :data
9
- attr_reader :name, :options
10
-
11
- def initialize(name, string = nil, opts = {})
12
- @options = opts
13
- @name = name
14
-
15
- if string
16
- raw_percentage,raw_users,raw_groups,raw_data = string.split('|', 4)
17
- @percentage = raw_percentage.to_f
18
- @users = users_from_string(raw_users)
19
- @groups = groups_from_string(raw_groups)
20
- @data = raw_data.nil? || raw_data.strip.empty? ? {} : JSON.parse(raw_data)
21
- else
22
- clear
23
- end
24
- end
25
-
26
- def serialize
27
- "#{@percentage}|#{@users.to_a.join(",")}|#{@groups.to_a.join(",")}|#{serialize_data}"
28
- end
29
-
30
- def add_user(user)
31
- id = user_id(user)
32
- @users << id unless @users.include?(id)
33
- end
34
-
35
- def remove_user(user)
36
- @users.delete(user_id(user))
37
- end
38
-
39
- def add_group(group)
40
- @groups << group.to_sym unless @groups.include?(group.to_sym)
41
- end
42
-
43
- def remove_group(group)
44
- @groups.delete(group.to_sym)
45
- end
3
+ require 'rollout/feature'
4
+ require 'rollout/version'
5
+ require 'zlib'
6
+ require 'set'
7
+ require 'json'
46
8
 
47
- def clear
48
- @groups = groups_from_string("")
49
- @users = users_from_string("")
50
- @percentage = 0
51
- @data = {}
52
- end
53
-
54
- def active?(rollout, user)
55
- if user
56
- id = user_id(user)
57
- user_in_percentage?(id) ||
58
- user_in_active_users?(id) ||
59
- user_in_active_group?(user, rollout)
60
- else
61
- @percentage == 100
62
- end
63
- end
64
-
65
- def user_in_active_users?(user)
66
- @users.include?(user_id(user))
67
- end
68
-
69
- def to_hash
70
- {
71
- percentage: @percentage,
72
- groups: @groups,
73
- users: @users
74
- }
75
- end
76
-
77
- private
78
- def user_id(user)
79
- if user.is_a?(Integer) || user.is_a?(String)
80
- user.to_s
81
- else
82
- user.send(id_user_by).to_s
83
- end
84
- end
85
-
86
- def id_user_by
87
- @options[:id_user_by] || :id
88
- end
89
-
90
- def user_in_percentage?(user)
91
- Zlib.crc32(user_id_for_percentage(user)) % 100_000 < @percentage * 1_000
92
- end
93
-
94
- def user_id_for_percentage(user)
95
- if @options[:randomize_percentage]
96
- user_id(user).to_s + @name.to_s
97
- else
98
- user_id(user)
99
- end
100
- end
101
-
102
- def user_in_active_group?(user, rollout)
103
- @groups.any? do |g|
104
- rollout.active_in_group?(g, user)
105
- end
106
- end
107
-
108
- def serialize_data
109
- return "" unless @data.is_a? Hash
110
-
111
- @data.to_json
112
- end
113
-
114
- def users_from_string(raw_users)
115
- users = (raw_users || "").split(",").map(&:to_s)
116
- if @options[:use_sets]
117
- users.to_set
118
- else
119
- users
120
- end
121
- end
122
-
123
- def groups_from_string(raw_groups)
124
- groups = (raw_groups || "").split(",").map(&:to_sym)
125
- if @options[:use_sets]
126
- groups.to_set
127
- else
128
- groups
129
- end
130
- end
131
- end
9
+ class Rollout
10
+ RAND_BASE = (2**32 - 1) / 100.0
132
11
 
133
12
  def initialize(storage, opts = {})
134
13
  @storage = storage
135
14
  @options = opts
136
- @groups = { all: lambda { |user| true } }
15
+ @groups = { all: ->(_user) { true } }
137
16
  end
138
17
 
139
18
  def activate(feature)
@@ -143,15 +22,13 @@ class Rollout
143
22
  end
144
23
 
145
24
  def deactivate(feature)
146
- with_feature(feature) do |f|
147
- f.clear
148
- end
25
+ with_feature(feature, &:clear)
149
26
  end
150
27
 
151
28
  def delete(feature)
152
- features = (@storage.get(features_key) || "").split(",")
29
+ features = (@storage.get(features_key) || '').split(',')
153
30
  features.delete(feature.to_s)
154
- @storage.set(features_key, features.join(","))
31
+ @storage.set(features_key, features.join(','))
155
32
  @storage.del(key(feature))
156
33
  end
157
34
 
@@ -191,13 +68,20 @@ class Rollout
191
68
 
192
69
  def activate_users(feature, users)
193
70
  with_feature(feature) do |f|
194
- users.each{|user| f.add_user(user)}
71
+ users.each { |user| f.add_user(user) }
195
72
  end
196
73
  end
197
74
 
198
75
  def deactivate_users(feature, users)
199
76
  with_feature(feature) do |f|
200
- users.each{|user| f.remove_user(user)}
77
+ users.each { |user| f.remove_user(user) }
78
+ end
79
+ end
80
+
81
+ def set_users(feature, users)
82
+ with_feature(feature) do |f|
83
+ f.users = []
84
+ users.each { |user| f.add_user(user) }
201
85
  end
202
86
  end
203
87
 
@@ -233,7 +117,7 @@ class Rollout
233
117
 
234
118
  def active_in_group?(group, user)
235
119
  f = @groups[group.to_sym]
236
- f && f.call(user)
120
+ f&.call(user)
237
121
  end
238
122
 
239
123
  def get(feature)
@@ -254,35 +138,47 @@ class Rollout
254
138
  end
255
139
 
256
140
  def multi_get(*features)
257
- feature_keys = features.map{ |feature| key(feature) }
141
+ return [] if features.empty?
142
+
143
+ feature_keys = features.map { |feature| key(feature) }
258
144
  @storage.mget(*feature_keys).map.with_index { |string, index| Feature.new(features[index], string, @options) }
259
145
  end
260
146
 
261
147
  def features
262
- (@storage.get(features_key) || "").split(",").map(&:to_sym)
148
+ (@storage.get(features_key) || '').split(',').map(&:to_sym)
263
149
  end
264
150
 
265
151
  def feature_states(user = nil)
266
- features.each_with_object({}) do |f, hash|
267
- hash[f] = active?(f, user)
152
+ multi_get(*features).each_with_object({}) do |f, hash|
153
+ hash[f.name] = f.active?(self, user)
268
154
  end
269
155
  end
270
156
 
271
157
  def active_features(user = nil)
272
- features.select do |f|
273
- active?(f, user)
274
- end
158
+ multi_get(*features).select do |f|
159
+ f.active?(self, user)
160
+ end.map(&:name)
275
161
  end
276
162
 
277
163
  def clear!
278
164
  features.each do |feature|
279
- with_feature(feature) { |f| f.clear }
165
+ with_feature(feature, &:clear)
280
166
  @storage.del(key(feature))
281
167
  end
282
168
 
283
169
  @storage.del(features_key)
284
170
  end
285
171
 
172
+ def exists?(feature)
173
+ # since redis-rb v4.2, `#exists?` replaces `#exists` which now returns integer value instead of boolean
174
+ # https://github.com/redis/redis-rb/pull/918
175
+ if @storage.respond_to?(:exists?)
176
+ @storage.exists?(key(feature))
177
+ else
178
+ @storage.exists(key(feature))
179
+ end
180
+ end
181
+
286
182
  private
287
183
 
288
184
  def key(name)
@@ -290,7 +186,7 @@ class Rollout
290
186
  end
291
187
 
292
188
  def features_key
293
- "feature:__features__"
189
+ 'feature:__features__'
294
190
  end
295
191
 
296
192
  def with_feature(feature)
@@ -301,6 +197,6 @@ class Rollout
301
197
 
302
198
  def save(feature)
303
199
  @storage.set(key(feature.name), feature.serialize)
304
- @storage.set(features_key, (features | [feature.name.to_sym]).join(","))
200
+ @storage.set(features_key, (features | [feature.name.to_sym]).join(','))
305
201
  end
306
202
  end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Rollout
4
+ class Feature
5
+ attr_accessor :groups, :users, :percentage, :data
6
+ attr_reader :name, :options
7
+
8
+ def initialize(name, string = nil, opts = {})
9
+ @options = opts
10
+ @name = name
11
+
12
+ if string
13
+ raw_percentage, raw_users, raw_groups, raw_data = string.split('|', 4)
14
+ @percentage = raw_percentage.to_f
15
+ @users = users_from_string(raw_users)
16
+ @groups = groups_from_string(raw_groups)
17
+ @data = raw_data.nil? || raw_data.strip.empty? ? {} : JSON.parse(raw_data)
18
+ else
19
+ clear
20
+ end
21
+ end
22
+
23
+ def serialize
24
+ "#{@percentage}|#{@users.to_a.join(',')}|#{@groups.to_a.join(',')}|#{serialize_data}"
25
+ end
26
+
27
+ def add_user(user)
28
+ id = user_id(user)
29
+ @users << id unless @users.include?(id)
30
+ end
31
+
32
+ def remove_user(user)
33
+ @users.delete(user_id(user))
34
+ end
35
+
36
+ def add_group(group)
37
+ @groups << group.to_sym unless @groups.include?(group.to_sym)
38
+ end
39
+
40
+ def remove_group(group)
41
+ @groups.delete(group.to_sym)
42
+ end
43
+
44
+ def clear
45
+ @groups = groups_from_string('')
46
+ @users = users_from_string('')
47
+ @percentage = 0
48
+ @data = {}
49
+ end
50
+
51
+ def active?(rollout, user)
52
+ if user
53
+ id = user_id(user)
54
+ user_in_percentage?(id) ||
55
+ user_in_active_users?(id) ||
56
+ user_in_active_group?(user, rollout)
57
+ else
58
+ @percentage == 100
59
+ end
60
+ end
61
+
62
+ def user_in_active_users?(user)
63
+ @users.include?(user_id(user))
64
+ end
65
+
66
+ def to_hash
67
+ {
68
+ percentage: @percentage,
69
+ groups: @groups,
70
+ users: @users
71
+ }
72
+ end
73
+
74
+ private
75
+
76
+ def user_id(user)
77
+ if user.is_a?(Integer) || user.is_a?(String)
78
+ user.to_s
79
+ else
80
+ user.send(id_user_by).to_s
81
+ end
82
+ end
83
+
84
+ def id_user_by
85
+ @options[:id_user_by] || :id
86
+ end
87
+
88
+ def user_in_percentage?(user)
89
+ Zlib.crc32(user_id_for_percentage(user)) < RAND_BASE * @percentage
90
+ end
91
+
92
+ def user_id_for_percentage(user)
93
+ if @options[:randomize_percentage]
94
+ user_id(user).to_s + @name.to_s
95
+ else
96
+ user_id(user)
97
+ end
98
+ end
99
+
100
+ def user_in_active_group?(user, rollout)
101
+ @groups.any? do |g|
102
+ rollout.active_in_group?(g, user)
103
+ end
104
+ end
105
+
106
+ def serialize_data
107
+ return '' unless @data.is_a? Hash
108
+
109
+ @data.to_json
110
+ end
111
+
112
+ def users_from_string(raw_users)
113
+ users = (raw_users || '').split(',').map(&:to_s)
114
+ if @options[:use_sets]
115
+ users.to_set
116
+ else
117
+ users
118
+ end
119
+ end
120
+
121
+ def groups_from_string(raw_groups)
122
+ groups = (raw_groups || '').split(',').map(&:to_sym)
123
+ if @options[:use_sets]
124
+ groups.to_set
125
+ else
126
+ groups
127
+ end
128
+ end
129
+ end
130
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Rollout
2
- VERSION = "2.4.1"
4
+ VERSION = '2.4.6'
3
5
  end
@@ -1,27 +1,31 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "rollout/version"
1
+ # frozen_string_literal: true
4
2
 
5
- Gem::Specification.new do |s|
6
- s.name = "rollout"
7
- s.version = Rollout::VERSION
8
- s.authors = ["James Golick"]
9
- s.email = ["jamesgolick@gmail.com"]
10
- s.description = "Feature flippers with redis."
11
- s.summary = "Feature flippers with redis."
12
- s.homepage = "https://github.com/FetLife/rollout"
13
- s.license = "MIT"
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
4
+ require 'rollout/version'
14
5
 
15
- s.files = `git ls-files`.split("\n")
16
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
- s.require_paths = ["lib"]
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'rollout'
8
+ spec.version = Rollout::VERSION
9
+ spec.authors = ['James Golick']
10
+ spec.email = ['jamesgolick@gmail.com']
11
+ spec.description = 'Feature flippers with redis.'
12
+ spec.summary = 'Feature flippers with redis.'
13
+ spec.homepage = 'https://github.com/FetLife/rollout'
14
+ spec.license = 'MIT'
19
15
 
20
- s.add_development_dependency "rspec"
21
- s.add_development_dependency "appraisal"
22
- s.add_development_dependency "bundler", ">= 1.0.0"
23
- s.add_development_dependency "redis"
24
- s.add_development_dependency "fakeredis"
25
- s.add_development_dependency "simplecov"
26
- s.add_development_dependency "codeclimate-test-reporter"
16
+ spec.files = `git ls-files`.split("\n")
17
+ spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ spec.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.required_ruby_version = '>= 2.3'
22
+
23
+ spec.add_dependency 'redis', '~> 4.0'
24
+
25
+ spec.add_development_dependency 'bundler', '>= 1.17'
26
+ spec.add_development_dependency 'pry'
27
+ spec.add_development_dependency 'rspec', '~> 3.0'
28
+ spec.add_development_dependency 'rspec_junit_formatter', '~> 0.4'
29
+ spec.add_development_dependency 'rubocop', '~> 0.71'
30
+ spec.add_development_dependency 'simplecov', '0.17'
27
31
  end
@@ -2,8 +2,7 @@ require "spec_helper"
2
2
 
3
3
  RSpec.describe "Rollout" do
4
4
  before do
5
- @redis = Redis.new
6
- @rollout = Rollout.new(@redis)
5
+ @rollout = Rollout.new(Redis.current)
7
6
  end
8
7
 
9
8
  describe "when a group is activated" do
@@ -226,6 +225,16 @@ RSpec.describe "Rollout" do
226
225
  end
227
226
  end
228
227
 
228
+
229
+ describe 'set a group of users' do
230
+ it 'should replace the users with the given array' do
231
+ users = %w(1 2 3 4)
232
+ @rollout.activate_users(:chat, %w(10 20 30))
233
+ @rollout.set_users(:chat, users)
234
+ expect(@rollout.get(:chat).users).to eq(users)
235
+ end
236
+ end
237
+
229
238
  describe "activating a feature globally" do
230
239
  before do
231
240
  @rollout.activate(:chat)
@@ -595,6 +604,12 @@ RSpec.describe "Rollout" do
595
604
  expect(features[2].percentage).to eq 100
596
605
  expect(features.size).to eq 3
597
606
  end
607
+
608
+ describe 'when given feature keys is empty' do
609
+ it 'returns empty array' do
610
+ expect(@rollout.multi_get(*[])).to match_array([])
611
+ end
612
+ end
598
613
  end
599
614
 
600
615
  describe "#set_feature_data" do
@@ -646,6 +661,17 @@ RSpec.describe "Rollout" do
646
661
  expect(@rollout.get(:chat).data).to eq({})
647
662
  end
648
663
  end
664
+
665
+ describe 'Check if feature exists' do
666
+ it 'it should return true if the feature is exist' do
667
+ @rollout.activate_percentage(:chat, 1)
668
+ expect(@rollout.exists?(:chat)).to be true
669
+ end
670
+
671
+ it 'it should return false if the feature is not exist' do
672
+ expect(@rollout.exists?(:chat)).to be false
673
+ end
674
+ end
649
675
  end
650
676
 
651
677
  describe "Rollout::Feature" do
@@ -1,18 +1,27 @@
1
- $LOAD_PATH.unshift(File.dirname(__FILE__))
2
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
1
+ # frozen_string_literal: true
3
2
 
4
- require "simplecov"
5
- require "rspec"
6
- require ENV["USE_REAL_REDIS"] == "true" ? "redis" : "fakeredis"
3
+ require 'simplecov'
7
4
 
8
- SimpleCov.start do
9
- formatter SimpleCov::Formatter::MultiFormatter.new([
10
- SimpleCov::Formatter::HTMLFormatter,
11
- ])
12
- end
5
+ SimpleCov.start
6
+
7
+ require 'bundler/setup'
8
+ require 'redis'
9
+ require 'rollout'
13
10
 
14
- require "rollout"
11
+ Redis.current = Redis.new(
12
+ host: ENV.fetch('REDIS_HOST', '127.0.0.1'),
13
+ port: ENV.fetch('REDIS_PORT', '6379'),
14
+ db: ENV.fetch('REDIS_DB', '7'),
15
+ )
15
16
 
16
17
  RSpec.configure do |config|
17
- config.before { Redis.new.flushdb }
18
+ config.example_status_persistence_file_path = '.rspec_status'
19
+
20
+ # config.disable_monkey_patching!
21
+
22
+ config.expect_with :rspec do |c|
23
+ c.syntax = :expect
24
+ end
25
+
26
+ config.before { Redis.current.flushdb }
18
27
  end
metadata CHANGED
@@ -1,113 +1,113 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rollout
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.1
4
+ version: 2.4.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Golick
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-08 00:00:00.000000000 Z
11
+ date: 2020-06-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rspec
14
+ name: redis
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
19
+ version: '4.0'
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: '0'
26
+ version: '4.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: appraisal
28
+ name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '1.17'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '1.17'
41
41
  - !ruby/object:Gem::Dependency
42
- name: bundler
42
+ name: pry
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 1.0.0
47
+ version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: 1.0.0
54
+ version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: redis
56
+ name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: '3.0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: '3.0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: fakeredis
70
+ name: rspec_junit_formatter
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: '0.4'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ">="
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0'
82
+ version: '0.4'
83
83
  - !ruby/object:Gem::Dependency
84
- name: simplecov
84
+ name: rubocop
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ">="
87
+ - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0'
89
+ version: '0.71'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ">="
94
+ - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0'
96
+ version: '0.71'
97
97
  - !ruby/object:Gem::Dependency
98
- name: codeclimate-test-reporter
98
+ name: simplecov
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - ">="
101
+ - - '='
102
102
  - !ruby/object:Gem::Version
103
- version: '0'
103
+ version: '0.17'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - ">="
108
+ - - '='
109
109
  - !ruby/object:Gem::Version
110
- version: '0'
110
+ version: '0.17'
111
111
  description: Feature flippers with redis.
112
112
  email:
113
113
  - jamesgolick@gmail.com
@@ -115,19 +115,17 @@ executables: []
115
115
  extensions: []
116
116
  extra_rdoc_files: []
117
117
  files:
118
- - ".document"
118
+ - ".circleci/config.yml"
119
119
  - ".gitignore"
120
120
  - ".rspec"
121
+ - ".rubocop.yml"
121
122
  - ".travis.yml"
122
- - Appraisals
123
123
  - Gemfile
124
- - Gemfile.lock
125
124
  - LICENSE
126
125
  - README.md
127
126
  - Rakefile
128
- - gemfiles/redis_3.gemfile
129
- - gemfiles/redis_4.gemfile
130
127
  - lib/rollout.rb
128
+ - lib/rollout/feature.rb
131
129
  - lib/rollout/version.rb
132
130
  - rollout.gemspec
133
131
  - spec/rollout_spec.rb
@@ -144,15 +142,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
144
142
  requirements:
145
143
  - - ">="
146
144
  - !ruby/object:Gem::Version
147
- version: '0'
145
+ version: '2.3'
148
146
  required_rubygems_version: !ruby/object:Gem::Requirement
149
147
  requirements:
150
148
  - - ">="
151
149
  - !ruby/object:Gem::Version
152
150
  version: '0'
153
151
  requirements: []
154
- rubyforge_project:
155
- rubygems_version: 2.6.10
152
+ rubygems_version: 3.0.3
156
153
  signing_key:
157
154
  specification_version: 4
158
155
  summary: Feature flippers with redis.
data/.document DELETED
@@ -1,5 +0,0 @@
1
- README.rdoc
2
- lib/**/*.rb
3
- bin/*
4
- features/**/*.feature
5
- LICENSE
data/Appraisals DELETED
@@ -1,7 +0,0 @@
1
- appraise "redis-3" do
2
- gem "redis", "~> 3.3.2"
3
- end
4
-
5
- appraise "redis-4" do
6
- gem "redis", "4.0.0.rc1"
7
- end
@@ -1,56 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- rollout (2.4.0)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- appraisal (2.1.0)
10
- bundler
11
- rake
12
- thor (>= 0.14.0)
13
- codeclimate-test-reporter (1.0.5)
14
- simplecov
15
- diff-lcs (1.3)
16
- docile (1.1.5)
17
- fakeredis (0.6.0)
18
- redis (~> 3.2)
19
- json (2.0.3)
20
- rake (12.0.0)
21
- redis (3.3.3)
22
- rspec (3.5.0)
23
- rspec-core (~> 3.5.0)
24
- rspec-expectations (~> 3.5.0)
25
- rspec-mocks (~> 3.5.0)
26
- rspec-core (3.5.4)
27
- rspec-support (~> 3.5.0)
28
- rspec-expectations (3.5.0)
29
- diff-lcs (>= 1.2.0, < 2.0)
30
- rspec-support (~> 3.5.0)
31
- rspec-mocks (3.5.0)
32
- diff-lcs (>= 1.2.0, < 2.0)
33
- rspec-support (~> 3.5.0)
34
- rspec-support (3.5.0)
35
- simplecov (0.13.0)
36
- docile (~> 1.1.0)
37
- json (>= 1.8, < 3)
38
- simplecov-html (~> 0.10.0)
39
- simplecov-html (0.10.0)
40
- thor (0.19.4)
41
-
42
- PLATFORMS
43
- ruby
44
-
45
- DEPENDENCIES
46
- appraisal
47
- bundler (>= 1.0.0)
48
- codeclimate-test-reporter
49
- fakeredis
50
- redis
51
- rollout!
52
- rspec
53
- simplecov
54
-
55
- BUNDLED WITH
56
- 1.12.5
@@ -1,13 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- if RUBY_VERSION >= '2.0'
6
- gem "json"
7
- else
8
- gem "json", "~> 1.8.3"
9
- end
10
-
11
- gem "redis", "~> 3.3.2"
12
-
13
- gemspec :path => "../"
@@ -1,13 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- if RUBY_VERSION >= '2.0'
6
- gem "json"
7
- else
8
- gem "json", "~> 1.8.3"
9
- end
10
-
11
- gem "redis", "4.0.0.rc1"
12
-
13
- gemspec :path => "../"