flag 0.0.1
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.
- 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
|