toggles 0.0.2

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: 43bb08698c0cc3f3d5ee291ab7185f210ddbb074
4
+ data.tar.gz: 84a995ec6b03ab71c55e86debbd67dcab16b343c
5
+ SHA512:
6
+ metadata.gz: 77953be11f5972d440d4b7655d10f4b079d1a2d2a50ed173507bdaa5675f183e2b0c9f2c3f8211a9790bb117b0a262670bca17f030f92cd68ae51564b1724b98
7
+ data.tar.gz: a0002c98e7947a0a799abaab00507405c28029fa7ba097c88a1b68dc343f0f325136ec6adc853db8ad8aa1e306c2fc3c9c06db9d5b1a7caac9d5cc015a5ae25b
data/.gitignore ADDED
@@ -0,0 +1,45 @@
1
+ ### Ruby ###
2
+ *.gem
3
+ *.rbc
4
+ /.config
5
+ /coverage/
6
+ /InstalledFiles
7
+ /pkg/
8
+ /spec/reports/
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ ## Specific to RubyMotion:
14
+ .dat*
15
+ .repl_history
16
+ build/
17
+
18
+ ## Documentation cache and generated files:
19
+ /.yardoc/
20
+ /_yardoc/
21
+ /doc/
22
+ /rdoc/
23
+
24
+ ## Environment normalisation:
25
+ /.bundle/
26
+ /vendor/
27
+ /lib/bundler/man/
28
+
29
+ # for a library or gem, you might want to ignore these files since the code is
30
+ # intended to run in multiple environments; otherwise, check them in:
31
+ # Gemfile.lock
32
+ # .ruby-version
33
+ # .ruby-gemset
34
+
35
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
36
+ .rvmrc
37
+
38
+
39
+ ### vim ###
40
+ [._]*.s[a-w][a-z]
41
+ [._]s[a-w][a-z]
42
+ *.un~
43
+ Session.vim
44
+ .netrwhist
45
+ *~
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,50 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ toggles (0.0.2)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coderay (1.1.0)
10
+ diff-lcs (1.2.5)
11
+ method_source (0.8.2)
12
+ pry (0.10.1)
13
+ coderay (~> 1.1.0)
14
+ method_source (~> 0.8.1)
15
+ slop (~> 3.4)
16
+ pry-nav (0.2.4)
17
+ pry (>= 0.9.10, < 0.11.0)
18
+ pry-remote (0.1.8)
19
+ pry (~> 0.9)
20
+ slop (~> 3.0)
21
+ rake (10.4.1)
22
+ rspec (3.1.0)
23
+ rspec-core (~> 3.1.0)
24
+ rspec-expectations (~> 3.1.0)
25
+ rspec-mocks (~> 3.1.0)
26
+ rspec-core (3.1.7)
27
+ rspec-support (~> 3.1.0)
28
+ rspec-expectations (3.1.2)
29
+ diff-lcs (>= 1.2.0, < 2.0)
30
+ rspec-support (~> 3.1.0)
31
+ rspec-its (1.1.0)
32
+ rspec-core (>= 3.0.0)
33
+ rspec-expectations (>= 3.0.0)
34
+ rspec-mocks (3.1.3)
35
+ rspec-support (~> 3.1.0)
36
+ rspec-support (3.1.2)
37
+ slop (3.6.0)
38
+
39
+ PLATFORMS
40
+ ruby
41
+
42
+ DEPENDENCIES
43
+ bundler
44
+ pry
45
+ pry-nav
46
+ pry-remote
47
+ rake
48
+ rspec
49
+ rspec-its
50
+ toggles!
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,8 @@
1
+ user:
2
+ id:
3
+ or:
4
+ in:
5
+ range:
6
+ - 1
7
+ - 10
8
+ gt: 50
data/features/test.yml ADDED
@@ -0,0 +1,5 @@
1
+ user:
2
+ id: 1
3
+ logged_in?: true
4
+ widget:
5
+ id: 2
data/lib/toggles.rb ADDED
@@ -0,0 +1 @@
1
+ require "toggles/feature"
@@ -0,0 +1,50 @@
1
+ require "find"
2
+
3
+ require "toggles/feature/base"
4
+ require "toggles/feature/subject"
5
+ require "toggles/feature/permissions"
6
+ require "toggles/feature/permissions/operation"
7
+
8
+ module Feature
9
+ OPERATIONS = {and: Permissions::Operation::And,
10
+ gt: Permissions::Operation::GreaterThan,
11
+ in: Permissions::Operation::In,
12
+ lt: Permissions::Operation::LessThan,
13
+ or: Permissions::Operation::Or,
14
+ range: Permissions::Operation::Range}
15
+ end
16
+
17
+ # Dynamically create modules and classes within the `Feature` module based on
18
+ # the directory structure of `features`.
19
+ #
20
+ # For example if the `features` directory has the structure:
21
+ #
22
+ # features
23
+ # ├── thing
24
+ # | ├── one.yml
25
+ # | └── two.yml
26
+ # └── test.yml
27
+ #
28
+ # `Feature::Test`, `Feature::Thing::One`, `Feature::Thing::Two` would be
29
+ # available by default.
30
+ Find.find("features") do |path|
31
+ if path.match(/\.ya?ml\Z/)
32
+ *directories, filename = path.chomp(File.extname(path)).split("/")
33
+
34
+ previous = Feature
35
+ directories[1..-1].each do |directory|
36
+ module_name = directory.split("_").map(&:capitalize).join.to_sym
37
+ previous = begin
38
+ previous.const_get(module_name)
39
+ rescue NameError
40
+ previous.const_set(module_name, Module.new)
41
+ end
42
+ end
43
+
44
+ cls = Class.new(Feature::Base) do |c|
45
+ c.const_set(:PERMISSIONS, Feature::Permissions.new(path))
46
+ end
47
+
48
+ previous.const_set(filename.split("_").map(&:capitalize).join.to_sym, cls)
49
+ end
50
+ end
@@ -0,0 +1,27 @@
1
+ require "yaml"
2
+
3
+ module Feature
4
+ class Base
5
+ attr_reader :subjects
6
+
7
+ def self.enabled_for?(subjects = {})
8
+ new(subjects).enabled?
9
+ end
10
+
11
+ def self.disabled_for?(subjects = {})
12
+ !enabled_for? subjects
13
+ end
14
+
15
+ def initialize(subjects)
16
+ @subjects = subjects
17
+ end
18
+
19
+ def permissions
20
+ @permissions ||= self.class::PERMISSIONS
21
+ end
22
+
23
+ def enabled?
24
+ permissions.valid_for? subjects
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ require "toggles/feature/permissions/operation"
2
+
3
+ module Feature
4
+ class Permissions
5
+ extend Forwardable
6
+
7
+ attr_reader :path
8
+
9
+ def_delegators :rules, :all?, :keys
10
+
11
+ def initialize(path)
12
+ @path = path
13
+ end
14
+
15
+ def rules
16
+ @rules ||= YAML.load(File.read(path))
17
+ end
18
+
19
+ def subjects
20
+ @subjects ||= keys.map(&:to_sym)
21
+ end
22
+
23
+ def valid_for?(entities)
24
+ unless subjects == entities.keys
25
+ raise Subject::Invalid, Subject.difference(subjects, entities.keys)
26
+ end
27
+
28
+ rules.all? do |name, rule|
29
+ rule.all? do |key, value|
30
+ OPERATIONS.fetch(key, Operation::Attribute).call(
31
+ entities[name.to_sym], key, value
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,7 @@
1
+ require "toggles/feature/permissions/operation/and"
2
+ require "toggles/feature/permissions/operation/gt"
3
+ require "toggles/feature/permissions/operation/in"
4
+ require "toggles/feature/permissions/operation/lt"
5
+ require "toggles/feature/permissions/operation/or"
6
+ require "toggles/feature/permissions/operation/property"
7
+ require "toggles/feature/permissions/operation/range"
@@ -0,0 +1,15 @@
1
+ module Feature
2
+ class Permissions
3
+ module Operation
4
+ class And
5
+ def self.call(entity, attr_name, expected)
6
+ expected.all? do |operation, value|
7
+ OPERATIONS.fetch(operation.to_sym, Attribute).call(
8
+ entity, attr_name, value
9
+ )
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Feature
2
+ class Permissions
3
+ module Operation
4
+ class GreaterThan
5
+ def self.call(entity, attr_name, expected)
6
+ entity.send(attr_name) > expected
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ module Feature
2
+ class Permissions
3
+ module Operation
4
+ class In
5
+ def self.call(entity, attr_name, expected)
6
+ if expected.kind_of? Hash
7
+ expected = expected.reduce([]) do |list, (operation, args)|
8
+ OPERATIONS[operation.to_sym].call(args)
9
+ end
10
+ end
11
+
12
+ expected.include? entity.send(attr_name.to_sym)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ module Feature
2
+ class Permissions
3
+ module Operation
4
+ class LessThan
5
+ def self.call(entity, attr_name, expected)
6
+ entity.send(attr_name) < expected
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Feature
2
+ class Permissions
3
+ module Operation
4
+ class Or
5
+ def self.call(entity, attr_name, expected)
6
+ expected.any? do |operation, value|
7
+ OPERATIONS.fetch(operation.to_sym, Attribute).call(
8
+ entity, attr_name, value
9
+ )
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ module Feature
2
+ class Permissions
3
+ module Operation
4
+ class Attribute
5
+ def self.call(entity, attr_name, expected)
6
+ if expected.kind_of? Hash
7
+ expected.all? do |operation, value|
8
+ OPERATIONS.fetch(operation.to_sym, Operation::Attribute).call(
9
+ entity, attr_name, value
10
+ )
11
+ end
12
+ else
13
+ entity.send(attr_name) == expected
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ module Feature
2
+ class Permissions
3
+ module Operation
4
+ class Range
5
+ def self.call(args)
6
+ raise StandardError, "Invalid range operation" if args.size != 2
7
+ (args.first..args.last)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ module Feature
2
+ module Subject
3
+ class Invalid < StandardError
4
+ def initialize(args)
5
+ super("Invalid subjects for permissions: #{args}")
6
+ end
7
+ end
8
+
9
+ def self.difference(subjects, others)
10
+ Set.new((subjects - others) + (others - subjects)).to_a
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ require "rspec/its"
2
+
3
+ require "toggles"
4
+
5
+ RSpec.configure do |config|
6
+ config.order = "random"
7
+ end
@@ -0,0 +1,32 @@
1
+ describe Feature::Base do
2
+ let(:user) { double(id: 1, logged_in?: true) }
3
+ let(:widget) { double(id: 2) }
4
+
5
+ subject { Feature::Test.new(user: user, widget: widget) }
6
+
7
+ its(:enabled?) { is_expected.to eq true }
8
+ its(:subjects) { is_expected.to eq user: user, widget: widget }
9
+ its("permissions.subjects") { is_expected.to eq [:user, :widget] }
10
+
11
+ describe "#enabled_for?" do
12
+ specify do
13
+ expect(Feature::Test.enabled_for?(user: double(id: 1, logged_in?: true),
14
+ widget: double(id: 2))).to eq true
15
+ expect(Feature::Test.enabled_for?(user: double(id: 1, logged_in?: false),
16
+ widget: double(id: 2))).to eq false
17
+ end
18
+
19
+ specify "invalid permissions" do
20
+ expect { Feature::Test.enabled_for?(widget: double) }.
21
+ to raise_error Feature::Subject::Invalid, "Invalid subjects for permissions: [:user]"
22
+ end
23
+
24
+ specify "collection" do
25
+ expect(Feature::Collection.enabled_for?(user: double(id: 1))).to eq true
26
+ expect(Feature::Collection.enabled_for?(user: double(id: 5))).to eq true
27
+ expect(Feature::Collection.enabled_for?(user: double(id: 10))).to eq true
28
+ expect(Feature::Collection.enabled_for?(user: double(id: 49))).to eq false
29
+ expect(Feature::Collection.enabled_for?(user: double(id: 51))).to eq true
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ describe Feature::Permissions do
2
+ let(:path) { "features/test.yml" }
3
+
4
+ subject { Feature::Permissions.new(path) }
5
+
6
+ its(:path) { is_expected.to eq path }
7
+ its(:rules) { is_expected.to eq({"user"=>{"id"=>1, "logged_in?"=>true},
8
+ "widget"=>{"id"=>2}}) }
9
+ its(:subjects) { [:user, :widget] }
10
+
11
+ describe "#valid_for?" do
12
+ specify do
13
+ expect(subject.valid_for?(user: double(id: 1, logged_in?: true),
14
+ widget: double(id: 2))).to eq true
15
+ expect(subject.valid_for?(user: double(id: 2, logged_in?: true),
16
+ widget: double(id: 2))).to eq false
17
+ end
18
+
19
+ specify "invalid subjects" do
20
+ expect { subject.valid_for?(user: double) }.
21
+ to raise_error Feature::Subject::Invalid, "Invalid subjects for permissions: [:widget]"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ describe Feature::Subject do
2
+ describe Feature::Subject::Invalid do
3
+ subject { Feature::Subject::Invalid.new([:user]) }
4
+
5
+ its(:message) { is_expected.to eq "Invalid subjects for permissions: [:user]" }
6
+ end
7
+
8
+ describe "#difference" do
9
+ specify do
10
+ expect(Feature::Subject.difference([:foo, :bar], [:bar])).to eq [:foo]
11
+ expect(Feature::Subject.difference([:bar], [:foo, :bar])).to eq [:foo]
12
+ expect(Feature::Subject.difference([:bar], [:foo])).to eq [:bar, :foo]
13
+ end
14
+ end
15
+ end
File without changes
data/toggles.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "toggles"
3
+ s.version = "0.0.2"
4
+ s.authors = ["Andrew Tribone"]
5
+ s.summary = "YAML backed feature toggles"
6
+ s.email = "tribone@easypost.com"
7
+ s.homepage = ""
8
+ s.license = ""
9
+ s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
10
+ s.test_files = s.files.grep(/^(spec)\//)
11
+
12
+ s.add_development_dependency "bundler"
13
+ s.add_development_dependency "pry"
14
+ s.add_development_dependency "pry-nav"
15
+ s.add_development_dependency "pry-remote"
16
+ s.add_development_dependency "rake"
17
+ s.add_development_dependency "rspec"
18
+ s.add_development_dependency "rspec-its"
19
+ end
metadata ADDED
@@ -0,0 +1,172 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: toggles
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Tribone
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-nav
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-remote
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec-its
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description:
112
+ email: tribone@easypost.com
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - ".gitignore"
118
+ - ".rspec"
119
+ - Gemfile
120
+ - Gemfile.lock
121
+ - Rakefile
122
+ - features/collection.yml
123
+ - features/test.yml
124
+ - lib/toggles.rb
125
+ - lib/toggles/feature.rb
126
+ - lib/toggles/feature/base.rb
127
+ - lib/toggles/feature/permissions.rb
128
+ - lib/toggles/feature/permissions/operation.rb
129
+ - lib/toggles/feature/permissions/operation/and.rb
130
+ - lib/toggles/feature/permissions/operation/gt.rb
131
+ - lib/toggles/feature/permissions/operation/in.rb
132
+ - lib/toggles/feature/permissions/operation/lt.rb
133
+ - lib/toggles/feature/permissions/operation/or.rb
134
+ - lib/toggles/feature/permissions/operation/property.rb
135
+ - lib/toggles/feature/permissions/operation/range.rb
136
+ - lib/toggles/feature/subject.rb
137
+ - spec/spec_helper.rb
138
+ - spec/toggles/feature/base_spec.rb
139
+ - spec/toggles/feature/permissions_spec.rb
140
+ - spec/toggles/feature/subject_spec.rb
141
+ - spec/toggles_spec.rb
142
+ - toggles.gemspec
143
+ homepage: ''
144
+ licenses:
145
+ - ''
146
+ metadata: {}
147
+ post_install_message:
148
+ rdoc_options: []
149
+ require_paths:
150
+ - lib
151
+ required_ruby_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ requirements: []
162
+ rubyforge_project:
163
+ rubygems_version: 2.2.2
164
+ signing_key:
165
+ specification_version: 4
166
+ summary: YAML backed feature toggles
167
+ test_files:
168
+ - spec/spec_helper.rb
169
+ - spec/toggles/feature/base_spec.rb
170
+ - spec/toggles/feature/permissions_spec.rb
171
+ - spec/toggles/feature/subject_spec.rb
172
+ - spec/toggles_spec.rb