rollout 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f64f1c9012cf7fed3496317cec7542d606c1b7b7
4
- data.tar.gz: af8ead4306e97cd5038a16cc88b495e4dc68c9ca
3
+ metadata.gz: 531e50e9914dd1ea745969469ae49b805fe583b7
4
+ data.tar.gz: 2c25094aa9935d7ce97263f0829072bcb728dffc
5
5
  SHA512:
6
- metadata.gz: 6972dfe9065234da74ed4f260a8d73ca837828b83793fc7b12ea8327c45fc463af2ab89782ec79a0f8988a0fbcc7af7291e3c829452c8d373a27f72494ba7db7
7
- data.tar.gz: e82e2f6c194edd80142e88b54419c4d466910438a3b92ee243e2edbd71ea546178b72fedc34749c9ca114851e862221f2009f23e8adedd2fa65213736fbda5b6
6
+ metadata.gz: 87983bb4671e964ea46e5e7c85f98b4a9e9254707c2a4775c7a24998a2480dc0faa07a67acb0f90c2cdab8a84530ffd41417fca763cfd9c4308060179f8ece78
7
+ data.tar.gz: fa7c1d4f2c9fc0202d0272f0750cf0ea9087a6bb375e692631bc9c4b2ea3c6e52e649a601a0c2bcef4549e38d7fcd5d683fd08711805a77db757b86d6531bab3
@@ -4,7 +4,7 @@ sudo: false
4
4
  services:
5
5
  - redis-server
6
6
  rvm:
7
- - 2.3.0
7
+ - 2.3.1
8
8
  - 2.2
9
9
  - 2.1
10
10
  - 2.0.0
data/README.md CHANGED
@@ -10,7 +10,7 @@ Feature flippers.
10
10
  ## MAKE SURE TO READ THIS: 2.X Changes and Migration Path
11
11
 
12
12
  As of rollout-2.x, only one key is used per feature for performance reasons.
13
- The data format is `percentage|user_id,user_id,...|group,_group...`. This has
13
+ The serialized format is `percentage|user_id,user_id,...|group,_group...|data_json`. This has
14
14
  the effect of making concurrent feature modifications unsafe, but in practice,
15
15
  I doubt this will actually be a problem.
16
16
 
@@ -35,6 +35,12 @@ $redis = Redis.new
35
35
  $rollout = Rollout.new($redis)
36
36
  ```
37
37
 
38
+ Update data specific to a feature:
39
+
40
+ ```ruby
41
+ @rollout.set_feature_data(:chat, description: 'foo', release_date: 'bar', whatever: 'baz')
42
+ ```
43
+
38
44
  Check whether a feature is active for a particular user:
39
45
 
40
46
  ```ruby
@@ -103,7 +109,7 @@ $rollout.activate_percentage(:chat, 20)
103
109
  The algorithm for determining which users get let in is this:
104
110
 
105
111
  ```ruby
106
- CRC32(user.id) % 100 < percentage
112
+ CRC32(user.id) % 100_000 < percentage * 1_000
107
113
  ```
108
114
 
109
115
  So, for 20%, users 0, 1, 10, 11, 20, 21, etc would be allowed in. Those users
@@ -1,10 +1,11 @@
1
1
  require "rollout/version"
2
2
  require "zlib"
3
3
  require "set"
4
+ require "json"
4
5
 
5
6
  class Rollout
6
7
  class Feature
7
- attr_accessor :groups, :users, :percentage
8
+ attr_accessor :groups, :users, :percentage, :data
8
9
  attr_reader :name, :options
9
10
 
10
11
  def initialize(name, string = nil, opts = {})
@@ -12,17 +13,18 @@ class Rollout
12
13
  @name = name
13
14
 
14
15
  if string
15
- raw_percentage,raw_users,raw_groups = string.split("|")
16
+ raw_percentage,raw_users,raw_groups,raw_data = string.split('|', 4)
16
17
  @percentage = raw_percentage.to_f
17
- @users = (raw_users || "").split(",").map(&:to_s).to_set
18
- @groups = (raw_groups || "").split(",").map(&:to_sym).to_set
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)
19
21
  else
20
22
  clear
21
23
  end
22
24
  end
23
25
 
24
26
  def serialize
25
- "#{@percentage}|#{@users.to_a.join(",")}|#{@groups.to_a.join(",")}"
27
+ "#{@percentage}|#{@users.to_a.join(",")}|#{@groups.to_a.join(",")}|#{serialize_data}"
26
28
  end
27
29
 
28
30
  def add_user(user)
@@ -43,9 +45,10 @@ class Rollout
43
45
  end
44
46
 
45
47
  def clear
46
- @groups = Set.new
47
- @users = Set.new
48
+ @groups = groups_from_string("")
49
+ @users = users_from_string("")
48
50
  @percentage = 0
51
+ @data = {}
49
52
  end
50
53
 
51
54
  def active?(rollout, user)
@@ -101,6 +104,30 @@ class Rollout
101
104
  rollout.active_in_group?(g, user)
102
105
  end
103
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
104
131
  end
105
132
 
106
133
  def initialize(storage, opts = {})
@@ -122,8 +149,8 @@ class Rollout
122
149
  end
123
150
 
124
151
  def delete(feature)
125
- features = (@storage.get(features_key) || "").split(",").map(&:to_sym)
126
- features.delete(feature)
152
+ features = (@storage.get(features_key) || "").split(",")
153
+ features.delete(feature.to_s)
127
154
  @storage.set(features_key, features.join(","))
128
155
  @storage.del(key(feature))
129
156
  end
@@ -214,6 +241,23 @@ class Rollout
214
241
  Feature.new(feature, string, @options)
215
242
  end
216
243
 
244
+ def set_feature_data(feature, data)
245
+ with_feature(feature) do |f|
246
+ f.data.merge!(data) if data.is_a? Hash
247
+ end
248
+ end
249
+
250
+ def clear_feature_data(feature)
251
+ with_feature(feature) do |f|
252
+ f.data = {}
253
+ end
254
+ end
255
+
256
+ def multi_get(*features)
257
+ feature_keys = features.map{ |feature| key(feature) }
258
+ @storage.mget(*feature_keys).map.with_index { |string, index| Feature.new(features[index], string, @options) }
259
+ end
260
+
217
261
  def features
218
262
  (@storage.get(features_key) || "").split(",").map(&:to_sym)
219
263
  end
@@ -1,3 +1,3 @@
1
1
  class Rollout
2
- VERSION = "2.3.0"
2
+ VERSION = "2.4.0"
3
3
  end
@@ -51,6 +51,12 @@ RSpec.describe "Rollout" do
51
51
  end
52
52
 
53
53
  it "leaves the other groups active" do
54
+ expect(@rollout.get(:chat).groups).to eq [:fivesonly]
55
+ end
56
+
57
+ it "leaves the other groups active using sets" do
58
+ @options = @rollout.instance_variable_get("@options")
59
+ @options[:use_sets] = true
54
60
  expect(@rollout.get(:chat).groups).to eq [:fivesonly].to_set
55
61
  end
56
62
  end
@@ -169,6 +175,15 @@ RSpec.describe "Rollout" do
169
175
  end
170
176
 
171
177
  it "remains active for other active users" do
178
+ @options = @rollout.instance_variable_get("@options")
179
+ @options[:use_sets] = false
180
+ expect(@rollout.get(:chat).users).to eq %w(24)
181
+ end
182
+
183
+ it "remains active for other active users using sets" do
184
+ @options = @rollout.instance_variable_get("@options")
185
+ @options[:use_sets] = true
186
+
172
187
  expect(@rollout.get(:chat).users).to eq %w(24).to_set
173
188
  end
174
189
  end
@@ -219,6 +234,10 @@ RSpec.describe "Rollout" do
219
234
  it "activates the feature" do
220
235
  expect(@rollout).to be_active(:chat)
221
236
  end
237
+
238
+ it "sets @data to empty hash" do
239
+ expect(@rollout.get(:chat).data).to eq({})
240
+ end
222
241
  end
223
242
 
224
243
  describe "activating a feature for a percentage of users" do
@@ -344,6 +363,14 @@ RSpec.describe "Rollout" do
344
363
  @rollout.set(:chat, true)
345
364
  end
346
365
 
366
+ context "when feature was passed as string" do
367
+ it "should be removed from features list" do
368
+ expect(@rollout.features.size).to eq 1
369
+ @rollout.delete('chat')
370
+ expect(@rollout.features.size).to eq 0
371
+ end
372
+ end
373
+
347
374
  it "should be removed from features list" do
348
375
  expect(@rollout.features.size).to eq 1
349
376
  @rollout.delete(:chat)
@@ -386,6 +413,26 @@ RSpec.describe "Rollout" do
386
413
  end
387
414
 
388
415
  it "returns the feature object" do
416
+ feature = @rollout.get(:chat)
417
+ expect(feature.groups).to eq [:caretakers, :greeters]
418
+ expect(feature.percentage).to eq 10
419
+ expect(feature.users).to eq %w(42)
420
+ expect(feature.to_hash).to eq(
421
+ groups: [:caretakers, :greeters],
422
+ percentage: 10,
423
+ users: %w(42)
424
+ )
425
+
426
+ feature = @rollout.get(:signup)
427
+ expect(feature.groups).to be_empty
428
+ expect(feature.users).to be_empty
429
+ expect(feature.percentage).to eq(100)
430
+ end
431
+
432
+ it "returns the feature objects using sets" do
433
+ @options = @rollout.instance_variable_get("@options")
434
+ @options[:use_sets] = true
435
+
389
436
  feature = @rollout.get(:chat)
390
437
  expect(feature.groups).to eq [:caretakers, :greeters].to_set
391
438
  expect(feature.percentage).to eq 10
@@ -413,6 +460,18 @@ RSpec.describe "Rollout" do
413
460
  end
414
461
 
415
462
  it "each feature is cleared" do
463
+ features.each do |feature|
464
+ expect(@rollout.get(feature).to_hash).to eq(
465
+ percentage: 0,
466
+ users: [],
467
+ groups: []
468
+ )
469
+ end
470
+ end
471
+
472
+ it "each feature is cleared with sets" do
473
+ @options = @rollout.instance_variable_get("@options")
474
+ @options[:use_sets] = true
416
475
  features.each do |feature|
417
476
  expect(@rollout.get(feature).to_hash).to eq(
418
477
  percentage: 0,
@@ -515,18 +574,127 @@ RSpec.describe "Rollout" do
515
574
  expect(@rollout.user_in_active_users?(:chat, "5")).to eq(false)
516
575
  end
517
576
  end
518
- end
519
577
 
520
- describe "Rollout::Feature" do
521
- before do
522
- @user = double("User", email: "test@test.com")
523
- @feature = Rollout::Feature.new(:chat, nil, id_user_by: :email)
578
+ describe "#multi_get" do
579
+ before do
580
+ @rollout.activate_percentage(:chat, 10)
581
+ @rollout.activate_group(:chat, :caretakers)
582
+ @rollout.activate_group(:videos, :greeters)
583
+ @rollout.activate(:signup)
584
+ @rollout.activate_user(:photos, double(id: 42))
585
+ end
586
+
587
+ it "returns an array of features" do
588
+ features = @rollout.multi_get(:chat, :videos, :signup)
589
+ expect(features[0].name).to eq :chat
590
+ expect(features[0].groups).to eq [:caretakers]
591
+ expect(features[0].percentage).to eq 10
592
+ expect(features[1].name).to eq :videos
593
+ expect(features[1].groups).to eq [:greeters]
594
+ expect(features[2].name).to eq :signup
595
+ expect(features[2].percentage).to eq 100
596
+ expect(features.size).to eq 3
597
+ end
598
+ end
599
+
600
+ describe "#set_feature_data" do
601
+ before do
602
+ @rollout.set_feature_data(:chat, description: 'foo', release_date: 'bar')
603
+ end
604
+
605
+ it 'sets the data attribute on feature' do
606
+ expect(@rollout.get(:chat).data).to include('description' => 'foo', 'release_date' => 'bar')
607
+ end
608
+
609
+ it 'updates a data attribute' do
610
+ @rollout.set_feature_data(:chat, description: 'baz')
611
+ expect(@rollout.get(:chat).data).to include('description' => 'baz', 'release_date' => 'bar')
612
+ end
613
+
614
+ it 'only sets data on specified feature' do
615
+ @rollout.set_feature_data(:talk, image_url: 'kittens.png')
616
+ expect(@rollout.get(:chat).data).not_to include('image_url' => 'kittens.png')
617
+ expect(@rollout.get(:chat).data).to include('description' => 'foo', 'release_date' => 'bar')
618
+ end
619
+
620
+ it 'does not modify @data if param is nil' do
621
+ expect(@rollout.get(:chat).data).to include('description' => 'foo', 'release_date' => 'bar')
622
+ @rollout.set_feature_data(:chat, nil)
623
+ expect(@rollout.get(:chat).data).to include('description' => 'foo', 'release_date' => 'bar')
624
+ end
625
+
626
+ it 'does not modify @data if param is empty string' do
627
+ expect(@rollout.get(:chat).data).to include('description' => 'foo', 'release_date' => 'bar')
628
+ @rollout.set_feature_data(:chat, " ")
629
+ expect(@rollout.get(:chat).data).to include('description' => 'foo', 'release_date' => 'bar')
630
+ end
631
+
632
+ it 'properly parses data when it contains a |' do
633
+ user = double("User", id: 8)
634
+ @rollout.activate_user(:chat, user)
635
+ @rollout.set_feature_data(:chat, "|call||text|" => "a|bunch|of|stuff")
636
+ expect(@rollout.get(:chat).data).to include("|call||text|" => "a|bunch|of|stuff")
637
+ expect(@rollout.active?(:chat, user)).to be true
638
+ end
639
+ end
640
+
641
+ describe "#clear_feature_data" do
642
+ it 'resets data to empty string' do
643
+ @rollout.set_feature_data(:chat, description: 'foo')
644
+ expect(@rollout.get(:chat).data).to include('description' => 'foo')
645
+ @rollout.clear_feature_data(:chat)
646
+ expect(@rollout.get(:chat).data).to eq({})
647
+ end
524
648
  end
649
+ end
525
650
 
651
+ describe "Rollout::Feature" do
526
652
  describe "#add_user" do
527
653
  it "ids a user using id_user_by" do
528
- @feature.add_user(@user)
529
- expect(@user).to have_received :email
654
+ user = double("User", email: "test@test.com")
655
+ feature = Rollout::Feature.new(:chat, nil, id_user_by: :email)
656
+ feature.add_user(user)
657
+ expect(user).to have_received :email
658
+ end
659
+ end
660
+
661
+ describe "#initialize" do
662
+ describe "when string does not exist" do
663
+ it 'clears feature attributes when string is not given' do
664
+ feature = Rollout::Feature.new(:chat)
665
+ expect(feature.groups).to be_empty
666
+ expect(feature.users).to be_empty
667
+ expect(feature.percentage).to eq 0
668
+ expect(feature.data).to eq({})
669
+ end
670
+
671
+ it 'clears feature attributes when string is nil' do
672
+ feature = Rollout::Feature.new(:chat, nil)
673
+ expect(feature.groups).to be_empty
674
+ expect(feature.users).to be_empty
675
+ expect(feature.percentage).to eq 0
676
+ expect(feature.data).to eq({})
677
+ end
678
+
679
+ it 'clears feature attributes when string is empty string' do
680
+ feature = Rollout::Feature.new(:chat, "")
681
+ expect(feature.groups).to be_empty
682
+ expect(feature.users).to be_empty
683
+ expect(feature.percentage).to eq 0
684
+ expect(feature.data).to eq({})
685
+ end
686
+
687
+ describe "when there is no data" do
688
+ it 'sets @data to empty hash' do
689
+ feature = Rollout::Feature.new(:chat, "0||")
690
+ expect(feature.data).to eq({})
691
+ end
692
+
693
+ it 'sets @data to empty hash' do
694
+ feature = Rollout::Feature.new(:chat, "||| ")
695
+ expect(feature.data).to eq({})
696
+ end
697
+ end
530
698
  end
531
699
  end
532
700
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rollout
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Golick
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-06 00:00:00.000000000 Z
11
+ date: 2016-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -126,4 +126,3 @@ summary: Feature flippers with redis.
126
126
  test_files:
127
127
  - spec/rollout_spec.rb
128
128
  - spec/spec_helper.rb
129
- has_rdoc: