toggles 0.0.2

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