rollout 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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