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 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