rollout 2.4.1 → 2.4.6

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
- 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 => "../"