rollout 1.1.0 → 1.2.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.
- data/.gitignore +21 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +40 -0
- data/lib/rollout.rb +43 -10
- data/lib/rollout/version.rb +3 -0
- data/rollout.gemspec +22 -45
- data/spec/rollout_spec.rb +47 -7
- data/spec/spec_helper.rb +2 -3
- metadata +61 -23
- data/Rakefile +0 -47
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
rollout (1.2.0)
|
5
|
+
redis
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
bourne (1.0)
|
11
|
+
mocha (= 0.9.8)
|
12
|
+
diff-lcs (1.1.3)
|
13
|
+
git (1.2.5)
|
14
|
+
jeweler (1.6.4)
|
15
|
+
bundler (~> 1.0)
|
16
|
+
git (>= 1.2.5)
|
17
|
+
rake
|
18
|
+
mocha (0.9.8)
|
19
|
+
rake
|
20
|
+
rake (0.9.2.2)
|
21
|
+
redis (2.2.2)
|
22
|
+
rspec (2.10.0)
|
23
|
+
rspec-core (~> 2.10.0)
|
24
|
+
rspec-expectations (~> 2.10.0)
|
25
|
+
rspec-mocks (~> 2.10.0)
|
26
|
+
rspec-core (2.10.1)
|
27
|
+
rspec-expectations (2.10.0)
|
28
|
+
diff-lcs (~> 1.1.3)
|
29
|
+
rspec-mocks (2.10.1)
|
30
|
+
|
31
|
+
PLATFORMS
|
32
|
+
ruby
|
33
|
+
|
34
|
+
DEPENDENCIES
|
35
|
+
bourne (= 1.0)
|
36
|
+
bundler (>= 1.0.0)
|
37
|
+
jeweler (~> 1.6.4)
|
38
|
+
mocha (= 0.9.8)
|
39
|
+
rollout!
|
40
|
+
rspec (~> 2.10.0)
|
data/lib/rollout.rb
CHANGED
@@ -4,6 +4,14 @@ class Rollout
|
|
4
4
|
@groups = {"all" => lambda { |user| true }}
|
5
5
|
end
|
6
6
|
|
7
|
+
def activate_globally(feature)
|
8
|
+
@redis.sadd(global_key, feature)
|
9
|
+
end
|
10
|
+
|
11
|
+
def deactivate_globally(feature)
|
12
|
+
@redis.srem(global_key, feature)
|
13
|
+
end
|
14
|
+
|
7
15
|
def activate_group(feature, group)
|
8
16
|
@redis.sadd(group_key(feature), group)
|
9
17
|
end
|
@@ -16,6 +24,7 @@ class Rollout
|
|
16
24
|
@redis.del(group_key(feature))
|
17
25
|
@redis.del(user_key(feature))
|
18
26
|
@redis.del(percentage_key(feature))
|
27
|
+
deactivate_globally(feature)
|
19
28
|
end
|
20
29
|
|
21
30
|
def activate_user(feature, user)
|
@@ -30,10 +39,15 @@ class Rollout
|
|
30
39
|
@groups[group.to_s] = block
|
31
40
|
end
|
32
41
|
|
33
|
-
def active?(feature, user)
|
34
|
-
|
35
|
-
|
36
|
-
|
42
|
+
def active?(feature, user = nil)
|
43
|
+
if user
|
44
|
+
active_globally?(feature) ||
|
45
|
+
user_in_active_group?(feature, user) ||
|
46
|
+
user_active?(feature, user) ||
|
47
|
+
user_within_active_percentage?(feature, user)
|
48
|
+
else
|
49
|
+
active_globally?(feature)
|
50
|
+
end
|
37
51
|
end
|
38
52
|
|
39
53
|
def activate_percentage(feature, percentage)
|
@@ -44,12 +58,19 @@ class Rollout
|
|
44
58
|
@redis.del(percentage_key(feature))
|
45
59
|
end
|
46
60
|
|
47
|
-
def info(feature)
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
61
|
+
def info(feature = nil)
|
62
|
+
if feature
|
63
|
+
{
|
64
|
+
:percentage => (active_percentage(feature) || 0).to_i,
|
65
|
+
:groups => active_groups(feature).map { |g| g.to_sym },
|
66
|
+
:users => active_user_ids(feature),
|
67
|
+
:global => active_global_features
|
68
|
+
}
|
69
|
+
else
|
70
|
+
{
|
71
|
+
:global => active_global_features
|
72
|
+
}
|
73
|
+
end
|
53
74
|
end
|
54
75
|
|
55
76
|
private
|
@@ -69,6 +90,10 @@ class Rollout
|
|
69
90
|
"#{key(name)}:percentage"
|
70
91
|
end
|
71
92
|
|
93
|
+
def global_key
|
94
|
+
"feature:__global__"
|
95
|
+
end
|
96
|
+
|
72
97
|
def active_groups(feature)
|
73
98
|
@redis.smembers(group_key(feature)) || []
|
74
99
|
end
|
@@ -77,10 +102,18 @@ class Rollout
|
|
77
102
|
@redis.smembers(user_key(feature)).map { |id| id.to_i }
|
78
103
|
end
|
79
104
|
|
105
|
+
def active_global_features
|
106
|
+
(@redis.smembers(global_key) || []).map(&:to_sym)
|
107
|
+
end
|
108
|
+
|
80
109
|
def active_percentage(feature)
|
81
110
|
@redis.get(percentage_key(feature))
|
82
111
|
end
|
83
112
|
|
113
|
+
def active_globally?(feature)
|
114
|
+
@redis.sismember(global_key, feature)
|
115
|
+
end
|
116
|
+
|
84
117
|
def user_in_active_group?(feature, user)
|
85
118
|
active_groups(feature).any? do |group|
|
86
119
|
@groups.key?(group) && @groups[group].call(user)
|
data/rollout.gemspec
CHANGED
@@ -1,54 +1,31 @@
|
|
1
|
-
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
1
|
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "rollout/version"
|
5
4
|
|
6
5
|
Gem::Specification.new do |s|
|
7
6
|
s.name = "rollout"
|
8
|
-
s.version =
|
9
|
-
|
10
|
-
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
7
|
+
s.version = Rollout::VERSION
|
11
8
|
s.authors = ["James Golick"]
|
12
|
-
s.
|
13
|
-
s.description = "
|
14
|
-
s.
|
15
|
-
s.
|
16
|
-
|
17
|
-
"README.rdoc"
|
18
|
-
]
|
19
|
-
s.files = [
|
20
|
-
".document",
|
21
|
-
"LICENSE",
|
22
|
-
"README.rdoc",
|
23
|
-
"Rakefile",
|
24
|
-
"VERSION",
|
25
|
-
"lib/rollout.rb",
|
26
|
-
"rollout.gemspec",
|
27
|
-
"spec/rollout_spec.rb",
|
28
|
-
"spec/spec.opts",
|
29
|
-
"spec/spec_helper.rb"
|
30
|
-
]
|
31
|
-
s.homepage = "http://github.com/jamesgolick/rollout"
|
9
|
+
s.email = ["jamesgolick@gmail.com"]
|
10
|
+
s.description = "Feature flippers with redis."
|
11
|
+
s.summary = "Feature flippers with redis."
|
12
|
+
s.homepage = "https://github.com/jamesgolick/rollout"
|
13
|
+
|
32
14
|
s.require_paths = ["lib"]
|
33
|
-
s.rubygems_version = "1.8.10"
|
34
|
-
s.summary = "Conditionally roll out features with redis."
|
35
15
|
|
36
|
-
|
37
|
-
s.specification_version = 3
|
16
|
+
s.rubyforge_project = "rollout"
|
38
17
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
s.add_dependency(%q<bourne>, ["= 1.0.0"])
|
51
|
-
s.add_dependency(%q<redis>, ["= 0.1"])
|
52
|
-
end
|
53
|
-
end
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
|
23
|
+
# specify any dependencies here; for example:
|
24
|
+
s.add_development_dependency "rspec", "~> 2.10.0"
|
25
|
+
s.add_development_dependency "bundler", ">= 1.0.0"
|
26
|
+
s.add_development_dependency "jeweler", "~> 1.6.4"
|
27
|
+
s.add_development_dependency "bourne", "1.0"
|
28
|
+
s.add_development_dependency "mocha", "0.9.8"
|
54
29
|
|
30
|
+
s.add_runtime_dependency "redis"
|
31
|
+
end
|
data/spec/rollout_spec.rb
CHANGED
@@ -60,6 +60,7 @@ describe "Rollout" do
|
|
60
60
|
@rollout.activate_group(:chat, :fivesonly)
|
61
61
|
@rollout.activate_user(:chat, stub(:id => 51))
|
62
62
|
@rollout.activate_percentage(:chat, 100)
|
63
|
+
@rollout.activate_globally(:chat)
|
63
64
|
@rollout.deactivate_all(:chat)
|
64
65
|
end
|
65
66
|
|
@@ -74,6 +75,10 @@ describe "Rollout" do
|
|
74
75
|
it "removes the percentage" do
|
75
76
|
@rollout.should_not be_active(:chat, stub(:id => 24))
|
76
77
|
end
|
78
|
+
|
79
|
+
it "removes globally" do
|
80
|
+
@rollout.should_not be_active(:chat)
|
81
|
+
end
|
77
82
|
end
|
78
83
|
|
79
84
|
describe "activating a specific user" do
|
@@ -106,6 +111,16 @@ describe "Rollout" do
|
|
106
111
|
end
|
107
112
|
end
|
108
113
|
|
114
|
+
describe "activating a feature globally" do
|
115
|
+
before do
|
116
|
+
@rollout.activate_globally(:chat)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "activates the feature" do
|
120
|
+
@rollout.should be_active(:chat)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
109
124
|
describe "activating a feature for a percentage of users" do
|
110
125
|
before do
|
111
126
|
@rollout.activate_percentage(:chat, 20)
|
@@ -148,33 +163,58 @@ describe "Rollout" do
|
|
148
163
|
end
|
149
164
|
end
|
150
165
|
|
166
|
+
describe "deactivating the feature globally" do
|
167
|
+
before do
|
168
|
+
@rollout.activate_globally(:chat)
|
169
|
+
@rollout.deactivate_globally(:chat)
|
170
|
+
end
|
171
|
+
|
172
|
+
it "becomes inactivate" do
|
173
|
+
@rollout.should_not be_active(:chat)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
151
177
|
describe "#info" do
|
178
|
+
context "global features" do
|
179
|
+
let(:features) { [:signup, :chat, :table] }
|
180
|
+
|
181
|
+
before do
|
182
|
+
features.each do |f|
|
183
|
+
@rollout.activate_globally(f)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
it "returns all global features" do
|
188
|
+
@rollout.info.should eq({ :global => features.reverse })
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
152
192
|
describe "with a percentage set" do
|
153
193
|
before do
|
154
194
|
@rollout.activate_percentage(:chat, 10)
|
155
195
|
@rollout.activate_group(:chat, :caretakers)
|
156
196
|
@rollout.activate_group(:chat, :greeters)
|
197
|
+
@rollout.activate_globally(:signup)
|
157
198
|
@rollout.activate_user(:chat, stub(:id => 42))
|
158
199
|
end
|
159
200
|
|
160
201
|
it "returns info about all the activations" do
|
161
202
|
@rollout.info(:chat).should == {
|
162
203
|
:percentage => 10,
|
163
|
-
:groups
|
164
|
-
:users
|
204
|
+
:groups => [:greeters, :caretakers],
|
205
|
+
:users => [42],
|
206
|
+
:global => [:signup]
|
165
207
|
}
|
166
208
|
end
|
167
209
|
end
|
168
210
|
|
169
211
|
describe "without a percentage set" do
|
170
|
-
before do
|
171
|
-
end
|
172
|
-
|
173
212
|
it "the percentage defaults to 0" do
|
174
213
|
@rollout.info(:chat).should == {
|
175
214
|
:percentage => 0,
|
176
|
-
:groups
|
177
|
-
:users
|
215
|
+
:groups => [],
|
216
|
+
:users => [],
|
217
|
+
:global => []
|
178
218
|
}
|
179
219
|
end
|
180
220
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
2
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
3
|
require 'rollout'
|
4
|
-
require '
|
5
|
-
require 'spec/autorun'
|
4
|
+
require 'rspec'
|
6
5
|
require 'bourne'
|
7
6
|
require 'redis'
|
8
7
|
|
9
|
-
|
8
|
+
RSpec.configure do |config|
|
10
9
|
config.mock_with :mocha
|
11
10
|
config.before { Redis.new.flushdb }
|
12
11
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rollout
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,60 +9,95 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-09-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &70171348875940 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
|
-
- -
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.10.0
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70171348875940
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: bundler
|
27
|
+
requirement: &70171348875080 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.0.0
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70171348875080
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: jeweler
|
38
|
+
requirement: &70171348874140 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
20
42
|
- !ruby/object:Gem::Version
|
21
|
-
version: 1.
|
43
|
+
version: 1.6.4
|
22
44
|
type: :development
|
23
45
|
prerelease: false
|
24
|
-
version_requirements: *
|
46
|
+
version_requirements: *70171348874140
|
25
47
|
- !ruby/object:Gem::Dependency
|
26
48
|
name: bourne
|
27
|
-
requirement: &
|
49
|
+
requirement: &70171348872540 !ruby/object:Gem::Requirement
|
28
50
|
none: false
|
29
51
|
requirements:
|
30
52
|
- - =
|
31
53
|
- !ruby/object:Gem::Version
|
32
|
-
version: 1.0
|
54
|
+
version: '1.0'
|
33
55
|
type: :development
|
34
56
|
prerelease: false
|
35
|
-
version_requirements: *
|
57
|
+
version_requirements: *70171348872540
|
36
58
|
- !ruby/object:Gem::Dependency
|
37
|
-
name:
|
38
|
-
requirement: &
|
59
|
+
name: mocha
|
60
|
+
requirement: &70171348871700 !ruby/object:Gem::Requirement
|
39
61
|
none: false
|
40
62
|
requirements:
|
41
63
|
- - =
|
42
64
|
- !ruby/object:Gem::Version
|
43
|
-
version:
|
65
|
+
version: 0.9.8
|
44
66
|
type: :development
|
45
67
|
prerelease: false
|
46
|
-
version_requirements: *
|
47
|
-
|
48
|
-
|
68
|
+
version_requirements: *70171348871700
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: redis
|
71
|
+
requirement: &70171348870700 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70171348870700
|
80
|
+
description: Feature flippers with redis.
|
81
|
+
email:
|
82
|
+
- jamesgolick@gmail.com
|
49
83
|
executables: []
|
50
84
|
extensions: []
|
51
|
-
extra_rdoc_files:
|
52
|
-
- LICENSE
|
53
|
-
- README.rdoc
|
85
|
+
extra_rdoc_files: []
|
54
86
|
files:
|
55
87
|
- .document
|
88
|
+
- .gitignore
|
89
|
+
- Gemfile
|
90
|
+
- Gemfile.lock
|
56
91
|
- LICENSE
|
57
92
|
- README.rdoc
|
58
|
-
- Rakefile
|
59
93
|
- VERSION
|
60
94
|
- lib/rollout.rb
|
95
|
+
- lib/rollout/version.rb
|
61
96
|
- rollout.gemspec
|
62
97
|
- spec/rollout_spec.rb
|
63
98
|
- spec/spec.opts
|
64
99
|
- spec/spec_helper.rb
|
65
|
-
homepage:
|
100
|
+
homepage: https://github.com/jamesgolick/rollout
|
66
101
|
licenses: []
|
67
102
|
post_install_message:
|
68
103
|
rdoc_options: []
|
@@ -81,9 +116,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
116
|
- !ruby/object:Gem::Version
|
82
117
|
version: '0'
|
83
118
|
requirements: []
|
84
|
-
rubyforge_project:
|
119
|
+
rubyforge_project: rollout
|
85
120
|
rubygems_version: 1.8.10
|
86
121
|
signing_key:
|
87
122
|
specification_version: 3
|
88
|
-
summary:
|
89
|
-
test_files:
|
123
|
+
summary: Feature flippers with redis.
|
124
|
+
test_files:
|
125
|
+
- spec/rollout_spec.rb
|
126
|
+
- spec/spec.opts
|
127
|
+
- spec/spec_helper.rb
|
data/Rakefile
DELETED
@@ -1,47 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'rake'
|
3
|
-
|
4
|
-
begin
|
5
|
-
require 'jeweler'
|
6
|
-
Jeweler::Tasks.new do |gem|
|
7
|
-
gem.name = "rollout"
|
8
|
-
gem.summary = %Q{Conditionally roll out features with redis.}
|
9
|
-
gem.description = %Q{Conditionally roll out features with redis.}
|
10
|
-
gem.email = "jamesgoick@gmail.com"
|
11
|
-
gem.homepage = "http://github.com/jamesgolick/rollout"
|
12
|
-
gem.authors = ["James Golick"]
|
13
|
-
gem.add_development_dependency "rspec", "1.2.9"
|
14
|
-
gem.add_development_dependency "bourne", "1.0.0"
|
15
|
-
gem.add_development_dependency "redis", "0.1"
|
16
|
-
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
|
-
end
|
18
|
-
Jeweler::GemcutterTasks.new
|
19
|
-
rescue LoadError
|
20
|
-
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
21
|
-
end
|
22
|
-
|
23
|
-
require 'spec/rake/spectask'
|
24
|
-
Spec::Rake::SpecTask.new(:spec) do |spec|
|
25
|
-
spec.libs << 'lib' << 'spec'
|
26
|
-
spec.spec_files = FileList['spec/**/*_spec.rb']
|
27
|
-
end
|
28
|
-
|
29
|
-
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
30
|
-
spec.libs << 'lib' << 'spec'
|
31
|
-
spec.pattern = 'spec/**/*_spec.rb'
|
32
|
-
spec.rcov = true
|
33
|
-
end
|
34
|
-
|
35
|
-
task :spec => :check_dependencies
|
36
|
-
|
37
|
-
task :default => :spec
|
38
|
-
|
39
|
-
require 'rake/rdoctask'
|
40
|
-
Rake::RDocTask.new do |rdoc|
|
41
|
-
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
42
|
-
|
43
|
-
rdoc.rdoc_dir = 'rdoc'
|
44
|
-
rdoc.title = "rollout #{version}"
|
45
|
-
rdoc.rdoc_files.include('README*')
|
46
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
47
|
-
end
|