rollout 2.3.0 → 2.4.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
  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: