flag 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,11 @@
1
+ require "rake/testtask"
2
+
3
+ Rake::TestTask.new("spec") do |t|
4
+ t.libs << "lib"
5
+ t.libs << "test"
6
+ t.pattern = "test/**/*_test.rb"
7
+ end
8
+
9
+ task :default => [:test]
10
+ task :all => [:test, :bench]
11
+ task :test => [:spec]
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
@@ -0,0 +1,7 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+
3
+ require "minitest/autorun"
4
+ require "minitest/spec"
5
+ require "minitest/pride"
6
+ require "minitest/given"
7
+ require "flag"
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