fittings 0.2.0

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: 3d284c9153aca2da7768369111eece8dba1689fa
4
+ data.tar.gz: 32ceeb38ef5c2ddc8c2878f4c49ea3383de0118d
5
+ SHA512:
6
+ metadata.gz: a20d5db9cdc38c55707b843860eae271c47d211ff1220a2c043e8780965c3e3c3dc35de1c358614aaf587b3caddb541e2f8eba130994f0326c87bda4838911a9
7
+ data.tar.gz: e2a67f2e123d73f37ecacb303d9a451bca42fef4c7325cbb8c7233c0dbe35532e589943fdac110d88f394bd9c04e86a3c961891dd8782508756f4a941240a47b
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,34 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ fittings (0.2.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.3)
10
+ rake (12.1.0)
11
+ rspec (3.6.0)
12
+ rspec-core (~> 3.6.0)
13
+ rspec-expectations (~> 3.6.0)
14
+ rspec-mocks (~> 3.6.0)
15
+ rspec-core (3.6.0)
16
+ rspec-support (~> 3.6.0)
17
+ rspec-expectations (3.6.0)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.6.0)
20
+ rspec-mocks (3.6.0)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.6.0)
23
+ rspec-support (3.6.0)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ fittings!
30
+ rake
31
+ rspec
32
+
33
+ BUNDLED WITH
34
+ 1.15.4
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Edwin Cruz
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,175 @@
1
+ = Application Settings Manager
2
+
3
+ == Summary
4
+
5
+ This gem provides an easy and Capistrano-friendly way to manage application configuration across
6
+ multiple environments, such as development, QA, staging, production, etc.
7
+
8
+ Applications typically rely on configuration settings, such as host names, URLs, usernames and many
9
+ more. Some change between environments, some do not. This gem assumes that application configuration
10
+ is represented by a Hash of arbitrary depth, and provides convenient and compact syntax to access the
11
+ settings through a singleton instance inside Setting class.
12
+
13
+ Configuration is stored in one or more YAML files with the top-level data structure being a Hash,
14
+ with keys being the names of individual settings. For example, consider the following sample
15
+ application configuration file:
16
+
17
+ tax:
18
+ default: 0.0
19
+ california: 7.5
20
+ states:
21
+ default:
22
+ - 'CA'
23
+ - 'WA'
24
+ - 'NY'
25
+ ship_to:
26
+ - 'CA'
27
+ - 'NY'
28
+ math_pi: 3.14159526
29
+
30
+ Setting Gem provides Setting.load(..) method to load configuration from files in a way that allows
31
+ some configuration files to override previously loaded values, and then offers a simple method API
32
+ to access the values, for example Setting.tax(:california) or Setting.tax. Supporting default values
33
+ in 2nd, 3rd, .. - level hashes is one of the advantages of using this gem.
34
+
35
+ By loading configuration from YAML files, Setting gem is inherently compatible with Capistrano deployment
36
+ methodology, where a certain set of files may become "activated" by simply sym-linking them into
37
+ the appropriate settings folder.
38
+
39
+ Note: using example above, "1st level" hash is the one with keys "tax", "states" and "math_pi".
40
+ 2nd-level hash is, for example, the tax definition one, with keys "default" and "california".
41
+
42
+ == Usage in Code
43
+
44
+ Once configuration is initialized using Setting#load or Setting#reload methods (see below), they can be used in
45
+ code in the following way:
46
+
47
+ * Setting.key_name is optimized to return default value if available instead of a Hash.
48
+ * Setting.key_name(:sub_key_name) returns a value from the 2nd level hash.
49
+ * Setting.key_name(:sub_key_name, :sub_sub_key_name) returns value from the 3rd level hash if available. The algorithm is recursive, so only the maximum method stack depth will limit the number of nested hash values you can access this way.
50
+ * Special syntax Setting[:key_name], Setting[:key_name][:sub_key_name], etc also supported. This method, however, does not support default values (see below).
51
+
52
+ Method notation is recommended over square bracket notation for accessing single values. However,
53
+ square bracket notation may be useful when you want to fetch the entire 2nd level hash that
54
+ includes the default value, instead of the default value itself.
55
+
56
+ For example, given the above YAML file, you can access the settings in your code as follows:
57
+
58
+ Setting.tax => 0.0
59
+ Setting.tax(:california) => 7.5
60
+ Setting.math_pi => 3.14159526
61
+ Setting[:math_pi] => 3.14159526
62
+ Setting.states => [ 'CA', 'WA', 'NY' ]
63
+ Setting.states['ship_to'] => [ 'CA', 'NY' ]
64
+
65
+ Method-calling notation allows passing an array of keys to fetch a value from a nested hash.
66
+ This method also supports returning a default value, stored against the "default" key.
67
+
68
+ Setting.tax => 0.0
69
+
70
+ Square bracket syntax returns the actual nested hash, without any regard for the default value:
71
+
72
+ Setting[:tax] => { 'default' => 0.0, 'california' => 7.5 }
73
+
74
+ == Loading Settings
75
+
76
+ The gem should be initialized in your environment.rb (if using Rails), or in any other
77
+ application initialization block. Setting.load() method is provided for loading settings, and it
78
+ can be called only once in application lifecycle, or it will throw an exception. If you need to reload
79
+ settings completely, you can use reload() method with similar arguments.
80
+
81
+ Consider an example:
82
+
83
+ Setting.load(:path => "#{Rails.root}/config/settings",
84
+ :files => ["default.yml", "environments/#{Rails.env}.yml"],
85
+ :local => true)
86
+
87
+ The argument is an options hash that configures which YAML files to load, in what order, and from where.
88
+
89
+ * path specifies the "root" folder where settings files will be loaded from
90
+ * files is an array that lists file names relative to the :path. In the example above, settings folder contains subfolder "environments" where Rails-specific environment files are located (such as "development.yml", "staging.yml", "production.yml", etc)
91
+ * local can be optionally specified as a true value, and if specified Setting gem will load all *.yml files that live under the :path/local folder.
92
+
93
+ Below is list of YAML files loaded in order specified in the above example, assuming that "development" is
94
+ the Rails environment, and "local" folder exists with 3 additional YAML files in it:
95
+
96
+ config/settings/default.yml
97
+ config/settings/environments/development.yml
98
+ config/settings/local/authorize-net.yml
99
+ config/settings/local/paypal.yml
100
+ config/settings/local/other.yml
101
+
102
+ Each YML file defines a ruby Hash. During file loading, the hashes are merged,
103
+ so that values loaded in early files may be overwritten by values in subsequent
104
+ files. This is deliberate and by design: it allows you to create small "override"
105
+ files for each environment, or even each machine you want to deploy to. Exactly
106
+ how you split your application settings in files is up to you.
107
+
108
+ == Nested Hashes and Default Values
109
+
110
+ MC Setting gem provides a convenient way to access nested values, including full
111
+ support for the default values within nested hashes (as of 0.1.1).
112
+
113
+ Consider the following nested hash example:
114
+
115
+ default.yml:
116
+
117
+ services:
118
+ inventory:
119
+ url: http://ims.mycompany.com:3443/inventory_manager
120
+ name: Inventory Management
121
+ shipping:
122
+ url: http://ship.mycompany.com:3443/shipper
123
+ name: Shipping
124
+
125
+ Setting.load(:files => ['default.yml'], :path => ...)
126
+
127
+ Setting.services(:inventory) => { :url => "http://localhost:3443/inventory_manager" :name => "Inventory Management"}
128
+ Setting.services(:inventory, :url) => "http://localhost:3443/inventory_manager"
129
+
130
+ staging.yml
131
+
132
+ We are changing URLs for services in staging.yml, so they work in the staging environment. Service URLs have been updated
133
+ to use localhost:
134
+
135
+ services:
136
+ inventory:
137
+ url: http://localhost:8009/inventory_manager
138
+ shipping:
139
+ url: http://localhost:8008/shipper
140
+
141
+
142
+ Setting.load(:files => ['default.yml', 'staging.yml'], :path => ...)
143
+
144
+ Setting.services(:inventory) => { :url => "http://localhost:8009/inventory_manager" :name => "Inventory Management"}
145
+ Setting.services(:inventory, :url) => "http://localhost:8008/inventory_manager"
146
+
147
+ == Capistrano Recommendations
148
+
149
+ Assume the directory structure of your Rails application is as follows:
150
+
151
+ config/settings/default.yml
152
+ config/settings/environments/development.yml
153
+ config/settings/environments/staging.yml
154
+ config/settings/environments/production.yml
155
+ config/settings/local
156
+ config/settings/systems/reporting.yml
157
+ config/settings/systems/admin.yml
158
+
159
+ Note that the "local" directory is empty, and that "systems" directory contains several YAML files that provide alternative
160
+ configuration for a reporting server, and an admin server (both of which run in "production" rails environment).
161
+
162
+ When deploying to the main production site, neither YAML files inside "systems" folder are activated or used.
163
+
164
+ But upon deployment to the admin server, Capistrano could symlink "admin.yml" from config/settings/local folder, so the Setting gem
165
+ would load these values. So for each Capistrano role, you can define which files need to be symlinked into local, thus creating
166
+ a very flexible configuration scheme that's easily managed by Capistrano.
167
+
168
+ == Copyright
169
+
170
+ Copyright 2010 (c) ModCloth Inc.
171
+
172
+ Authors: 2010 Edwin Cruz & Konstantin Gredeskoul
173
+
174
+ See LICENSE.txt for further details.
175
+
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rubygems/package_task'
3
+ require 'rspec/core/rake_task'
4
+
5
+ $: << File.join(File.dirname(__FILE__),'lib')
6
+
7
+ include Rake::DSL
8
+
9
+ gemspec = eval(File.read('fittings.gemspec'))
10
+ Gem::PackageTask.new(gemspec) {}
11
+ RSpec::Core::RakeTask.new(:spec)
12
+ Bundler::GemHelper.install_tasks
13
+
14
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.6
data/fittings.gemspec ADDED
@@ -0,0 +1,50 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "fittings"
8
+ s.version = "0.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Edwin Cruz", "Colin Shield"]
12
+ s.date = %q{2011-09-06}
13
+ s.description = %q{implement custom keys independently of environment}
14
+ s.email = %q{eng@stitchfix.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE.txt",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "lib/mc-settings.rb",
28
+ "lib/setting.rb",
29
+ "fittings.gemspec",
30
+ "spec/fixtures/joes-colors.yml",
31
+ "spec/fixtures/sample.yml",
32
+ "spec/fixtures/shipping.yml",
33
+ "spec/mc_settings_spec.rb",
34
+ "spec/spec_helper.rb",
35
+ "spec/support/settings_helper.rb"
36
+ ]
37
+ s.homepage = %q{http://github.com/stitchfix/fittings}
38
+ s.licenses = ["MIT"]
39
+ s.require_paths = ["lib"]
40
+ s.summary = %q{Manage settings per environment}
41
+ s.test_files = [
42
+ "spec/mc_settings_spec.rb",
43
+ "spec/spec_helper.rb",
44
+ "spec/support/settings_helper.rb"
45
+ ]
46
+
47
+ s.add_development_dependency "rspec"
48
+ s.add_development_dependency "rake"
49
+ end
50
+
@@ -0,0 +1 @@
1
+ require File.expand_path(File.dirname(__FILE__)) + '/setting'
data/lib/setting.rb ADDED
@@ -0,0 +1,187 @@
1
+ require 'singleton'
2
+ require 'yaml'
3
+ require 'erb'
4
+
5
+ class Hash
6
+ def recursive_merge!(other)
7
+ other.keys.each do |k|
8
+ if self[k].is_a?(Array) && other[k].is_a?(Array)
9
+ self[k] = other[k]
10
+ elsif self[k].is_a?(Hash) && other[k].is_a?(Hash)
11
+ self[k].recursive_merge!(other[k])
12
+ else
13
+ self[k] = other[k]
14
+ end
15
+ end
16
+ self
17
+ end
18
+ end
19
+
20
+ class Setting
21
+ class NotFound < RuntimeError; end
22
+ class FileError < RuntimeError; end
23
+ class AlreadyLoaded < RuntimeError; end
24
+
25
+ include Singleton
26
+ NUM_KLASS = if RUBY_VERSION.split(/\./)[0].to_i == 2 && RUBY_VERSION.split(/\./)[1].to_i >= 4
27
+ Integer
28
+ else
29
+ Fixnum
30
+ end
31
+
32
+ attr_reader :available_settings
33
+
34
+ # This method can be called only once.
35
+ #
36
+ # Parameter hash looks like this:
37
+ #
38
+ # { :files => [ "file1.yml", "file2.yml", ...],
39
+ # :path => "/var/www/apps/my-app/current/config/settings",
40
+ # :local => true }
41
+ #
42
+ # If :local => true is set, we will load all *.yml files under :path/local directory
43
+ # after all files in :files have been loaded. "Local" settings thus take precedence
44
+ # by design. See README for more details.
45
+ #
46
+ def self.load(args = {})
47
+ raise AlreadyLoaded.new('Settings already loaded') if self.instance.loaded?
48
+ self.instance.load(args)
49
+ end
50
+
51
+ def self.reload(args = {})
52
+ self.instance.load(args)
53
+ end
54
+
55
+ # In Method invocation syntax we collapse Hash values
56
+ # and return a single value if 'default' is found among keys
57
+ # or Hash has only one key/value pair.
58
+ #
59
+ # For example, if the YML data is:
60
+ # tax:
61
+ # default: 0.0
62
+ # california: 7.5
63
+ #
64
+ # Then calling Setting.tax returns "0.0""
65
+ #
66
+ # This is the preferred method of using settings class.
67
+ #
68
+ def self.method_missing(method, *args, &block)
69
+ self.instance.value_for(method, args) do |v, args|
70
+ self.instance.collapse_hashes(v, args)
71
+ end
72
+ end
73
+
74
+ # In [] invocation syntax, we return settings value 'as is' without
75
+ # Hash conversions.
76
+ #
77
+ # For example, if the YML data is:
78
+ # tax:
79
+ # default: 0.0
80
+ # california: 7.5
81
+ #
82
+ # Then calling Setting['tax'] returns
83
+ # { 'default' => "0.0", 'california' => "7.5"}
84
+
85
+ def self.[](value)
86
+ self.instance.value_for(value)
87
+ end
88
+
89
+ # <b>DEPRECATED:</b> Please use <tt>method accessors</tt> instead.
90
+ def self.available_settings
91
+ self.instance.available_settings
92
+ end
93
+
94
+ #=================================================================
95
+ # Instance Methods
96
+ #=================================================================
97
+
98
+ def initialize
99
+ @available_settings ||= {}
100
+ end
101
+
102
+ def has_key?(key)
103
+ @available_settings.has_key?(key) ||
104
+ (key[-1,1] == '?' && @available_settings.has_key?(key.chop))
105
+ end
106
+
107
+ def value_for(key, args = [])
108
+ name = key.to_s
109
+ raise NotFound.new("#{name} was not found") unless has_key?(name)
110
+ bool = false
111
+ if name[-1,1] == '?'
112
+ name.chop!
113
+ bool = true
114
+ end
115
+
116
+ v = @available_settings[name]
117
+ if block_given?
118
+ v = yield(v, args)
119
+ end
120
+
121
+
122
+ if v.is_a?(NUM_KLASS) && bool
123
+ v.to_i > 0
124
+ else
125
+ v
126
+ end
127
+ end
128
+
129
+ # This method performs collapsing of the Hash settings values if the Hash
130
+ # contains 'default' value, or just 1 element.
131
+
132
+ def collapse_hashes(v, args)
133
+ out = if v.is_a?(Hash)
134
+ if args.empty?
135
+ if v.has_key?("default")
136
+ v['default'].nil? ? "" : v['default']
137
+ elsif v.keys.size == 1
138
+ v.values.first
139
+ else
140
+ v
141
+ end
142
+ else
143
+ v[args.shift.to_s]
144
+ end
145
+ else
146
+ v
147
+ end
148
+ if out.is_a?(Hash) && !args.empty?
149
+ collapse_hashes(out, args)
150
+ elsif out.is_a?(Hash) && out.has_key?('default')
151
+ out['default']
152
+ else
153
+ out
154
+ end
155
+ end
156
+
157
+ def loaded?
158
+ @loaded
159
+ end
160
+
161
+ def load(params)
162
+ # reset settings hash
163
+ @available_settings = {}
164
+ @loaded = false
165
+
166
+ files = []
167
+ path = params[:path] || Dir.pwd
168
+ params[:files].each do |file|
169
+ files << File.join(path, file)
170
+ end
171
+
172
+ if params[:local]
173
+ files << Dir.glob(File.join(path, 'local', '*.yml')).sort
174
+ end
175
+
176
+ files.flatten.each do |file|
177
+ begin
178
+ @available_settings.recursive_merge!(YAML::load(ERB.new(IO.read(file)).result) || {}) if File.exists?(file)
179
+ rescue Exception => e
180
+ raise FileError.new("Error parsing file #{file}, with: #{e.message}")
181
+ end
182
+ end
183
+
184
+ @loaded = true
185
+ @available_settings
186
+ end
187
+ end
@@ -0,0 +1,9 @@
1
+ color:
2
+ pants:
3
+ favorite:
4
+ :orange
5
+ shorts:
6
+ favorite:
7
+ :white
8
+ default:
9
+ :stripes
@@ -0,0 +1,35 @@
1
+ tax:
2
+ default: 0.0
3
+ california: 7.5
4
+ states:
5
+ default:
6
+ - 'CA'
7
+ - 'WA'
8
+ - 'NY'
9
+ ship_to:
10
+ - 'CA'
11
+ - 'NY'
12
+ boolean_true:
13
+ default: 4
14
+ boolean_false:
15
+ default: false
16
+ negated: true
17
+ color:
18
+ default:
19
+ :grey
20
+ pants:
21
+ default:
22
+ :purple
23
+ favorite:
24
+ :red
25
+ school:
26
+ :blue
27
+ shorts:
28
+ default:
29
+ :plaid
30
+ favorite:
31
+ :yellow
32
+ school:
33
+ :black
34
+
35
+
@@ -0,0 +1,36 @@
1
+ shipping_config:
2
+ default: <%= 'Defaulted' %>
3
+ domestic:
4
+ service: MyService
5
+ countries:
6
+ - "US"
7
+ non_shippable_regions:
8
+ - "US-AS"
9
+ - "US-GU"
10
+ - "US-MP"
11
+ - "US-PR"
12
+ - "US-UM"
13
+ - "US-VI"
14
+ - "US-AF"
15
+ - "US-AA"
16
+ - "US-AC"
17
+ - "US-AE"
18
+ - "US-AM"
19
+ - "US-AP"
20
+ - "US-FM"
21
+ - "US-PW"
22
+ - "US-MH"
23
+ international:
24
+ service: Foo
25
+ countries:
26
+ - "GU"
27
+ - "VI"
28
+ - "AS"
29
+ - "MP"
30
+ - "UM"
31
+ - "FR"
32
+ - "GR"
33
+ - "RU"
34
+ shipping_carrier: Bar
35
+ number: <%= 5%>
36
+ stringified: <%= nil || "stringified"%>
@@ -0,0 +1,160 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ describe Setting do
3
+ subject { Setting }
4
+
5
+ context "Test with stubs" do
6
+ before :each do
7
+ stub_setting_files
8
+ subject.reload(
9
+ :path => "config/settings",
10
+ :files => ["default.yml", "environments/test.yml"],
11
+ :local => true)
12
+ end
13
+
14
+ it 'should return test specific values' do
15
+ expect(subject.available_settings['one']).to eq("test")
16
+ expect(subject.one).to eq("test")
17
+ expect(subject['one']).to eq("test")
18
+ end
19
+
20
+ it "should handle custom values overriding everything else" do
21
+ expect(subject.seven).to eq("seven from custom")
22
+ end
23
+
24
+ it "handles multiple values" do
25
+ expect(subject[:six]).to eq({"default"=>"default value", "extra"=>"recursively overriden", "deep_level"=>{"value"=>"even deeper level"}})
26
+ expect(subject.available_settings['six']['default']).to eq("default value")
27
+ expect(subject.seven).to eq("seven from custom")
28
+ end
29
+
30
+ it "handles default key" do
31
+ expect(subject.default_setting).to eq(1)
32
+ expect(subject['seven']['default']).to eq("seven from custom")
33
+ end
34
+
35
+ it "should handle empty strings" do
36
+ expect(subject.empty).to eq("")
37
+ end
38
+
39
+ it "should responds to ? mark" do
40
+ expect(subject.autologin?).to eq(true)
41
+ end
42
+
43
+ it "should returns false correctly" do
44
+ expect(subject.flag_false).to be(false)
45
+ end
46
+
47
+ it "should merge keys recursivelly" do
48
+ expect(subject.six(:extra)).to eq("recursively overriden")
49
+ expect(subject.six(:deep_level, :value)).to eq("even deeper level")
50
+ end
51
+
52
+ it "should create keys if it does not exist" do
53
+ expect(subject.test_specific).to eq("exist")
54
+ end
55
+
56
+ context "working with arrays" do
57
+ it "should replace the whole array instead of appending new values" do
58
+ expect(subject.nested_array).to eq(['first', 'four', 'five'])
59
+ end
60
+ end
61
+ end
62
+
63
+ context "When running with threads" do
64
+ it "should keep its values" do
65
+ 3.times do |time|
66
+ Thread.new {
67
+ subject.available_settings.shoud_not be_empty
68
+ }
69
+ end
70
+ end
71
+ end
72
+
73
+ context "Test from file" do
74
+ before :each do
75
+ subject.reload(
76
+ :path => File.join(File.dirname(__FILE__)) + '/fixtures',
77
+ :files => ['sample.yml']
78
+ )
79
+ end
80
+
81
+ it 'should support [] syntax' do
82
+ expect(subject['tax']['default']).to eq(0.0)
83
+ expect(subject['tax']).to eq({ 'default' => 0.0, 'california' => 7.5 })
84
+ end
85
+
86
+ it 'should support method invocation syntax' do
87
+ expect(subject.tax).to eq(0.0)
88
+
89
+ expect(subject.tax(:default)).to eq(subject.tax)
90
+ expect(subject.tax('default')).to eq(subject.tax)
91
+ expect(subject.tax(:california)).to eq(7.5)
92
+
93
+ expect(subject.states).to eq(['CA', 'WA', 'NY'])
94
+ expect(subject.states(:default)).to eq(subject.states)
95
+ expect(subject.states(:ship_to)).to eq(['CA', 'NY'])
96
+ end
97
+
98
+ it 'should correctly process Boolean values' do
99
+ expect(subject.boolean_true?).to be(true)
100
+ expect(subject.boolean_true).to eq(4)
101
+ expect(subject.boolean_false?).to be(false)
102
+ expect(subject.boolean_false?(:default)).to be(false)
103
+ expect(subject.boolean_false?(:negated)).to be(true)
104
+ end
105
+ end
106
+
107
+ context "Test recursive overrides and nested hashes" do
108
+ before :each do
109
+ subject.reload(
110
+ :path => File.join(File.dirname(__FILE__)) + '/fixtures',
111
+ :files => ['sample.yml', 'joes-colors.yml']
112
+ )
113
+ end
114
+
115
+ it 'should override colors with Joes and support nested hashes' do
116
+ expect(subject.color).to eq(:grey) # default
117
+ expect(subject.color(:pants)).to eq(:purple) # default
118
+
119
+ expect(subject.color(:pants, :school)).to eq(:blue) # in sample
120
+ expect(subject.color(:pants, :favorite)).to eq(:orange) # joes override
121
+
122
+ expect(subject.color(:shorts, :school)).to eq(:black) # in sample
123
+ expect(subject.color(:shorts, :favorite)).to eq(:white) # joe's override
124
+
125
+ expect(subject.color(:shorts)).to eq(:stripes) # joe's override of default
126
+ end
127
+
128
+ end
129
+ context "Complex nested configs" do
130
+ before :each do
131
+ subject.reload(
132
+ :path => File.join(File.dirname(__FILE__)) + '/fixtures',
133
+ :files => ['shipping.yml']
134
+ )
135
+ end
136
+ it "should build correct tree with arrays and default values " do
137
+ expect(subject.shipping_config).to eq("Defaulted")
138
+ expect(subject.shipping_config(:domestic, :non_shippable_regions).first).to eq("US-AS")
139
+ expect(subject.shipping_config(:international, :service)).to eq('Foo')
140
+ expect(subject.shipping_config(:international, :countries).size).to be > 0
141
+ expect(subject.shipping_config(:international, :shipping_carrier)).to eq('Bar')
142
+ #backward compatibility:
143
+ expect(subject.shipping_config(:domestic)['non_shippable_regions'].size).to be > 0
144
+ end
145
+ end
146
+
147
+ context "Ruby code inside yml file" do
148
+ before :each do
149
+ subject.reload(
150
+ :path => File.join(File.dirname(__FILE__)) + '/fixtures',
151
+ :files => ['shipping.yml']
152
+ )
153
+ end
154
+ it "should interpret ruby code and put correct values" do
155
+ expect(subject.shipping_config).to eq("Defaulted")
156
+ subject.number == 5
157
+ subject.stringified == "stringified"
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'mc-settings'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
@@ -0,0 +1,59 @@
1
+ def stub_setting_files
2
+ defaults = <<-CONTENT
3
+ one: default
4
+ two:
5
+ three: 3
6
+ four: "4"
7
+ five: "default string"
8
+ default_setting: 1
9
+ six:
10
+ default: "default value"
11
+ extra: "extra"
12
+ deep_level:
13
+ value: "even deeper level"
14
+ seven:
15
+ default: "seven"
16
+ empty:
17
+ default:
18
+ autologin:
19
+ format: int
20
+ default: 7
21
+ flag_false:
22
+ default: false
23
+ nested_array:
24
+ - first
25
+ - second
26
+ - third
27
+ CONTENT
28
+ test = <<-CONTENT
29
+ one: test
30
+ two:
31
+ three: 5
32
+ four: "6"
33
+ five: "test string"
34
+ six:
35
+ extra: "recursively overriden"
36
+ test_specific: "exist"
37
+ nested_array:
38
+ - first
39
+ - four
40
+ - five
41
+ CONTENT
42
+
43
+ empty = <<-CONTENT
44
+ CONTENT
45
+
46
+ custom = <<-CONTENT
47
+ seven:
48
+ default: "seven from custom"
49
+ CONTENT
50
+
51
+ allow(File).to receive(:exists?).and_return(true)
52
+ allow(File).to receive(:exists?).with("config/settings/environments/development.yml").and_return(false)
53
+ allow(IO).to receive(:read).with("config/settings/default.yml").and_return(defaults)
54
+ allow(IO).to receive(:read).with("config/settings/environments/test.yml").and_return(test)
55
+ allow(IO).to receive(:read).with("config/settings/local/custom.yml").and_return(custom)
56
+ allow(IO).to receive(:read).with("config/settings/local/empty.yml").and_return(empty)
57
+
58
+ allow(Dir).to receive(:glob).and_return(["config/settings/local/empty.yml", "config/settings/local/custom.yml"])
59
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fittings
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Edwin Cruz
8
+ - Colin Shield
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-09-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ description: implement custom keys independently of environment
43
+ email: eng@stitchfix.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files:
47
+ - LICENSE.txt
48
+ - README.rdoc
49
+ files:
50
+ - ".document"
51
+ - Gemfile
52
+ - Gemfile.lock
53
+ - LICENSE.txt
54
+ - README.rdoc
55
+ - Rakefile
56
+ - VERSION
57
+ - fittings.gemspec
58
+ - lib/mc-settings.rb
59
+ - lib/setting.rb
60
+ - spec/fixtures/joes-colors.yml
61
+ - spec/fixtures/sample.yml
62
+ - spec/fixtures/shipping.yml
63
+ - spec/mc_settings_spec.rb
64
+ - spec/spec_helper.rb
65
+ - spec/support/settings_helper.rb
66
+ homepage: http://github.com/stitchfix/fittings
67
+ licenses:
68
+ - MIT
69
+ metadata: {}
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 2.6.13
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: Manage settings per environment
90
+ test_files:
91
+ - spec/mc_settings_spec.rb
92
+ - spec/spec_helper.rb
93
+ - spec/support/settings_helper.rb