toggles 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4c0bf3043c4b85c32062fa22750cc20c88af4cd4
4
- data.tar.gz: f8aeb5d080385e546815ccc57d8401e8abc1c9e4
3
+ metadata.gz: 3fc507d16102054690297319ce1e47e61c1ef408
4
+ data.tar.gz: 482eb06c1c41bd52afd536d0aad227e57ac7deab
5
5
  SHA512:
6
- metadata.gz: 2b7f5e471ba5456ee1ecc659f902b2826fecd2de532c1a4aef9b49e4791985df6cbdf73260e9643cd340711c3ab8f63c6daefb2691bcbc761fade903d3eacf93
7
- data.tar.gz: 08d1d0b300525bde3f0166b3fced8bc426b11f383eeb0ef4a6a60a1530e4e52f1e67522ea96472f2546a4f98d35ffece5642b3997569fb00777639626e9b14e0
6
+ metadata.gz: 97b6de15c5f8abff3dc5fac12dd1429163ebe7208a1b68c4666af82d130cb6aa2ca3a44c20a87e959ee6df3bf42964b046658a0355816d3b799aa1292bd340bd
7
+ data.tar.gz: 85553a0544a5c6cb9f8929fe9f9c66d540451d6df8e2996af56af9bb3a16ea28ff9495f39dcb250e6396c8cf74bc48a30d18979a7e41c9ea9dc532cefc607321
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2016-2016, Simpler Postage, Inc. <oss@easypost.com>
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any
4
+ purpose with or without fee is hereby granted, provided that the above
5
+ copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10
+ SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12
+ OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13
+ CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
@@ -0,0 +1,79 @@
1
+ # Toggles
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/toggles.svg)](https://badge.fury.io/rb/toggles)
4
+ [![CircleCI](https://circleci.com/gh/EasyPost/toggles.svg?style=svg)](https://circleci.com/gh/EasyPost/toggles)
5
+
6
+ YAML backed feature toggles
7
+
8
+ ## Installation
9
+
10
+ Add the following to your Gemfile:
11
+
12
+ ```ruby
13
+ gem "toggles"
14
+ ```
15
+
16
+ and run `bundle install` from your shell.
17
+
18
+ To install the gem manually from your shell, run:
19
+
20
+ ```ruby
21
+ gem install toggles
22
+ ```
23
+
24
+ ## Configuration
25
+
26
+ Configure `toggles`:
27
+
28
+ ```ruby
29
+ Toggles.configure do |config|
30
+ config.features_dir = "features"
31
+ end
32
+ ```
33
+
34
+ You can now express conditional logic within `features_dir`. The structure of the `features_dir` determines the structure of the classes within the `Feature` module. For example if the `features_dir` has the structure:
35
+
36
+ ```
37
+ features
38
+ ├── thing
39
+ | ├── one.yml
40
+ | └── two.yml
41
+ └── test.yml
42
+ ```
43
+
44
+ The classes `Feature::Test`, `Feature::Thing::One` and `Feature::Thing::Two` will be available for use within
45
+ your application.
46
+
47
+ You can call the `Toggles.init` method to force re-parsing the configuration and re-initializing all Features
48
+ structures at any time. The `Toggles.reinit_if_necessary` method is a convenience helper which will only
49
+ re-initialize of the top-level features directory has changed. Note that, in general, this will only detect
50
+ changes if you use a system where you swap out the entire features directory on changes and do not edit
51
+ individual files within the directory.
52
+
53
+ ## Usage
54
+
55
+ Create a file in `features_dir`:
56
+
57
+ ```yaml
58
+ user:
59
+ id:
60
+ in:
61
+ - 12345
62
+ - 54321
63
+ ```
64
+
65
+ Check if the feature is enabled or disabled:
66
+
67
+ ```ruby
68
+ Feature::NewFeature::AvailableForPresentation.enabled_for?(user: OpenStruct.new(id: 12345)) # true
69
+ Feature::NewFeature::AvailableForPresentation.enabled_for?(user: OpenStruct.new(id: 54321)) # true
70
+ Feature::NewFeature::AvailableForPresentation.enabled_for?(user: OpenStruct.new(id: 7)) # false
71
+
72
+ Feature::NewFeature::AvailableForPresentation.disabled_for?(user: OpenStruct.new(id: 12345)) # false
73
+ Feature::NewFeature::AvailableForPresentation.disabled_for?(user: OpenStruct.new(id: 54321)) # false
74
+ Feature::NewFeature::AvailableForPresentation.disabled_for?(user: OpenStruct.new(id: 7)) # true
75
+ ```
76
+
77
+ ## License
78
+
79
+ This project is licensed under the ISC License, the contents of which can be found at [LICENSE.txt](LICENSE.txt).
@@ -1,10 +1,16 @@
1
+ require "find"
2
+ require "pathname"
3
+
1
4
  require "toggles/configuration"
2
5
  require "toggles/feature"
3
6
 
4
7
  module Toggles
5
8
  extend self
6
9
 
10
+ StatResult = Struct.new(:inode, :mtime)
11
+
7
12
  def configure
13
+ @stat_tuple ||= StatResult.new(0, 0)
8
14
  yield configuration
9
15
  init
10
16
  end
@@ -30,11 +36,25 @@ module Toggles
30
36
  def init
31
37
  return unless Dir.exists? configuration.features_dir
32
38
 
33
- Find.find(configuration.features_dir) do |path|
39
+ new_tree = Module.new
40
+
41
+ top_level = File.realpath(configuration.features_dir)
42
+ top_level_p = Pathname.new(top_level)
43
+
44
+ Find.find(top_level) do |path|
45
+ previous = new_tree
46
+ abspath = path
47
+ path = Pathname.new(path).relative_path_from(top_level_p).to_s
34
48
  if path.match(/\.ya?ml\Z/)
35
- _, *directories, filename = path.chomp(File.extname(path)).split("/")
49
+ base = path.chomp(File.extname(path)).split("/")
50
+ if base.size > 1
51
+ directories = base[0...-1]
52
+ filename = base[-1]
53
+ else
54
+ directories = []
55
+ filename = base[0]
56
+ end
36
57
 
37
- previous = Feature
38
58
  directories.each do |directory|
39
59
  module_name = directory.split("_").map(&:capitalize).join.to_sym
40
60
  previous = if previous.constants.include? module_name
@@ -45,11 +65,30 @@ module Toggles
45
65
  end
46
66
 
47
67
  cls = Class.new(Feature::Base) do |c|
48
- c.const_set(:PERMISSIONS, Feature::Permissions.new(path))
68
+ c.const_set(:PERMISSIONS, Feature::Permissions.new(abspath))
49
69
  end
50
70
 
51
71
  previous.const_set(filename.split("_").map(&:capitalize).join.to_sym, cls)
52
72
  end
53
73
  end
74
+
75
+ stbuf = File.stat(top_level)
76
+ @stat_tuple = StatResult.new(stbuf.ino, stbuf.mtime)
77
+
78
+ Feature.set_tree(new_tree)
79
+ end
80
+
81
+ def reinit_if_changed
82
+ # Reload the configuration if the top-level directory has changed.
83
+ # Does not detect changes to files inside that directory unless your
84
+ # filesystem propagates mtimes.
85
+ return unless Dir.exists? configuration.features_dir
86
+ top_level = File.realpath(configuration.features_dir)
87
+ stbuf = File.stat(top_level)
88
+ stat_tuple = StatResult.new(stbuf.ino, stbuf.mtime)
89
+
90
+ if @stat_tuple != stat_tuple
91
+ init
92
+ end
54
93
  end
55
94
  end
@@ -1,5 +1,3 @@
1
- require "find"
2
-
3
1
  require "toggles/feature/base"
4
2
  require "toggles/feature/operation"
5
3
  require "toggles/feature/permissions"
@@ -13,4 +11,14 @@ module Feature
13
11
  not: Operation::Not,
14
12
  or: Operation::Or,
15
13
  range: Operation::Range}
14
+
15
+ @@tree = Module.new
16
+
17
+ def self.set_tree(tree)
18
+ @@tree = tree
19
+ end
20
+
21
+ def self.const_missing(sym)
22
+ @@tree.const_get(sym, inherit: false)
23
+ end
16
24
  end
@@ -1,11 +1,14 @@
1
1
  require "rspec/its"
2
+ require 'rspec/temp_dir'
2
3
 
3
4
  require "toggles"
4
5
 
5
- Toggles.configure do |config|
6
- config.features_dir = "features"
7
- end
8
-
9
6
  RSpec.configure do |config|
10
7
  config.order = "random"
8
+
9
+ config.before(:each) do
10
+ Toggles.configure do |c|
11
+ c.features_dir = "features"
12
+ end
13
+ end
11
14
  end
@@ -1,4 +1,4 @@
1
- describe Feature::Collection do
1
+ describe "Feature::Collection" do
2
2
  specify do
3
3
  expect(Feature::Collection.enabled_for?(user: double(id: 1))).to eq true
4
4
  expect(Feature::Collection.enabled_for?(user: double(id: 5))).to eq true
@@ -1,4 +1,4 @@
1
- describe Feature::ComplexAnd do
1
+ describe "Feature::ComplexAnd" do
2
2
  specify do
3
3
  expect(Feature::ComplexAnd.enabled_for?(data: double(id: 1))).to eq true
4
4
  expect(Feature::ComplexAnd.enabled_for?(data: double(id: 2, timestamp: 0))).to eq true
@@ -1,4 +1,4 @@
1
- describe Feature::MultipleSubjects do
1
+ describe "Feature::MultipleSubjects" do
2
2
  specify do
3
3
  expect(Feature::MultipleSubjects.enabled_for?(
4
4
  user: double(id: 1, logged_in?: true), widget: double(id: 2))).to eq true
@@ -1,4 +1,4 @@
1
- describe Feature::NestedAttributes do
1
+ describe "Feature::NestedAttributes" do
2
2
  specify do
3
3
  expect(Feature::NestedAttributes.enabled_for?(
4
4
  foo: double(bar: :two, baz: double(id: 51)))).to eq true
@@ -1,4 +1,4 @@
1
- describe Feature::OrAttributes do
1
+ describe "Feature::OrAttributes" do
2
2
  specify do
3
3
  expect(Feature::OrAttributes.enabled_for?(
4
4
  user: double(foo: 20, bar: 10))).to eq true
@@ -1,4 +1,4 @@
1
- describe Feature::Type do
1
+ describe "Feature::Type" do
2
2
  specify do
3
3
  expect(Feature::Type.enabled_for?(user_id: 1)).to eq true
4
4
  expect(Feature::Type.enabled_for?(user_id: 25)).to eq false
@@ -0,0 +1,84 @@
1
+ describe Toggles do
2
+ describe "#init" do
3
+ include_context "uses temp dir"
4
+
5
+ it "correctly loads configuration" do
6
+ Dir.mkdir("#{temp_dir}/foo")
7
+ Dir.mkdir("#{temp_dir}/bar")
8
+
9
+ File.open("#{temp_dir}/foo/users.yml", "w") do |f|
10
+ f.write("{\"id\": {\"in\": [1, 2]}}")
11
+ end
12
+
13
+ File.open("#{temp_dir}/bar/users.yml", "w") do |f|
14
+ f.write("{\"id\": {\"in\": [3, 4]}}")
15
+ end
16
+
17
+ Toggles.configure do |c|
18
+ c.features_dir = temp_dir
19
+ end
20
+
21
+ expect(Feature::Foo::Users.enabled_for?(id: 1)).to eq(true)
22
+ expect(Feature::Bar::Users.enabled_for?(id: 3)).to eq(true)
23
+ end
24
+
25
+ it "reloads configuration when #init is called" do
26
+ Dir.mkdir("#{temp_dir}/foo")
27
+
28
+ File.open("#{temp_dir}/foo/users.yml", "w") do |f|
29
+ f.write("{\"id\": {\"in\": [1, 2]}}")
30
+ end
31
+ File.open("#{temp_dir}/foo/children.yml", "w") do |f|
32
+ f.write("{\"id\": {\"in\": [1, 2]}}")
33
+ end
34
+
35
+
36
+ Toggles.configure do |c|
37
+ c.features_dir = temp_dir
38
+ end
39
+
40
+ expect(Feature::Foo::Users.enabled_for?(id: 1)).to eq(true)
41
+ expect(Feature::Foo::Children.enabled_for?(id: 1)).to eq(true)
42
+
43
+ File.open("#{temp_dir}/foo/users.yml", "w") do |f|
44
+ f.write("{\"id\": {\"in\": [2]}}")
45
+ end
46
+
47
+ File.unlink("#{temp_dir}/foo/children.yml")
48
+
49
+ Toggles.init
50
+
51
+ expect(Feature::Foo::Users.enabled_for?(id: 1)).to eq(false)
52
+ expect { Feature::Foo::Children.enabled_for?(id: 1) }.to raise_error(NameError)
53
+ end
54
+ end
55
+
56
+ describe "#reinit_if_changed" do
57
+ include_context "uses temp dir"
58
+
59
+ it "reloads when the contents have changed" do
60
+ Dir.mkdir("#{temp_dir}/features")
61
+
62
+ Toggles.configure do |c|
63
+ c.features_dir = "#{temp_dir}/features"
64
+ end
65
+
66
+ Dir.delete("#{temp_dir}/features")
67
+ Dir.mkdir("#{temp_dir}/features")
68
+
69
+ expect(Toggles).to receive("init")
70
+ Toggles.reinit_if_changed
71
+ end
72
+
73
+ it "does not reload when the contents have not changed" do
74
+ Dir.mkdir("#{temp_dir}/features")
75
+
76
+ Toggles.configure do |c|
77
+ c.features_dir = "#{temp_dir}/features"
78
+ end
79
+
80
+ expect(Toggles).not_to receive("init")
81
+ Toggles.reinit_if_changed
82
+ end
83
+ end
84
+ end
@@ -1,13 +1,19 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "toggles"
3
- s.version = "0.1.1"
4
- s.authors = ["Andrew Tribone"]
3
+ s.version = "0.1.2"
4
+ s.authors = ["Andrew Tribone", "James Brown"]
5
5
  s.summary = "YAML backed feature toggles"
6
- s.email = "tribone@easypost.com"
7
- s.homepage = "https://github.com/att14/toggles"
8
- s.license = ""
6
+ s.email = "oss@easypost.com"
7
+ s.homepage = "https://github.com/EasyPost/toggles"
8
+ s.license = "ISC"
9
9
  s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
10
10
  s.test_files = s.files.grep(/^(spec)\//)
11
+ s.description = <<-EOF
12
+ YAML-backed implementation of the feature flags pattern. Build a
13
+ hierarchy of features in YAML files in the filesystem, apply various
14
+ conditions using boolean logic and a selection of filters, and easily
15
+ check whether a given feature should be applied.
16
+ EOF
11
17
 
12
18
  s.add_development_dependency "bundler"
13
19
  s.add_development_dependency "pry"
@@ -16,4 +22,5 @@ Gem::Specification.new do |s|
16
22
  s.add_development_dependency "rake"
17
23
  s.add_development_dependency "rspec"
18
24
  s.add_development_dependency "rspec-its"
25
+ s.add_development_dependency "rspec-temp_dir"
19
26
  end
metadata CHANGED
@@ -1,122 +1,143 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: toggles
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Tribone
8
+ - James Brown
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2015-08-25 00:00:00.000000000 Z
12
+ date: 2016-11-08 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: bundler
15
16
  requirement: !ruby/object:Gem::Requirement
16
17
  requirements:
17
- - - ">="
18
+ - - '>='
18
19
  - !ruby/object:Gem::Version
19
20
  version: '0'
20
21
  type: :development
21
22
  prerelease: false
22
23
  version_requirements: !ruby/object:Gem::Requirement
23
24
  requirements:
24
- - - ">="
25
+ - - '>='
25
26
  - !ruby/object:Gem::Version
26
27
  version: '0'
27
28
  - !ruby/object:Gem::Dependency
28
29
  name: pry
29
30
  requirement: !ruby/object:Gem::Requirement
30
31
  requirements:
31
- - - ">="
32
+ - - '>='
32
33
  - !ruby/object:Gem::Version
33
34
  version: '0'
34
35
  type: :development
35
36
  prerelease: false
36
37
  version_requirements: !ruby/object:Gem::Requirement
37
38
  requirements:
38
- - - ">="
39
+ - - '>='
39
40
  - !ruby/object:Gem::Version
40
41
  version: '0'
41
42
  - !ruby/object:Gem::Dependency
42
43
  name: pry-nav
43
44
  requirement: !ruby/object:Gem::Requirement
44
45
  requirements:
45
- - - ">="
46
+ - - '>='
46
47
  - !ruby/object:Gem::Version
47
48
  version: '0'
48
49
  type: :development
49
50
  prerelease: false
50
51
  version_requirements: !ruby/object:Gem::Requirement
51
52
  requirements:
52
- - - ">="
53
+ - - '>='
53
54
  - !ruby/object:Gem::Version
54
55
  version: '0'
55
56
  - !ruby/object:Gem::Dependency
56
57
  name: pry-remote
57
58
  requirement: !ruby/object:Gem::Requirement
58
59
  requirements:
59
- - - ">="
60
+ - - '>='
60
61
  - !ruby/object:Gem::Version
61
62
  version: '0'
62
63
  type: :development
63
64
  prerelease: false
64
65
  version_requirements: !ruby/object:Gem::Requirement
65
66
  requirements:
66
- - - ">="
67
+ - - '>='
67
68
  - !ruby/object:Gem::Version
68
69
  version: '0'
69
70
  - !ruby/object:Gem::Dependency
70
71
  name: rake
71
72
  requirement: !ruby/object:Gem::Requirement
72
73
  requirements:
73
- - - ">="
74
+ - - '>='
74
75
  - !ruby/object:Gem::Version
75
76
  version: '0'
76
77
  type: :development
77
78
  prerelease: false
78
79
  version_requirements: !ruby/object:Gem::Requirement
79
80
  requirements:
80
- - - ">="
81
+ - - '>='
81
82
  - !ruby/object:Gem::Version
82
83
  version: '0'
83
84
  - !ruby/object:Gem::Dependency
84
85
  name: rspec
85
86
  requirement: !ruby/object:Gem::Requirement
86
87
  requirements:
87
- - - ">="
88
+ - - '>='
88
89
  - !ruby/object:Gem::Version
89
90
  version: '0'
90
91
  type: :development
91
92
  prerelease: false
92
93
  version_requirements: !ruby/object:Gem::Requirement
93
94
  requirements:
94
- - - ">="
95
+ - - '>='
95
96
  - !ruby/object:Gem::Version
96
97
  version: '0'
97
98
  - !ruby/object:Gem::Dependency
98
99
  name: rspec-its
99
100
  requirement: !ruby/object:Gem::Requirement
100
101
  requirements:
101
- - - ">="
102
+ - - '>='
102
103
  - !ruby/object:Gem::Version
103
104
  version: '0'
104
105
  type: :development
105
106
  prerelease: false
106
107
  version_requirements: !ruby/object:Gem::Requirement
107
108
  requirements:
108
- - - ">="
109
+ - - '>='
109
110
  - !ruby/object:Gem::Version
110
111
  version: '0'
111
- description:
112
- email: tribone@easypost.com
112
+ - !ruby/object:Gem::Dependency
113
+ name: rspec-temp_dir
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: |2
127
+ YAML-backed implementation of the feature flags pattern. Build a
128
+ hierarchy of features in YAML files in the filesystem, apply various
129
+ conditions using boolean logic and a selection of filters, and easily
130
+ check whether a given feature should be applied.
131
+ email: oss@easypost.com
113
132
  executables: []
114
133
  extensions: []
115
134
  extra_rdoc_files: []
116
135
  files:
117
- - ".gitignore"
118
- - ".rspec"
136
+ - .gitignore
137
+ - .rspec
119
138
  - Gemfile
139
+ - LICENSE.txt
140
+ - README.md
120
141
  - Rakefile
121
142
  - features/collection.yml
122
143
  - features/complex_and.yml
@@ -158,10 +179,11 @@ files:
158
179
  - spec/toggles/feature/operation/range_spec.rb
159
180
  - spec/toggles/feature/permissions_spec.rb
160
181
  - spec/toggles/feature/subject_spec.rb
182
+ - spec/toggles/init_spec.rb
161
183
  - toggles.gemspec
162
- homepage: https://github.com/att14/toggles
184
+ homepage: https://github.com/EasyPost/toggles
163
185
  licenses:
164
- - ''
186
+ - ISC
165
187
  metadata: {}
166
188
  post_install_message:
167
189
  rdoc_options: []
@@ -169,17 +191,17 @@ require_paths:
169
191
  - lib
170
192
  required_ruby_version: !ruby/object:Gem::Requirement
171
193
  requirements:
172
- - - ">="
194
+ - - '>='
173
195
  - !ruby/object:Gem::Version
174
196
  version: '0'
175
197
  required_rubygems_version: !ruby/object:Gem::Requirement
176
198
  requirements:
177
- - - ">="
199
+ - - '>='
178
200
  - !ruby/object:Gem::Version
179
201
  version: '0'
180
202
  requirements: []
181
203
  rubyforge_project:
182
- rubygems_version: 2.2.2
204
+ rubygems_version: 2.0.14.1
183
205
  signing_key:
184
206
  specification_version: 4
185
207
  summary: YAML backed feature toggles
@@ -203,3 +225,4 @@ test_files:
203
225
  - spec/toggles/feature/operation/range_spec.rb
204
226
  - spec/toggles/feature/permissions_spec.rb
205
227
  - spec/toggles/feature/subject_spec.rb
228
+ - spec/toggles/init_spec.rb