bastet 0.0.2 → 0.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.
- data/README.markdown +13 -14
- data/lib/bastet/base.rb +13 -39
- data/lib/bastet/group.rb +31 -0
- data/lib/bastet/version.rb +1 -1
- data/lib/bastet.rb +19 -4
- data/spec/bastet/base_spec.rb +10 -30
- data/spec/bastet/group_spec.rb +64 -0
- data/spec/bastet_spec.rb +7 -5
- data/spec/spec_helper.rb +8 -0
- metadata +15 -12
data/README.markdown
CHANGED
@@ -1,31 +1,30 @@
|
|
1
|
-
# Bastet - The
|
2
|
-
|
3
|
-
Goddess associated with war, protection of Lower Egypt and the pharaoh, the sun, perfumes, ointments and embalming
|
1
|
+
# Bastet - The group based feature rollout beast
|
4
2
|
|
5
3
|
## Gemfile
|
6
4
|
|
7
|
-
gem "bastet"
|
5
|
+
gem "bastet"
|
8
6
|
|
9
7
|
## Configuration
|
10
8
|
|
11
9
|
redis = Redis.new
|
12
|
-
|
10
|
+
bastet = Bastet.setup(redis) #=> Bastet::Base.instance ...
|
13
11
|
|
14
12
|
## Usage
|
15
13
|
|
16
|
-
|
17
|
-
|
14
|
+
group = Bastet::Group.new('admin_users') { |user| user.admin? }
|
15
|
+
bastet.activate("admin_only_feature", group)
|
16
|
+
|
17
|
+
user = User.new(admin: true)
|
18
|
+
bastet.active?("admin_only_feature", user) #=> true
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
$bastet.active?(:scary_feature, :admins) #=> true
|
20
|
+
bastet.deactivate("admin_only_feature", group)
|
21
|
+
bastet.inactive?("admin_only_feature", user) #=> true
|
22
22
|
|
23
23
|
## To do
|
24
24
|
|
25
|
-
1. Support activating/deactivating
|
26
|
-
2.
|
27
|
-
3.
|
28
|
-
4. Logging
|
25
|
+
1. Support activating/deactivating for multiple users/groups at once
|
26
|
+
2. Percentage support
|
27
|
+
3. Logging
|
29
28
|
|
30
29
|
## Contibuting
|
31
30
|
|
data/lib/bastet/base.rb
CHANGED
@@ -1,49 +1,23 @@
|
|
1
|
-
|
2
|
-
attr_accessor :redis
|
1
|
+
require 'singleton'
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
end
|
3
|
+
class Bastet::Base
|
4
|
+
include Singleton
|
7
5
|
|
8
|
-
def activate feature,
|
9
|
-
|
6
|
+
def activate feature, group
|
7
|
+
Bastet.redis.sadd("feature_#{feature}", group.name)
|
10
8
|
end
|
11
9
|
|
12
|
-
def deactivate feature,
|
13
|
-
|
10
|
+
def deactivate feature, group
|
11
|
+
Bastet.redis.srem("feature_#{feature}", group.name)
|
14
12
|
end
|
15
13
|
|
16
|
-
def active? feature,
|
17
|
-
|
14
|
+
def active? feature, entity
|
15
|
+
group_names = Bastet.redis.smembers("feature_#{feature}")
|
16
|
+
groups = Bastet.groups.select { |group| group_names.include?(group.name) }
|
17
|
+
groups.any? { |group| group.criteria.call(entity) }
|
18
18
|
end
|
19
19
|
|
20
|
-
def inactive? feature,
|
21
|
-
!active? feature,
|
20
|
+
def inactive? feature, entity
|
21
|
+
!active? feature, entity
|
22
22
|
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def act method, feature, target
|
27
|
-
@redis.send(method, key(feature, target), val(target))
|
28
|
-
end
|
29
|
-
|
30
|
-
def key feature, target
|
31
|
-
[].tap do |key|
|
32
|
-
key << "feature"
|
33
|
-
key << feature.to_s
|
34
|
-
key << namespace(target)
|
35
|
-
end.join(":")
|
36
|
-
end
|
37
|
-
|
38
|
-
def val target
|
39
|
-
instance?(target) ? target.id : target.to_s
|
40
|
-
end
|
41
|
-
|
42
|
-
def namespace target
|
43
|
-
instance?(target) ? "users" : "groups"
|
44
|
-
end
|
45
|
-
|
46
|
-
def instance? target
|
47
|
-
target.respond_to?(:id)
|
48
|
-
end
|
49
23
|
end
|
data/lib/bastet/group.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
class Bastet::Group
|
2
|
+
attr_accessor :name, :criteria
|
3
|
+
|
4
|
+
def initialize name, &block
|
5
|
+
raise ArgumentError.new("Need to pass a criteria") unless block_given?
|
6
|
+
validate_name!(name.to_s)
|
7
|
+
|
8
|
+
@name = name.to_s
|
9
|
+
@criteria = block
|
10
|
+
|
11
|
+
persist!
|
12
|
+
end
|
13
|
+
|
14
|
+
def contains? entity
|
15
|
+
criteria.call(entity)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def validate_name! name
|
21
|
+
if Bastet.redis.sismember('bastet_groups', name)
|
22
|
+
raise ArgumentError.new("#{name} is already initialized as a group.")
|
23
|
+
else
|
24
|
+
Bastet.redis.sadd('bastet_groups', name)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def persist!
|
29
|
+
Bastet.groups << self
|
30
|
+
end
|
31
|
+
end
|
data/lib/bastet/version.rb
CHANGED
data/lib/bastet.rb
CHANGED
@@ -1,9 +1,24 @@
|
|
1
|
-
require
|
1
|
+
require 'bastet/version'
|
2
|
+
require 'bastet/base'
|
3
|
+
require 'bastet/group'
|
2
4
|
|
3
5
|
module Bastet
|
4
|
-
|
6
|
+
class << self
|
7
|
+
def redis
|
8
|
+
@@redis
|
9
|
+
end
|
5
10
|
|
6
|
-
|
7
|
-
|
11
|
+
def redis=(redis)
|
12
|
+
@@redis = redis
|
13
|
+
end
|
14
|
+
|
15
|
+
def groups
|
16
|
+
@@groups ||= []
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup(redis)
|
20
|
+
self.redis = redis
|
21
|
+
Bastet::Base.instance
|
22
|
+
end
|
8
23
|
end
|
9
24
|
end
|
data/spec/bastet/base_spec.rb
CHANGED
@@ -2,49 +2,29 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Bastet::Base do
|
4
4
|
before do
|
5
|
-
@
|
6
|
-
@bastet = Bastet::Base.new(@redis)
|
7
|
-
end
|
8
|
-
|
9
|
-
describe "initialization" do
|
10
|
-
it "should configure the Redis instance" do
|
11
|
-
Bastet::Base.new(@redis).redis.should == @redis
|
12
|
-
end
|
5
|
+
@bastet = Bastet.setup(@redis)
|
13
6
|
end
|
14
7
|
|
15
8
|
describe "activate" do
|
16
|
-
it "should activate the :banana for the
|
17
|
-
|
18
|
-
user
|
9
|
+
it "should activate the :banana for the group" do
|
10
|
+
group = Bastet::Group.new("admins") { |entity| entity.admin? }
|
11
|
+
user = mock('user', admin?: true)
|
19
12
|
|
20
|
-
@bastet.activate(:banana,
|
13
|
+
@bastet.activate(:banana, group)
|
21
14
|
@bastet.active?(:banana, user).should be_true
|
22
15
|
end
|
23
|
-
|
24
|
-
it "should activate the :banana for the group" do
|
25
|
-
@bastet.activate(:banana, :admins)
|
26
|
-
@bastet.active?(:banana, :admins).should be_true
|
27
|
-
end
|
28
16
|
end
|
29
17
|
|
30
18
|
describe "deactivate" do
|
31
|
-
it "should
|
32
|
-
|
33
|
-
user
|
19
|
+
it "should deactive :banana for the group" do
|
20
|
+
group = Bastet::Group.new("admins") { |entity| entity.admin? }
|
21
|
+
user = mock('user', admin?: true)
|
34
22
|
|
35
|
-
@bastet.activate(:banana,
|
23
|
+
@bastet.activate(:banana, group)
|
36
24
|
@bastet.active?(:banana, user).should be_true
|
37
25
|
|
38
|
-
@bastet.deactivate(:banana,
|
26
|
+
@bastet.deactivate(:banana, group)
|
39
27
|
@bastet.inactive?(:banana, user).should be_true
|
40
28
|
end
|
41
|
-
|
42
|
-
it "should deactivate the :banana for the group" do
|
43
|
-
@bastet.activate(:banana, :admins)
|
44
|
-
@bastet.active?(:banana, :admins).should be_true
|
45
|
-
|
46
|
-
@bastet.deactivate(:banana, :admins)
|
47
|
-
@bastet.inactive?(:banana, :admins).should be_true
|
48
|
-
end
|
49
29
|
end
|
50
30
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Bastet::Group do
|
4
|
+
before do
|
5
|
+
@bastet = Bastet.setup(@redis)
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "initializer" do
|
9
|
+
it "should take a group name" do
|
10
|
+
Bastet::Group.new('cool_feature') { }.name.should == "cool_feature"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should always return a string" do
|
14
|
+
Bastet::Group.new('cooler_feature') { }.name.should be_a(String)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should take a required criteria block" do
|
18
|
+
Bastet::Group.new('bananas') { }.criteria.should be_a(Proc)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should raise an argument error if there is no block" do
|
22
|
+
lambda {
|
23
|
+
Bastet::Group.new('cooler_feature')
|
24
|
+
}.should raise_exception(ArgumentError)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should make sure the name is unique" do
|
28
|
+
Bastet::Group.new('cooler_feature') { }
|
29
|
+
['cooler_feature', :cooler_feature].each do |name|
|
30
|
+
lambda {
|
31
|
+
Bastet::Group.new(name) { }
|
32
|
+
}.should raise_exception(ArgumentError)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should add the group to the groups" do
|
37
|
+
lambda {
|
38
|
+
Bastet::Group.new('banana') { }
|
39
|
+
}.should change(Bastet.groups, :count).by(1)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "contains?" do
|
44
|
+
it "should be true if the criteria matches" do
|
45
|
+
group = Bastet::Group.new('admins_only') { |entity| entity.admin? }
|
46
|
+
group.contains?(mock(admin?: true)).should be_true
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should be false if the criteria doesn't match" do
|
50
|
+
group = Bastet::Group.new('people_named_bill') { |entity| entity.name == "Bill" }
|
51
|
+
group.contains?(mock(name: 'Joe')).should be_false
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should be true for all" do
|
55
|
+
group = Bastet::Group.new('all') { |entity| true }
|
56
|
+
group.contains?(mock).should be_true
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should be false for all" do
|
60
|
+
group = Bastet::Group.new('none') { |entity| false }
|
61
|
+
group.contains?(mock).should be_false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/spec/bastet_spec.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Bastet do
|
4
|
-
before do
|
5
|
-
@redis = Redis.new
|
6
|
-
end
|
7
|
-
|
8
4
|
describe "::setup" do
|
9
5
|
it "should return a new Bastet::Base object" do
|
10
|
-
Bastet.setup(@redis).should
|
6
|
+
Bastet.setup(@redis).should == Bastet::Base.instance
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "::groups" do
|
11
|
+
it "should have no groups" do
|
12
|
+
Bastet.groups.should be_empty
|
11
13
|
end
|
12
14
|
end
|
13
15
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bastet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-
|
12
|
+
date: 2011-12-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
requirement: &
|
16
|
+
requirement: &70334749774580 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 0.9.2.2
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70334749774580
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &70334749774080 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 2.7.0
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70334749774080
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: mocha
|
38
|
-
requirement: &
|
38
|
+
requirement: &70334749773620 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 0.10.0
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70334749773620
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: redis
|
49
|
-
requirement: &
|
49
|
+
requirement: &70334749773160 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: 2.2.2
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70334749773160
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: simplecov
|
60
|
-
requirement: &
|
60
|
+
requirement: &70334749772660 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,7 +65,7 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70334749772660
|
69
69
|
description: Simple feature rollout for users, and groups
|
70
70
|
email:
|
71
71
|
- itsmeduncan@gmail.com
|
@@ -84,8 +84,10 @@ files:
|
|
84
84
|
- bastet.gemspec
|
85
85
|
- lib/bastet.rb
|
86
86
|
- lib/bastet/base.rb
|
87
|
+
- lib/bastet/group.rb
|
87
88
|
- lib/bastet/version.rb
|
88
89
|
- spec/bastet/base_spec.rb
|
90
|
+
- spec/bastet/group_spec.rb
|
89
91
|
- spec/bastet_spec.rb
|
90
92
|
- spec/spec_helper.rb
|
91
93
|
homepage: https://github.com/itsmeduncan/bastet
|
@@ -114,5 +116,6 @@ specification_version: 3
|
|
114
116
|
summary: Simple feature rollout for users, and groups
|
115
117
|
test_files:
|
116
118
|
- spec/bastet/base_spec.rb
|
119
|
+
- spec/bastet/group_spec.rb
|
117
120
|
- spec/bastet_spec.rb
|
118
121
|
- spec/spec_helper.rb
|