rollout 2.0.0 → 2.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bc88ca14f2eb1ae5156873fce8e5f569e8e551d5
4
+ data.tar.gz: 37e6306414e96c1dad888d68f4241bb339af5dd0
5
+ SHA512:
6
+ metadata.gz: 43b4ba29fd8bc735ec1646aa3386ebf409d7d19c3d459fdd649670cf773bcef5ed28583b653ad824b15dcb022ed0e49d55b256a1c477044d93fbd292451df88f
7
+ data.tar.gz: 36031f802e6b61f2d4e8d5205da8bcc8103cc837ddf6fcff1b51db3c4f71e6a2deeb18286e691fdc5f2daa9ab82ee7717118739cd22d7b6bcd5f72de4075e0e4
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ services:
3
+ - redis-server
4
+ rvm:
5
+ - 2.1
6
+ - 2.0.0
7
+ - 1.9.3
8
+ - jruby-19mode
9
+ script:
10
+ - bundle exec rspec
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
- source :rubygems
1
+ source "https://rubygems.org"
2
2
 
3
3
  gemspec
@@ -1,11 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rollout (2.0.0a)
5
- redis
4
+ rollout (2.0.0)
6
5
 
7
6
  GEM
8
- remote: http://rubygems.org/
7
+ remote: https://rubygems.org/
9
8
  specs:
10
9
  bourne (1.0)
11
10
  mocha (= 0.9.8)
@@ -18,7 +17,7 @@ GEM
18
17
  mocha (0.9.8)
19
18
  rake
20
19
  rake (0.9.2.2)
21
- redis (3.0.2)
20
+ redis (3.0.7)
22
21
  rspec (2.10.0)
23
22
  rspec-core (~> 2.10.0)
24
23
  rspec-expectations (~> 2.10.0)
@@ -36,5 +35,6 @@ DEPENDENCIES
36
35
  bundler (>= 1.0.0)
37
36
  jeweler (~> 1.6.4)
38
37
  mocha (= 0.9.8)
38
+ redis
39
39
  rollout!
40
40
  rspec (~> 2.10.0)
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 James Golick
1
+ Copyright (c) 2010-InfinityAndBeyond BitLove, Inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -2,6 +2,8 @@
2
2
 
3
3
  Feature flippers.
4
4
 
5
+ {<img src="https://travis-ci.org/FetLife/rollout.svg?branch=master" alt="Build Status" />}[https://travis-ci.org/FetLife/rollout]
6
+
5
7
  == MAKE SURE TO READ THIS: 2.0 Changes and Migration Path
6
8
 
7
9
  As of rollout-2.x, only one key is used per feature for performance reasons. The data format is <tt>percentage|user_id,user_id,...|group,_group...</tt>. This has the effect of making concurrent feature modifications unsafe, but in practice, I doubt this will actually be a problem.
@@ -22,11 +24,13 @@ If you'd prefer to continue to use the old layout in redis, <tt>Rollout::Legacy<
22
24
 
23
25
  Initialize a rollout object. I assign it to a global var.
24
26
 
27
+ require 'redis'
28
+
25
29
  $redis = Redis.new
26
30
  $rollout = Rollout.new($redis)
27
31
 
28
32
  Check whether a feature is active for a particular user:
29
-
33
+
30
34
  $rollout.active?(:chat, User.first) # => true/false
31
35
 
32
36
  Check whether a feature is active globally:
@@ -37,7 +41,7 @@ You can activate features using a number of different mechanisms.
37
41
 
38
42
  == Groups
39
43
 
40
- Rollout ships with one group by default: "all", which does exactly what it sounds like.
44
+ Rollout ships with one group by default: "all", which does exactly what it sounds like.
41
45
 
42
46
  You can activate the all group for the chat feature like this:
43
47
 
@@ -58,7 +62,7 @@ Deactivate groups like this:
58
62
  == Specific Users
59
63
 
60
64
  You might want to let a specific user into a beta test or something. If that user isn't part of an existing group, you can let them in specifically:
61
-
65
+
62
66
  $rollout.activate_user(:chat, @user)
63
67
 
64
68
  Deactivate them like this:
@@ -73,7 +77,7 @@ If you're rolling out a new feature, you might want to test the waters by slowly
73
77
 
74
78
  The algorithm for determining which users get let in is this:
75
79
 
76
- user.id % 10 < percentage / 10
80
+ CRC32(user.id) % 100 < percentage
77
81
 
78
82
  So, for 20%, users 0, 1, 10, 11, 20, 21, etc would be allowed in. Those users would remain in as the percentage increases.
79
83
 
@@ -100,7 +104,7 @@ If you're using redis, you can namespace keys further to support multiple enviro
100
104
  $ns = Redis::Namespace.new(Rails.env, :redis => $redis)
101
105
  $rollout = Rollout.new($ns)
102
106
  $rollout.activate_group(:chat, :all)
103
-
107
+
104
108
  This example would use the "development:feature:chat:groups" key.
105
109
 
106
110
  == misc/check_rollout.rb
@@ -113,7 +117,14 @@ When a feature reaches "maturity" - in other words, expected to be at 100% rollo
113
117
  == Implementations in other languages
114
118
 
115
119
  * Python: http://github.com/asenchi/proclaim
120
+ * PHP: https://github.com/opensoft/rollout
121
+ * Clojure: https://github.com/tcrayford/shoutout
122
+
123
+ == Contributors
124
+
125
+ * James Golick - Creator - https://github.com/jamesgolick
126
+ * Eric Rafaloff - Maintainer - https://github.com/EricR
116
127
 
117
128
  == Copyright
118
129
 
119
- Copyright (c) 2010 James Golick, BitLove, Inc. See LICENSE for details.
130
+ Copyright (c) 2010-InfinityAndBeyond BitLove, Inc. See LICENSE for details.
@@ -1,13 +1,16 @@
1
+ require "rollout/version"
1
2
  require "rollout/legacy"
2
3
  require "zlib"
3
4
 
4
5
  class Rollout
5
6
  class Feature
6
- attr_reader :name, :groups, :users, :percentage
7
- attr_writer :percentage, :groups, :users
7
+ attr_accessor :groups, :users, :percentage
8
+ attr_reader :name, :options
9
+
10
+ def initialize(name, string = nil, opts = {})
11
+ @options = opts
12
+ @name = name
8
13
 
9
- def initialize(name, string = nil)
10
- @name = name
11
14
  if string
12
15
  raw_percentage,raw_users,raw_groups = string.split("|")
13
16
  @percentage = raw_percentage.to_i
@@ -23,11 +26,12 @@ class Rollout
23
26
  end
24
27
 
25
28
  def add_user(user)
26
- @users << user.id.to_s unless @users.include?(user.id.to_s)
29
+ id = user_id(user)
30
+ @users << id unless @users.include?(id)
27
31
  end
28
32
 
29
33
  def remove_user(user)
30
- @users.delete(user.id.to_s)
34
+ @users.delete(user_id(user))
31
35
  end
32
36
 
33
37
  def add_group(group)
@@ -48,8 +52,9 @@ class Rollout
48
52
  if user.nil?
49
53
  @percentage == 100
50
54
  else
51
- user_in_percentage?(user) ||
52
- user_in_active_users?(user) ||
55
+ id = user_id(user)
56
+ user_in_percentage?(id) ||
57
+ user_in_active_users?(id) ||
53
58
  user_in_active_group?(user, rollout)
54
59
  end
55
60
  end
@@ -61,12 +66,25 @@ class Rollout
61
66
  end
62
67
 
63
68
  private
69
+ def user_id(user)
70
+ if user.is_a?(Fixnum) ||
71
+ user.is_a?(String)
72
+ user.to_s
73
+ else
74
+ user.send(id_user_by).to_s
75
+ end
76
+ end
77
+
78
+ def id_user_by
79
+ @options[:id_user_by] || :id
80
+ end
81
+
64
82
  def user_in_percentage?(user)
65
- Zlib.crc32(user.id.to_s) % 100 < @percentage
83
+ Zlib.crc32(user_id(user)) % 100 < @percentage
66
84
  end
67
85
 
68
86
  def user_in_active_users?(user)
69
- @users.include?(user.id.to_s)
87
+ @users.include?(user_id(user))
70
88
  end
71
89
 
72
90
  def user_in_active_group?(user, rollout)
@@ -77,9 +95,10 @@ class Rollout
77
95
  end
78
96
 
79
97
  def initialize(storage, opts = {})
80
- @storage = storage
81
- @groups = {:all => lambda { |user| true }}
82
- @legacy = Legacy.new(opts[:legacy_storage] || @storage) if opts[:migrate]
98
+ @storage = storage
99
+ @options = opts
100
+ @groups = {:all => lambda { |user| true }}
101
+ @legacy = Legacy.new(opts[:legacy_storage] || @storage) if opts[:migrate]
83
102
  end
84
103
 
85
104
  def activate(feature)
@@ -94,6 +113,16 @@ class Rollout
94
113
  end
95
114
  end
96
115
 
116
+ def set(feature, desired_state)
117
+ with_feature(feature) do |f|
118
+ if desired_state
119
+ f.percentage = 100
120
+ else
121
+ f.clear
122
+ end
123
+ end
124
+ end
125
+
97
126
  def activate_group(feature, group)
98
127
  with_feature(feature) do |f|
99
128
  f.add_group(group)
@@ -147,11 +176,12 @@ class Rollout
147
176
  def get(feature)
148
177
  string = @storage.get(key(feature))
149
178
  if string || !migrate?
150
- Feature.new(feature, string)
179
+ Feature.new(feature, string, @options)
151
180
  else
152
181
  info = @legacy.info(feature)
153
182
  f = Feature.new(feature)
154
183
  f.percentage = info[:percentage]
184
+ f.percentage = 100 if info[:global].include? feature
155
185
  f.groups = info[:groups].map { |g| g.to_sym }
156
186
  f.users = info[:users].map { |u| u.to_s }
157
187
  save(f)
@@ -163,6 +193,15 @@ class Rollout
163
193
  (@storage.get(features_key) || "").split(",").map(&:to_sym)
164
194
  end
165
195
 
196
+ def clear!
197
+ features.each do |feature|
198
+ with_feature(feature) { |f| f.clear }
199
+ @storage.del(key(feature))
200
+ end
201
+
202
+ @storage.del(features_key)
203
+ end
204
+
166
205
  private
167
206
  def key(name)
168
207
  "feature:#{name}"
@@ -180,7 +219,7 @@ class Rollout
180
219
 
181
220
  def save(feature)
182
221
  @storage.set(key(feature.name), feature.serialize)
183
- @storage.set(features_key, (features | [feature.name]).join(","))
222
+ @storage.set(features_key, (features | [feature.name.to_sym]).join(","))
184
223
  end
185
224
 
186
225
  def migrate?
@@ -1,3 +1,3 @@
1
1
  class Rollout
2
- VERSION = "2.0.0"
2
+ VERSION = "2.1.0"
3
3
  end
@@ -11,8 +11,6 @@ Gem::Specification.new do |s|
11
11
  s.summary = "Feature flippers with redis."
12
12
  s.homepage = "https://github.com/jamesgolick/rollout"
13
13
 
14
- s.require_paths = ["lib"]
15
-
16
14
  s.rubyforge_project = "rollout"
17
15
 
18
16
  s.files = `git ls-files`.split("\n")
@@ -20,12 +18,10 @@ Gem::Specification.new do |s|
20
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
19
  s.require_paths = ["lib"]
22
20
 
23
- # specify any dependencies here; for example:
24
21
  s.add_development_dependency "rspec", "~> 2.10.0"
25
22
  s.add_development_dependency "bundler", ">= 1.0.0"
26
23
  s.add_development_dependency "jeweler", "~> 1.6.4"
27
24
  s.add_development_dependency "bourne", "1.0"
28
25
  s.add_development_dependency "mocha", "0.9.8"
29
-
30
- s.add_runtime_dependency "redis"
26
+ s.add_development_dependency "redis"
31
27
  end
@@ -185,7 +185,7 @@ describe "Rollout::Legacy" do
185
185
  end
186
186
 
187
187
  it "returns all global features" do
188
- @rollout.info.should eq({ :global => features.reverse })
188
+ @rollout.info[:global].should include(*features)
189
189
  end
190
190
  end
191
191
 
@@ -199,12 +199,11 @@ describe "Rollout::Legacy" do
199
199
  end
200
200
 
201
201
  it "returns info about all the activations" do
202
- @rollout.info(:chat).should == {
203
- :percentage => 10,
204
- :groups => [:greeters, :caretakers],
205
- :users => [42],
206
- :global => [:signup]
207
- }
202
+ info = @rollout.info(:chat)
203
+ info[:percentage].should == 10
204
+ info[:groups].should include(:caretakers, :greeters)
205
+ info[:users].should include(42)
206
+ info[:global].should include(:signup)
208
207
  end
209
208
  end
210
209
 
@@ -97,6 +97,20 @@ describe "Rollout" do
97
97
  end
98
98
  end
99
99
 
100
+ describe "activating a specific user by ID" do
101
+ before do
102
+ @rollout.activate_user(:chat, 42)
103
+ end
104
+
105
+ it "is active for that user" do
106
+ @rollout.should be_active(:chat, stub(:id => 42))
107
+ end
108
+
109
+ it "remains inactive for other users" do
110
+ @rollout.should_not be_active(:chat, stub(:id => 24))
111
+ end
112
+ end
113
+
100
114
  describe "activating a specific user with a string id" do
101
115
  before do
102
116
  @rollout.activate_user(:chat, stub(:id => 'user-72'))
@@ -206,6 +220,26 @@ describe "Rollout" do
206
220
  end
207
221
  end
208
222
 
223
+ describe "setting a feature on" do
224
+ before do
225
+ @rollout.set(:chat, true)
226
+ end
227
+
228
+ it "becomes activated" do
229
+ @rollout.should be_active(:chat)
230
+ end
231
+ end
232
+
233
+ describe "setting a feature off" do
234
+ before do
235
+ @rollout.set(:chat, false)
236
+ end
237
+
238
+ it "becomes activated" do
239
+ @rollout.should_not be_active(:chat)
240
+ end
241
+ end
242
+
209
243
  describe "keeps a list of features" do
210
244
  it "saves the feature" do
211
245
  @rollout.activate(:chat)
@@ -217,6 +251,12 @@ describe "Rollout" do
217
251
  @rollout.activate(:chat)
218
252
  @rollout.features.size.should == 1
219
253
  end
254
+
255
+ it "does not contain doubles when using string" do
256
+ @rollout.activate(:chat)
257
+ @rollout.activate("chat")
258
+ @rollout.features.size.should == 1
259
+ end
220
260
  end
221
261
 
222
262
  describe "#get" do
@@ -246,6 +286,30 @@ describe "Rollout" do
246
286
  end
247
287
  end
248
288
 
289
+ describe "#clear" do
290
+ let(:features) { %w(signup beta alpha gm) }
291
+
292
+ before do
293
+ features.each { |f| @rollout.activate(f) }
294
+
295
+ @rollout.clear!
296
+ end
297
+
298
+ it "each feature is cleared" do
299
+ features.each do |feature|
300
+ @rollout.get(feature).to_hash.should == {
301
+ :percentage => 0,
302
+ :users => [],
303
+ :groups => []
304
+ }
305
+ end
306
+ end
307
+
308
+ it "removes all features" do
309
+ @rollout.features.should be_empty
310
+ end
311
+ end
312
+
249
313
  describe "migration mode" do
250
314
  before do
251
315
  @legacy = Rollout::Legacy.new(@redis)
@@ -270,5 +334,24 @@ describe "Rollout" do
270
334
  }
271
335
  @redis.get("feature:chat").should_not be_nil
272
336
  end
337
+
338
+ it "imports settings that were globally activated" do
339
+ @legacy.activate_globally(:video_chat)
340
+ @rollout.get(:video_chat).to_hash[:percentage].should == 100
341
+ end
342
+ end
343
+ end
344
+
345
+ describe "Rollout::Feature" do
346
+ before do
347
+ @user = stub("User", :email => "test@test.com")
348
+ @feature = Rollout::Feature.new(:chat, nil, :id_user_by => :email)
349
+ end
350
+
351
+ describe "#add_user" do
352
+ it "ids a user using id_user_by" do
353
+ @feature.add_user(@user)
354
+ @user.should have_received :email
355
+ end
273
356
  end
274
357
  end
metadata CHANGED
@@ -1,68 +1,60 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rollout
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
5
- prerelease:
4
+ version: 2.1.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - James Golick
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-07-24 00:00:00.000000000 Z
11
+ date: 2014-12-10 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: rspec
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ~>
17
+ - - "~>"
20
18
  - !ruby/object:Gem::Version
21
19
  version: 2.10.0
22
20
  type: :development
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ~>
24
+ - - "~>"
28
25
  - !ruby/object:Gem::Version
29
26
  version: 2.10.0
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: bundler
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ! '>='
31
+ - - ">="
36
32
  - !ruby/object:Gem::Version
37
33
  version: 1.0.0
38
34
  type: :development
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ! '>='
38
+ - - ">="
44
39
  - !ruby/object:Gem::Version
45
40
  version: 1.0.0
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: jeweler
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
- - - ~>
45
+ - - "~>"
52
46
  - !ruby/object:Gem::Version
53
47
  version: 1.6.4
54
48
  type: :development
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
- - - ~>
52
+ - - "~>"
60
53
  - !ruby/object:Gem::Version
61
54
  version: 1.6.4
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: bourne
64
57
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
58
  requirements:
67
59
  - - '='
68
60
  - !ruby/object:Gem::Version
@@ -70,7 +62,6 @@ dependencies:
70
62
  type: :development
71
63
  prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
65
  requirements:
75
66
  - - '='
76
67
  - !ruby/object:Gem::Version
@@ -78,7 +69,6 @@ dependencies:
78
69
  - !ruby/object:Gem::Dependency
79
70
  name: mocha
80
71
  requirement: !ruby/object:Gem::Requirement
81
- none: false
82
72
  requirements:
83
73
  - - '='
84
74
  - !ruby/object:Gem::Version
@@ -86,7 +76,6 @@ dependencies:
86
76
  type: :development
87
77
  prerelease: false
88
78
  version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
79
  requirements:
91
80
  - - '='
92
81
  - !ruby/object:Gem::Version
@@ -94,17 +83,15 @@ dependencies:
94
83
  - !ruby/object:Gem::Dependency
95
84
  name: redis
96
85
  requirement: !ruby/object:Gem::Requirement
97
- none: false
98
86
  requirements:
99
- - - ! '>='
87
+ - - ">="
100
88
  - !ruby/object:Gem::Version
101
89
  version: '0'
102
- type: :runtime
90
+ type: :development
103
91
  prerelease: false
104
92
  version_requirements: !ruby/object:Gem::Requirement
105
- none: false
106
93
  requirements:
107
- - - ! '>='
94
+ - - ">="
108
95
  - !ruby/object:Gem::Version
109
96
  version: '0'
110
97
  description: Feature flippers with redis.
@@ -114,13 +101,13 @@ executables: []
114
101
  extensions: []
115
102
  extra_rdoc_files: []
116
103
  files:
117
- - .document
118
- - .gitignore
104
+ - ".document"
105
+ - ".gitignore"
106
+ - ".travis.yml"
119
107
  - Gemfile
120
108
  - Gemfile.lock
121
109
  - LICENSE
122
110
  - README.rdoc
123
- - VERSION
124
111
  - lib/rollout.rb
125
112
  - lib/rollout/legacy.rb
126
113
  - lib/rollout/version.rb
@@ -132,27 +119,26 @@ files:
132
119
  - spec/spec_helper.rb
133
120
  homepage: https://github.com/jamesgolick/rollout
134
121
  licenses: []
122
+ metadata: {}
135
123
  post_install_message:
136
124
  rdoc_options: []
137
125
  require_paths:
138
126
  - lib
139
127
  required_ruby_version: !ruby/object:Gem::Requirement
140
- none: false
141
128
  requirements:
142
- - - ! '>='
129
+ - - ">="
143
130
  - !ruby/object:Gem::Version
144
131
  version: '0'
145
132
  required_rubygems_version: !ruby/object:Gem::Requirement
146
- none: false
147
133
  requirements:
148
- - - ! '>='
134
+ - - ">="
149
135
  - !ruby/object:Gem::Version
150
136
  version: '0'
151
137
  requirements: []
152
138
  rubyforge_project: rollout
153
- rubygems_version: 1.8.23
139
+ rubygems_version: 2.2.2
154
140
  signing_key:
155
- specification_version: 3
141
+ specification_version: 4
156
142
  summary: Feature flippers with redis.
157
143
  test_files:
158
144
  - spec/legacy_spec.rb
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 1.1.0