rollout 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37e56080ba30fb08aaa7e718e22fae3cab0745e84a91537e4e5520c2843a63a2
4
- data.tar.gz: 2b2f8c5a6e7a3ddd8275d818b4a168e7573204c2e259e323ca7a2ea9996062d9
3
+ metadata.gz: 58914b07d5596f1db7ce88d65de6e5286519e4b527377b40b7c56ad3ea799323
4
+ data.tar.gz: 4200f92c9784c3d3f6d275a4308714c7b55d35154c97ef01a89a04166e69c041
5
5
  SHA512:
6
- metadata.gz: a31905463a16ca7413566460c9a693ab5b9063e74547c05aafe2cf40d5e8f8d83ecb9e85316dd48c3e6b5e5b63311ac222343b485228a695ff5b72f8b0d82a2c
7
- data.tar.gz: 33fc542bdfe0def2b13a1b3697ba59c1e5f83e813a7c67740d94bf0a9725e17003fdc3fecfa923874883730dde589e0906b5d49ab19ff49c36943e259c28f134
6
+ metadata.gz: 4e3fcddc89ff58fe0d652eed810767eba1a03a4a23312f9bceb174e63348b07fd47592a1504316c3e42169610f24fc78580b846b5649200159dcef6c6ffdbdb7
7
+ data.tar.gz: 02cc8771d302d5e0b33fb370d6a9ed063f5b7d05406f4ec57689f2196921e5ec4e1382b1a1fd175d7804e316f98538d0e2809e6ad7fe25847e06466dd12cafbc
data/.circleci/config.yml CHANGED
@@ -3,58 +3,64 @@ version: 2.1
3
3
  workflows:
4
4
  main:
5
5
  jobs:
6
+ - ruby33
7
+ - ruby31
8
+ - ruby32
9
+ - ruby30
6
10
  - ruby27
7
11
  - ruby26
8
12
  - ruby25
9
13
  - ruby24
10
- - ruby23
11
14
 
12
15
  executors:
16
+ ruby33:
17
+ docker:
18
+ - image: cimg/ruby:3.3
19
+ - image: cimg/redis:7.2
20
+ ruby32:
21
+ docker:
22
+ - image: cimg/ruby:3.2
23
+ - image: cimg/redis:7.2
24
+ ruby31:
25
+ docker:
26
+ - image: cimg/ruby:3.1
27
+ - image: cimg/redis:7.2
28
+ ruby30:
29
+ docker:
30
+ - image: cimg/ruby:3.0
31
+ - image: cimg/redis:7.2
13
32
  ruby27:
14
33
  docker:
15
- - image: circleci/ruby:2.7
16
- - image: circleci/redis:alpine
34
+ - image: cimg/ruby:2.7
35
+ - image: cimg/redis:7.2
17
36
  ruby26:
18
37
  docker:
19
- - image: circleci/ruby:2.6
20
- - image: circleci/redis:alpine
38
+ - image: cimg/ruby:2.7
39
+ - image: cimg/redis:7.2
21
40
  ruby25:
22
41
  docker:
23
- - image: circleci/ruby:2.5
24
- - image: circleci/redis:alpine
42
+ - image: cimg/ruby:2.7
43
+ - image: cimg/redis:7.2
25
44
  ruby24:
26
45
  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
46
+ - image: cimg/ruby:2.4
47
+ - image: cimg/redis:7.2
33
48
 
34
49
  commands:
35
50
  test:
36
51
  steps:
37
- - restore_cache:
38
- keys:
39
- - bundler-{{ checksum "Gemfile.lock" }}
40
-
41
52
  - run:
42
53
  name: Bundle Install
43
54
  command: bundle check --path vendor/bundle || bundle install
44
55
 
45
- - save_cache:
46
- key: bundler-{{ checksum "Gemfile.lock" }}
47
- paths:
48
- - vendor/bundle
49
-
50
56
  - run:
51
57
  name: Run rspec
52
58
  command: |
53
59
  bundle exec rspec --format documentation --format RspecJunitFormatter --out test_results/rspec.xml
54
60
 
55
61
  jobs:
56
- ruby27:
57
- executor: ruby27
62
+ ruby33:
63
+ executor: ruby33
58
64
  steps:
59
65
  - checkout
60
66
  - test
@@ -70,26 +76,44 @@ jobs:
70
76
  - store_test_results:
71
77
  path: test_results
72
78
 
73
- ruby26:
74
- executor: ruby26
79
+ ruby32:
80
+ executor: ruby30
75
81
  steps:
76
82
  - checkout
77
83
  - test
78
84
 
79
- ruby25:
80
- executor: ruby25
85
+ ruby31:
86
+ executor: ruby30
81
87
  steps:
82
88
  - checkout
83
89
  - test
84
90
 
85
- ruby24:
86
- executor: ruby24
91
+ ruby30:
92
+ executor: ruby30
87
93
  steps:
88
94
  - checkout
89
95
  - test
90
96
 
91
- ruby23:
92
- executor: ruby23
97
+ ruby27:
98
+ executor: ruby27
99
+ steps:
100
+ - checkout
101
+ - test
102
+
103
+ ruby26:
104
+ executor: ruby27
105
+ steps:
106
+ - checkout
107
+ - test
108
+
109
+ ruby25:
110
+ executor: ruby27
111
+ steps:
112
+ - checkout
113
+ - test
114
+
115
+ ruby24:
116
+ executor: ruby24
93
117
  steps:
94
118
  - checkout
95
119
  - test
data/README.md CHANGED
@@ -28,7 +28,7 @@ or even simpler
28
28
 
29
29
  ```ruby
30
30
  require 'redis'
31
- $rollout = Rollout.new(Redis.current) # Will use REDIS_URL env var or default redis url
31
+ $rollout = Rollout.new($redis) # Will use REDIS_URL env var or default redis url
32
32
  ```
33
33
 
34
34
 
@@ -161,6 +161,15 @@ deactivate them automatically when a threshold is reached to prevent service
161
161
  failures from cascading. See https://github.com/jamesgolick/degrade for the
162
162
  failure detection code.
163
163
 
164
+ ## Check Rollout Feature
165
+
166
+ You can inspect the state of your feature using:
167
+
168
+ ```ruby
169
+ >> $rollout.get(:chat)
170
+ => #<Rollout::Feature:0x00007f99fa4ec528 @data={}, @groups=[:caretakers], @name=:chat, @options={}, @percentage=0.05, @users=["1"]>
171
+ ```
172
+
164
173
  ## Namespacing
165
174
 
166
175
  Rollout separates its keys from other keys in the data store using the
@@ -180,6 +189,7 @@ This example would use the "development:feature:chat:groups" key.
180
189
 
181
190
  ## Frontend / UI
182
191
 
192
+ * [rollout-ui](https://github.com/fetlife/rollout-ui)
183
193
  * [Rollout-Dashboard](https://github.com/fiverr/rollout_dashboard/)
184
194
 
185
195
  ## Implementations in other languages
@@ -188,6 +198,7 @@ This example would use the "development:feature:chat:groups" key.
188
198
  * PHP: https://github.com/opensoft/rollout
189
199
  * Clojure: https://github.com/yeller/shoutout
190
200
  * Perl: https://metacpan.org/pod/Toggle
201
+ * Golang: https://github.com/SalesLoft/gorollout
191
202
 
192
203
 
193
204
  ## Contributors
@@ -5,12 +5,13 @@ class Rollout
5
5
  attr_accessor :groups, :users, :percentage, :data
6
6
  attr_reader :name, :options
7
7
 
8
- def initialize(name, string = nil, opts = {})
9
- @options = opts
10
- @name = name
8
+ def initialize(name, rollout:, state: nil, options: {})
9
+ @name = name
10
+ @rollout = rollout
11
+ @options = options
11
12
 
12
- if string
13
- raw_percentage, raw_users, raw_groups, raw_data = string.split('|', 4)
13
+ if state
14
+ raw_percentage, raw_users, raw_groups, raw_data = state.split('|', 4)
14
15
  @percentage = raw_percentage.to_f
15
16
  @users = users_from_string(raw_users)
16
17
  @groups = groups_from_string(raw_groups)
@@ -48,12 +49,12 @@ class Rollout
48
49
  @data = {}
49
50
  end
50
51
 
51
- def active?(rollout, user)
52
+ def active?(user)
52
53
  if user
53
54
  id = user_id(user)
54
55
  user_in_percentage?(id) ||
55
56
  user_in_active_users?(id) ||
56
- user_in_active_group?(user, rollout)
57
+ user_in_active_group?(user)
57
58
  else
58
59
  @percentage == 100
59
60
  end
@@ -72,6 +73,14 @@ class Rollout
72
73
  }
73
74
  end
74
75
 
76
+ def deep_clone
77
+ c = self.clone
78
+ c.instance_variable_set('@rollout', nil)
79
+ c = Marshal.load(Marshal.dump(c))
80
+ c.instance_variable_set('@rollot', @rollout)
81
+ c
82
+ end
83
+
75
84
  private
76
85
 
77
86
  def user_id(user)
@@ -98,9 +107,9 @@ class Rollout
98
107
  end
99
108
  end
100
109
 
101
- def user_in_active_group?(user, rollout)
110
+ def user_in_active_group?(user)
102
111
  @groups.any? do |g|
103
- rollout.active_in_group?(g, user)
112
+ @rollout.active_in_group?(g, user)
104
113
  end
105
114
  end
106
115
 
@@ -40,6 +40,7 @@ class Rollout
40
40
  name: @name,
41
41
  data: @data,
42
42
  context: @context,
43
+ created_at: @created_at,
43
44
  )
44
45
  end
45
46
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Rollout
4
- VERSION = '2.5.0'
4
+ VERSION = '2.6.0'
5
5
  end
data/lib/rollout.rb CHANGED
@@ -107,7 +107,7 @@ class Rollout
107
107
 
108
108
  def active?(feature, user = nil)
109
109
  feature = get(feature)
110
- feature.active?(self, user)
110
+ feature.active?(user)
111
111
  end
112
112
 
113
113
  def user_in_active_users?(feature, user = nil)
@@ -138,7 +138,7 @@ class Rollout
138
138
 
139
139
  def get(feature)
140
140
  string = @storage.get(key(feature))
141
- Feature.new(feature, string, @options)
141
+ Feature.new(feature, state: string, rollout: self, options: @options)
142
142
  end
143
143
 
144
144
  def set_feature_data(feature, data)
@@ -157,7 +157,13 @@ class Rollout
157
157
  return [] if features.empty?
158
158
 
159
159
  feature_keys = features.map { |feature| key(feature) }
160
- @storage.mget(*feature_keys).map.with_index { |string, index| Feature.new(features[index], string, @options) }
160
+
161
+ @storage
162
+ .mget(*feature_keys)
163
+ .map
164
+ .with_index do |string, index|
165
+ Feature.new(features[index], state: string, rollout: self, options: @options)
166
+ end
161
167
  end
162
168
 
163
169
  def features
@@ -166,13 +172,13 @@ class Rollout
166
172
 
167
173
  def feature_states(user = nil)
168
174
  multi_get(*features).each_with_object({}) do |f, hash|
169
- hash[f.name] = f.active?(self, user)
175
+ hash[f.name] = f.active?(user)
170
176
  end
171
177
  end
172
178
 
173
179
  def active_features(user = nil)
174
180
  multi_get(*features).select do |f|
175
- f.active?(self, user)
181
+ f.active?(user)
176
182
  end.map(&:name)
177
183
  end
178
184
 
@@ -199,7 +205,7 @@ class Rollout
199
205
  f = get(feature)
200
206
 
201
207
  if count_observers > 0
202
- before = Marshal.load(Marshal.dump(f))
208
+ before = f.deep_clone
203
209
  yield(f)
204
210
  save(f)
205
211
  changed
data/rollout.gemspec CHANGED
@@ -20,11 +20,11 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.required_ruby_version = '>= 2.3'
22
22
 
23
- spec.add_dependency 'redis', '~> 4.0'
23
+ spec.add_dependency 'redis', '>= 4.0', '< 6'
24
24
 
25
25
  spec.add_development_dependency 'bundler', '>= 1.17'
26
26
  spec.add_development_dependency 'pry'
27
- spec.add_development_dependency 'rspec', '~> 3.0'
27
+ spec.add_development_dependency 'rspec', '~> 3.13'
28
28
  spec.add_development_dependency 'rspec_junit_formatter', '~> 0.4'
29
29
  spec.add_development_dependency 'rubocop', '~> 0.71'
30
30
  spec.add_development_dependency 'simplecov', '0.17'
@@ -0,0 +1,54 @@
1
+ require "spec_helper"
2
+
3
+ describe "Rollout::Feature" do
4
+ let(:rollout) { Rollout.new($redis) }
5
+
6
+ describe "#add_user" do
7
+ it "ids a user using id_user_by" do
8
+ user = double("User", email: "test@test.com")
9
+ feature = Rollout::Feature.new(:chat, state: nil, rollout: rollout, options: { id_user_by: :email })
10
+ feature.add_user(user)
11
+ expect(user).to have_received :email
12
+ end
13
+ end
14
+
15
+ describe "#initialize" do
16
+ describe "when string does not exist" do
17
+ it 'clears feature attributes when string is not given' do
18
+ feature = Rollout::Feature.new(:chat, rollout: rollout)
19
+ expect(feature.groups).to be_empty
20
+ expect(feature.users).to be_empty
21
+ expect(feature.percentage).to eq 0
22
+ expect(feature.data).to eq({})
23
+ end
24
+
25
+ it 'clears feature attributes when string is nil' do
26
+ feature = Rollout::Feature.new(:chat, state: nil, rollout: rollout)
27
+ expect(feature.groups).to be_empty
28
+ expect(feature.users).to be_empty
29
+ expect(feature.percentage).to eq 0
30
+ expect(feature.data).to eq({})
31
+ end
32
+
33
+ it 'clears feature attributes when string is empty string' do
34
+ feature = Rollout::Feature.new(:chat, state: "", rollout: rollout)
35
+ expect(feature.groups).to be_empty
36
+ expect(feature.users).to be_empty
37
+ expect(feature.percentage).to eq 0
38
+ expect(feature.data).to eq({})
39
+ end
40
+
41
+ describe "when there is no data" do
42
+ it 'sets @data to empty hash' do
43
+ feature = Rollout::Feature.new(:chat, state: "0||", rollout: rollout)
44
+ expect(feature.data).to eq({})
45
+ end
46
+
47
+ it 'sets @data to empty hash' do
48
+ feature = Rollout::Feature.new(:chat, state: "||| ", rollout: rollout)
49
+ expect(feature.data).to eq({})
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe 'Rollout::Logging' do
4
- let(:rollout) { Rollout.new(Redis.current, logging: logging) }
4
+ let(:rollout) { Rollout.new($redis, logging: logging) }
5
5
  let(:logging) { true }
6
6
  let(:feature) { :foo }
7
7