flag 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +72 -0
- data/Rakefile +11 -0
- data/SPEC.md +16 -0
- data/flag.gemspec +17 -0
- data/lib/flag.rb +139 -0
- data/test/flag_test.rb +99 -0
- data/test/spec_helper.rb +7 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: aa905390895050cd8a589c4c97f36cc1b3d8a8b9
|
4
|
+
data.tar.gz: 360389f7f12e04b64b009bc4ba63cad0b0d40481
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 839277859db6982e7fb3833642a79ff49f83c0764a50390021648b6319bea6746fd60bed4b965d55450fdceee9a481075bac5f6f35588fecb937430a860a3d1a
|
7
|
+
data.tar.gz: 16fdbe22d8fa51324ec895b2db0d20cf83e2ea12395b91cb782b34e4ce11fd89a42aea67ee18ba49e1bd28a92006a938ceff14a47f0232976acfe41dba87ee90
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# Flag
|
2
|
+
|
3
|
+
Simple feature flags for any app
|
4
|
+
|
5
|
+
## Install
|
6
|
+
|
7
|
+
```
|
8
|
+
gem install flag
|
9
|
+
```
|
10
|
+
|
11
|
+
## Initialize
|
12
|
+
|
13
|
+
`Flag` uses `Redic.new` if no other conenction is supplied
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
Flag.store = Redic.new(ENV["OTHER_REDIS"]) # <3 Redic
|
17
|
+
```
|
18
|
+
|
19
|
+
## Basic usage
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
if Flag(:new_design).on?
|
23
|
+
# Shiny new design
|
24
|
+
else
|
25
|
+
# Marquee and blink everywhere
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
## Enable/Check feature flags
|
30
|
+
|
31
|
+
### Ids
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
Flag(:new_buttons).on! # Enabled for everyone
|
35
|
+
Flag(:new_buttons).off! # Disabled for everyone
|
36
|
+
|
37
|
+
Flag(:new_buttons).on!(1) # Enabled for id 1
|
38
|
+
Flag(:new_buttons).on?(1) #=> true
|
39
|
+
|
40
|
+
Flag(:new_buttons).on!("AnyRandomIdentification") # Use what you want as an id
|
41
|
+
Flag(:new_buttons).on?("AnyRandomIdentification") #=> true
|
42
|
+
```
|
43
|
+
|
44
|
+
### Groups
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
Flag.group[:staff] = lambda { |id| User.find(id).staff? }
|
48
|
+
|
49
|
+
Flag(:new_scary_feature).on!(:staff) # This will run a block to check if it's valid
|
50
|
+
Flag(:new_scary_feature).on?(user.id) #=> true
|
51
|
+
```
|
52
|
+
|
53
|
+
### Percentages
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
Flag(:testing).on!("33%")
|
57
|
+
```
|
58
|
+
|
59
|
+
## Info
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
Flag.enabled # Shows you an array of the currently activated features
|
63
|
+
#=> [:landing_page]
|
64
|
+
|
65
|
+
Flag.features # All the features, even the off ones
|
66
|
+
|
67
|
+
Flag.groups # The currently defined groups
|
68
|
+
#=> [:staff, :beta_testers]
|
69
|
+
|
70
|
+
Flag(:holidays).activated # A hash with info on who has this feature active
|
71
|
+
#=> {:percentage => 100, :users => ["1"], :groups => [:staff] }
|
72
|
+
```
|
data/Rakefile
ADDED
data/SPEC.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
``ruby
|
2
|
+
if Flag(:experiment).on?
|
3
|
+
end
|
4
|
+
|
5
|
+
Flag(:experiment).on!
|
6
|
+
Flag(:experiment).off!
|
7
|
+
|
8
|
+
Flag(:experiment).on!("30%")
|
9
|
+
Flag(:experiment).on!(user.id)
|
10
|
+
Flag(:experiment).on!(:group)
|
11
|
+
|
12
|
+
Flag(:experiment).on?(user.id)
|
13
|
+
Flag(:experiment).on?(:group)
|
14
|
+
|
15
|
+
Flag.group[:group] = lambda { |id| id % 2 }
|
16
|
+
```
|
data/flag.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "flag"
|
3
|
+
s.version = "0.0.1"
|
4
|
+
s.summary = "Simple feature flags"
|
5
|
+
s.description = "Feature flags for the humans and the coders"
|
6
|
+
s.authors = ["elcuervo"]
|
7
|
+
s.licenses = ["MIT", "HUGWARE"]
|
8
|
+
s.email = ["yo@brunoaguirre.com"]
|
9
|
+
s.homepage = "http://github.com/elcuervo/flag"
|
10
|
+
s.files = `git ls-files`.split("\n")
|
11
|
+
s.test_files = `git ls-files test`.split("\n")
|
12
|
+
|
13
|
+
s.add_dependency("redic", "~> 0.0.8")
|
14
|
+
|
15
|
+
s.add_development_dependency("minitest", "~> 5.3.0")
|
16
|
+
s.add_development_dependency("minitest-given", "~> 3.0.0")
|
17
|
+
end
|
data/lib/flag.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
require "zlib"
|
2
|
+
require "redic"
|
3
|
+
|
4
|
+
module Flag
|
5
|
+
FEATURES = "_flag:features".freeze
|
6
|
+
|
7
|
+
Members = Struct.new(:name) do
|
8
|
+
USERS = "users".freeze
|
9
|
+
GROUPS = "groups".freeze
|
10
|
+
|
11
|
+
def <<(item)
|
12
|
+
if item.to_s.end_with?("%")
|
13
|
+
Flag.store.call("HSET", Flag::FEATURES, name, item[0...-1])
|
14
|
+
else
|
15
|
+
Flag.store.call("SADD", subkey(item), item)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def key
|
20
|
+
"#{Flag::FEATURES}:#{name}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def actived
|
24
|
+
{ percentage: percentage.to_i, users: users, groups: groups }
|
25
|
+
end
|
26
|
+
|
27
|
+
def groups; members_for(GROUPS).map(&:to_sym) end
|
28
|
+
def users; members_for(USERS) end
|
29
|
+
|
30
|
+
def include?(item)
|
31
|
+
return percentage == item[0...-1].to_i if item.to_s.end_with?("%")
|
32
|
+
return true if Zlib.crc32(item.to_s) % 100 < percentage
|
33
|
+
|
34
|
+
Flag.store.call("SISMEMBER", subkey(item), item).to_i == 1
|
35
|
+
end
|
36
|
+
|
37
|
+
def reset
|
38
|
+
[USERS, GROUPS].each { |k| Flag.store.call("DEL", "#{key}:#{k}") }
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def members_for(type)
|
44
|
+
Flag.store.call("SMEMBERS", "#{key}:#{type}") || []
|
45
|
+
end
|
46
|
+
|
47
|
+
def percentage
|
48
|
+
Flag.store.call("HGET", Flag::FEATURES, name).to_i
|
49
|
+
end
|
50
|
+
|
51
|
+
def subkey(item)
|
52
|
+
"#{key}:#{subgroup(item)}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def subgroup(item)
|
56
|
+
case item
|
57
|
+
when Integer, Fixnum, String then USERS
|
58
|
+
when Symbol then GROUPS
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Feature
|
64
|
+
attr_accessor :active
|
65
|
+
attr_reader :name
|
66
|
+
|
67
|
+
def initialize(name)
|
68
|
+
@name = name
|
69
|
+
@members = Members.new(name)
|
70
|
+
end
|
71
|
+
|
72
|
+
def reset; @members.reset end
|
73
|
+
def key; @members.key end
|
74
|
+
def actived; @members.actived end
|
75
|
+
|
76
|
+
def off?; !active? end
|
77
|
+
|
78
|
+
def on?(what = false)
|
79
|
+
return active? if !what
|
80
|
+
return true if @members.include?(what)
|
81
|
+
|
82
|
+
case what
|
83
|
+
when Integer, Fixnum, String
|
84
|
+
@members.groups.any? { |g| Flag.group[g].call(what) }
|
85
|
+
else
|
86
|
+
false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def off!
|
91
|
+
Flag.store.call("HSET", Flag::FEATURES, name, 0)
|
92
|
+
end
|
93
|
+
|
94
|
+
def on!(what = false)
|
95
|
+
if what
|
96
|
+
@members << what
|
97
|
+
else
|
98
|
+
Flag.store.call("HSET", Flag::FEATURES, name, 100)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def active?
|
105
|
+
Flag.store.call("HGET", FEATURES, name).to_i == 100
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class << self
|
110
|
+
attr_accessor :store
|
111
|
+
|
112
|
+
def flush
|
113
|
+
@_group = nil
|
114
|
+
features.each { |_, f| f.reset }
|
115
|
+
self.store.call("DEL", FEATURES)
|
116
|
+
end
|
117
|
+
|
118
|
+
def enabled
|
119
|
+
features.select { |k, v| k if v.on? }.keys
|
120
|
+
end
|
121
|
+
|
122
|
+
def store; @store ||= Redic.new end
|
123
|
+
def group; @_group ||= Hash.new { |h, k| h[k] = lambda { |id| } } end
|
124
|
+
|
125
|
+
def groups; group.keys end
|
126
|
+
|
127
|
+
def features
|
128
|
+
@_features ||= Hash.new { |h, k| h[k] = Feature.new(k) }
|
129
|
+
|
130
|
+
self.store.call("HGETALL", FEATURES).each_slice(2) do |slice|
|
131
|
+
@_features[slice.first.to_sym]
|
132
|
+
end
|
133
|
+
|
134
|
+
@_features
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def Flag(feature); Flag.features[feature] end
|
data/test/flag_test.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Flag do
|
4
|
+
after { Flag.flush }
|
5
|
+
|
6
|
+
context "features" do
|
7
|
+
|
8
|
+
context "empty" do
|
9
|
+
Given(:features) { Flag.enabled }
|
10
|
+
Then { features == [] }
|
11
|
+
end
|
12
|
+
|
13
|
+
context "having one" do
|
14
|
+
Given { Flag(:test).on! }
|
15
|
+
Then { Flag.enabled == [:test] }
|
16
|
+
end
|
17
|
+
|
18
|
+
context "turning them off" do
|
19
|
+
Given { Flag(:test).on! }
|
20
|
+
When { Flag(:test).off! }
|
21
|
+
When { Flag(:test2).on! }
|
22
|
+
|
23
|
+
Then { Flag.enabled == [:test2] }
|
24
|
+
And { Flag.features.keys == [:test, :test2] }
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
context "groups" do
|
30
|
+
Given do
|
31
|
+
Flag.group[:staff] = lambda { |id| id > 1 }
|
32
|
+
end
|
33
|
+
|
34
|
+
context "adding groups" do
|
35
|
+
When(:groups) { Flag.groups }
|
36
|
+
Then { groups == [:staff] }
|
37
|
+
end
|
38
|
+
|
39
|
+
context "testing if feature is actived for a group" do
|
40
|
+
Given { Flag(:test).on!(:staff) }
|
41
|
+
When(:feature) { Flag(:test).on?(:staff) }
|
42
|
+
Then { feature == true }
|
43
|
+
end
|
44
|
+
|
45
|
+
context "trying to check for an empty group" do
|
46
|
+
Given { Flag(:test).on!(:bogus) }
|
47
|
+
When(:feature) { Flag(:test).on?(1) }
|
48
|
+
Then { feature == false }
|
49
|
+
end
|
50
|
+
|
51
|
+
context "testing if a user beloging to a group get stuff activated" do
|
52
|
+
Given { Flag(:test).on!(:staff) }
|
53
|
+
|
54
|
+
Then { Flag(:test).on?(1) == false }
|
55
|
+
And { Flag(:test).on?(2) == true }
|
56
|
+
And { Flag(:test).on?(3) == true }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "users" do
|
61
|
+
context "enabled only for a user" do
|
62
|
+
When { Flag(:test).on!(1) }
|
63
|
+
|
64
|
+
Then { Flag(:test).on?(1) == true }
|
65
|
+
And { Flag(:test).on?(2) == false }
|
66
|
+
end
|
67
|
+
|
68
|
+
context "with non numeric users" do
|
69
|
+
When { Flag(:test).on!("ImARandomUUID") }
|
70
|
+
Then { Flag(:test).on?("ImARandomUUID") == true }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "keys" do
|
75
|
+
Given(:feature) { Flag(:test_the_key) }
|
76
|
+
Then { feature.key == "_flag:features:test_the_key" }
|
77
|
+
end
|
78
|
+
|
79
|
+
context "percentage" do
|
80
|
+
Given { Flag(:test).on!("50%") }
|
81
|
+
|
82
|
+
Then { Flag(:test).on?(1) == false }
|
83
|
+
And { Flag(:test).on?(2) == true }
|
84
|
+
And { Flag(:test).on?("49%") == false }
|
85
|
+
And { Flag(:test).on?("50%") == true }
|
86
|
+
end
|
87
|
+
|
88
|
+
context "get feature info" do
|
89
|
+
Given { Flag(:test).on!("50%") }
|
90
|
+
Given { Flag(:test).on!("25") }
|
91
|
+
Given { Flag(:test).on!("UUID") }
|
92
|
+
Given { Flag(:test).on!(:staff) }
|
93
|
+
|
94
|
+
Then { Flag(:test).actived.is_a?(Hash) }
|
95
|
+
And { Flag(:test).actived[:percentage] == 50 }
|
96
|
+
And { Flag(:test).actived[:users] == ["25", "UUID"] }
|
97
|
+
And { Flag(:test).actived[:groups] == [:staff] }
|
98
|
+
end
|
99
|
+
end
|
data/test/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: flag
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- elcuervo
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: redic
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.0.8
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.0.8
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 5.3.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 5.3.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest-given
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.0.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.0.0
|
55
|
+
description: Feature flags for the humans and the coders
|
56
|
+
email:
|
57
|
+
- yo@brunoaguirre.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- README.md
|
63
|
+
- Rakefile
|
64
|
+
- SPEC.md
|
65
|
+
- flag.gemspec
|
66
|
+
- lib/flag.rb
|
67
|
+
- test/flag_test.rb
|
68
|
+
- test/spec_helper.rb
|
69
|
+
homepage: http://github.com/elcuervo/flag
|
70
|
+
licenses:
|
71
|
+
- MIT
|
72
|
+
- HUGWARE
|
73
|
+
metadata: {}
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
requirements: []
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 2.0.14
|
91
|
+
signing_key:
|
92
|
+
specification_version: 4
|
93
|
+
summary: Simple feature flags
|
94
|
+
test_files:
|
95
|
+
- test/flag_test.rb
|
96
|
+
- test/spec_helper.rb
|