mc-settings 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,53 +1,82 @@
1
- = Settings
1
+ = Application Settings Manager
2
2
 
3
- Application Settings Manager
3
+ This gem provides an easy and capistrano-friendly way to manage application configuration across
4
+ multiple environments, such as development, QA, staging, production, etc.
4
5
 
5
- == Usage in Rails
6
- Settings conventions
7
- key_name:
8
- default: value
9
- key_name: value
10
- multiples_values:
11
- default: default
12
- another_value: another_default
6
+ Applications typically rely on configuration settings, such as host names, URLs, usernames and many
7
+ more. Some change between environemnts, some do not. This gem makes managing this hierarchy of
8
+ settings values easy and provides convenient and compact syntax to access the settings.
13
9
 
10
+ Configuration is stored in YAML files, in the following format, with it's top level structure being a hash with keys being the names of individual settings. For example:
14
11
 
15
- config/settings/default.yml
16
- config/settings/environments/development.yml
17
- config/settings/environments/production.yml
18
- config/settings/local/custom.yml
19
- config/settings/local/verysecret.yml
12
+ tax:
13
+ default: 0.0
14
+ california: 7.5
15
+ states:
16
+ default:
17
+ - 'CA'
18
+ - 'WA'
19
+ - 'NY'
20
+ ship_to:
21
+ - 'CA'
22
+ - 'NY'
23
+ math_pi: 3.14159526
20
24
 
21
- Setting.load(:files => ["default.yml", "environments/#{Rails.env}.yml"],
22
- :path => "#{Rails.root}/config/settings",
23
- :local => true)
25
+ etc.
26
+
27
+ == Usage
28
+
29
+ Once settings are initialized (see below), they can be used in code in the following way:
30
+
31
+ * Setting.key_name is optimized to return default value if available.
32
+ * Setting.key_name(:sub_key_name) returns value from the 2nd level hash if available.
33
+ * Setting[:key_name] & Setting['key_name'] return entire 2nd-level hash without any regard for whether default value exists.
34
+
35
+ For example, given the above YAML file:
36
+
37
+ Setting.tax => 0.0
38
+ Setting.tax(:california) => 7.5
39
+ Setting[:tax] => { 'default' => 0.0, 'california' => 7.5 }
40
+ Setting.states => [ 'CA', 'WA', 'NY' ]
41
+ Setting.states['ship_to'] => [ 'CA', 'NY' ]
42
+
43
+ The following usage is supported for backwards compatibility, but is deprecated and should
44
+ not be used for new projects:
24
45
 
25
- Setting.key_name
26
46
  Setting.available_settings['key_name']
27
- Setting.available_settings['key_name']['another_value']
28
- Setting['key_name']
29
- Setting[:key_name]
30
- Setting['multiples_values']['another_value']
47
+ Setting.available_settings['key_name']['sub_key_name']
31
48
 
49
+ == Settings Loading
32
50
 
33
- This means:
34
- * Read default.yml, and load all setings in Setting object
35
- * Override specific keys depending on rails environment
36
- * Apply all #{path}/local/*.yml files to Settings, overriding common keys
51
+ This gem should be initialized in your environment.rb (if using Rails), or in any other
52
+ application initialization block.
37
53
 
54
+ Consider an example:
38
55
 
39
- == Contributing to mc-settings
40
-
41
- * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
42
- * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
43
- * Fork the project
44
- * Start a feature/bugfix branch
45
- * Commit and push until you are happy with your contribution
46
- * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
47
- * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
56
+ Setting.load(:files => ["default.yml", "environments/#{Rails.env}.yml"],
57
+ :path => "#{Rails.root}/config/settings",
58
+ :local => true)
59
+
60
+ This parameter hash tells Setting to load settings hashes, in order, from the following
61
+ files under "development" (assuming you have two files "custom.yml" and "secret.yml" under your
62
+ RAILS_ROOT/config/settings/local folder):
63
+
64
+ config/settings/default.yml
65
+ config/settings/environments/development.yml
66
+ config/settings/local/custom.yml
67
+ config/settings/local/secret.yml
68
+
69
+ The following is the sequence of loading explained:
70
+
71
+ * Read all files in :files list in order, and load their settings.
72
+ * Last file loaded overrides previous value (if already exist).
73
+ * Load all #{path}/local/*.yml files to Settings, overriding common keys. Files are loaded in order sorted by name.
48
74
 
49
75
  == Copyright
50
76
 
51
- Copyright (c) 2010 Edwin Cruz. See LICENSE.txt for
52
- further details.
77
+ Copyright 2010 (c) ModCloth Inc.
78
+
79
+ Authors: 2010 Edwin Cruz & Konstantin Gredeskoul
80
+
81
+ See LICENSE.txt for further details.
53
82
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.1.0
data/lib/mc-settings.rb CHANGED
@@ -1 +1 @@
1
- require 'setting'
1
+ require File.expand_path(File.dirname(__FILE__)) + '/setting'
data/lib/setting.rb CHANGED
@@ -1,84 +1,159 @@
1
1
  require 'singleton'
2
+ require 'yaml'
2
3
  class Setting
3
- class SettingNotFound < RuntimeError; end
4
- class SettingFileError < RuntimeError; end
4
+ class NotFound < RuntimeError; end
5
+ class FileError < RuntimeError; end
6
+ class AlreadyLoaded < RuntimeError; end
5
7
 
6
8
  include Singleton
7
9
 
8
- def self.reload params = {}
9
- @available_settings = {}
10
- self.load params
11
- end
10
+ attr_reader :available_settings
12
11
 
13
- def self.available_settings
14
- self.instance ? @available_settings : {}
12
+ # This method can be called only once.
13
+ #
14
+ # Parameter hash looks like this:
15
+ #
16
+ # { :files => [ "file1.yml", "file2.yml", ...],
17
+ # :path => "/var/www/apps/my-app/current/config/settings",
18
+ # :local => true }
19
+ #
20
+ # If :local => true is set, we will load all *.yml files under :path/local directory
21
+ # after all files in :files have been loaded. "Local" settings thus take precedence
22
+ # by design. See README for more details.
23
+ #
24
+ def self.load(args = {})
25
+ raise AlreadyLoaded.new('Settings already loaded') if self.instance.loaded?
26
+ self.instance.load(args)
15
27
  end
16
28
 
17
- # get a setting value by [] notation
18
- def self.[](name)
19
- self.check_value(name.to_s)
20
- self.value_for(name.to_s)
29
+ def self.reload(args = {})
30
+ self.instance.load(args)
21
31
  end
22
32
 
33
+ # In Method invocation syntax we collapse Hash values
34
+ # and return a single value if 'default' is found among keys
35
+ # or Hash has only one key/value pair.
36
+ #
37
+ # For example, if the YML data is:
38
+ # tax:
39
+ # default: 0.0
40
+ # california: 7.5
41
+ #
42
+ # Then calling Setting.tax returns "0.0""
43
+ #
44
+ # This is the preferred method of using settings class.
45
+ #
23
46
  def self.method_missing(method, *args, &block)
24
- # see if this method is defined above us in the hierarchy
25
- super(method, *args)
26
- rescue
27
- name = method.to_s
28
- if name[-1, 1] == "?"
29
- name.chomp!('?')
30
- self[name]['default'].to_i > 0
31
- else
32
- self[name]
47
+ self.instance.value_for(method, args) do |v, args|
48
+ self.instance.collapse_hashes(v, args)
33
49
  end
34
50
  end
35
51
 
52
+ # In [] invocation syntax, we return settings value 'as is' without
53
+ # Hash conversions.
54
+ #
55
+ # For example, if the YML data is:
56
+ # tax:
57
+ # default: 0.0
58
+ # california: 7.5
59
+ #
60
+ # Then calling Setting['tax'] returns
61
+ # { 'default' => "0.0", 'california' => "7.5"}
62
+
63
+ def self.[](value)
64
+ self.instance.value_for(value)
65
+ end
66
+
67
+ # <b>DEPRECATED:</b> Please use <tt>method accessors</tt> instead.
68
+ def self.available_settings
69
+ self.instance.available_settings
70
+ end
71
+
72
+ #=================================================================
73
+ # Instance Methods
74
+ #=================================================================
75
+
36
76
  def initialize
37
77
  @available_settings ||= {}
38
78
  end
39
79
 
40
- class << self
41
- def has_key?(key)
42
- @available_settings.has_key?(key)
80
+ def has_key?(key)
81
+ @available_settings.has_key?(key) ||
82
+ (key[-1,1] == '?' && @available_settings.has_key?(key.chop))
83
+ end
84
+
85
+ def value_for(key, args = [])
86
+ name = key.to_s
87
+ raise NotFound.new("#{name} was not found") unless has_key?(name)
88
+ bool = false
89
+ if name[-1,1] == '?'
90
+ name.chop!
91
+ bool = true
92
+ end
93
+
94
+ v = @available_settings[name]
95
+ if block_given?
96
+ v = yield(v, args)
97
+ end
98
+
99
+ if v.is_a?(Fixnum) && bool
100
+ v.to_i > 0
101
+ else
102
+ v
43
103
  end
104
+ end
44
105
 
45
- def value_for(value)
46
- v = @available_settings[value]
47
- if v.is_a?(Hash) && v.size > 1
48
- v
49
- elsif v.is_a?(Hash) && v.has_key?("default")
50
- v['default'].nil? ? "" : v['default']
106
+ # This method performs collapsing of the Hash settings values if the Hash
107
+ # contains 'default' value, or just 1 element.
108
+
109
+ def collapse_hashes(v, args)
110
+ if v.is_a?(Hash)
111
+ if args.empty?
112
+ if v.has_key?("default")
113
+ v['default'].nil? ? "" : v['default']
114
+ elsif v.keys.size == 1
115
+ v.values.first
116
+ else
117
+ v
118
+ end
51
119
  else
52
- v
120
+ v[args[0].to_s]
53
121
  end
122
+ else
123
+ v
54
124
  end
55
125
  end
126
+
127
+ def loaded?
128
+ @loaded
129
+ end
130
+
131
+ def load(params)
132
+ # reset settings hash
133
+ @available_settings = {}
134
+ @loaded = false
56
135
 
57
- def self.load(params)
58
136
  files = []
59
- path = params[:path]
137
+ path = params[:path] || Dir.pwd
60
138
  params[:files].each do |file|
61
139
  files << File.join(path, file)
62
140
  end
141
+
63
142
  if params[:local]
64
143
  files << Dir.glob(File.join(path, 'local', '*.yml'))
65
144
  end
66
- @available_settings ||= {}
145
+
67
146
  files.flatten.each do |file|
68
147
  begin
69
148
  @available_settings.merge!(YAML::load(File.open(file)) || {}) if File.exists?(file)
70
149
  rescue Exception => e
71
- raise SettingNotFound.new("Error parsing file #{file}, with: #{e.message}")
150
+ raise FileError.new("Error parsing file #{file}, with: #{e.message}")
72
151
  end
73
152
  end
153
+
154
+ @loaded = true
74
155
  @available_settings
75
156
  end
76
157
 
77
- private
78
-
79
- def self.check_value(name)
80
- raise RuntimeError.new("settings are not yet initialized") unless self.instance
81
- raise SettingNotFound.new("#{name} not found") unless self.has_key?(name)
82
- end
83
158
 
84
- end
159
+ end
@@ -0,0 +1,71 @@
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 = %q{mc-settings}
8
+ s.version = "0.1.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{2010-12-28}
13
+ s.description = %q{implement custom keys indenendently of environment}
14
+ s.email = %q{rubydev@modcloth.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
+ "mc-settings.gemspec",
30
+ "spec/fixtures/sample.yml",
31
+ "spec/mc_settings_spec.rb",
32
+ "spec/spec_helper.rb",
33
+ "spec/support/settings_helper.rb"
34
+ ]
35
+ s.homepage = %q{http://github.com/modcloth/mc-settings}
36
+ s.licenses = ["MIT"]
37
+ s.require_paths = ["lib"]
38
+ s.rubygems_version = %q{1.3.7}
39
+ s.summary = %q{Manage settings per environment}
40
+ s.test_files = [
41
+ "spec/mc_settings_spec.rb",
42
+ "spec/spec_helper.rb",
43
+ "spec/support/settings_helper.rb"
44
+ ]
45
+
46
+ if s.respond_to? :specification_version then
47
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
48
+ s.specification_version = 3
49
+
50
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
51
+ s.add_development_dependency(%q<rspec>, [">= 0"])
52
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
53
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
54
+ s.add_development_dependency(%q<rcov>, [">= 0"])
55
+ s.add_development_dependency(%q<ruby-debug>, [">= 0"])
56
+ else
57
+ s.add_dependency(%q<rspec>, [">= 0"])
58
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
59
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
60
+ s.add_dependency(%q<rcov>, [">= 0"])
61
+ s.add_dependency(%q<ruby-debug>, [">= 0"])
62
+ end
63
+ else
64
+ s.add_dependency(%q<rspec>, [">= 0"])
65
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
66
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
67
+ s.add_dependency(%q<rcov>, [">= 0"])
68
+ s.add_dependency(%q<ruby-debug>, [">= 0"])
69
+ end
70
+ end
71
+
@@ -0,0 +1,16 @@
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
@@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
  describe Setting do
3
3
  subject { Setting }
4
4
 
5
- context "Test environment" do
5
+ context "Test with stubs" do
6
6
  before :each do
7
7
  stub_setting_files
8
8
  Setting.reload(
@@ -10,6 +10,7 @@ describe Setting do
10
10
  :path => "config/settings",
11
11
  :local => true)
12
12
  end
13
+
13
14
  it 'should return test specific values' do
14
15
  Setting.available_settings['one'].should == "test"
15
16
  Setting.one.should == "test"
@@ -30,9 +31,10 @@ describe Setting do
30
31
  it "should support symbols as keys" do
31
32
  Setting[:six].should == {"default"=>"default value", "extra"=>"extra"}
32
33
  end
34
+
33
35
  it "handles default key" do
34
36
  Setting.default_setting.should == 1
35
- Setting['seven'].should == "seven from custom"
37
+ Setting['seven']['default'].should == "seven from custom"
36
38
  end
37
39
 
38
40
  it "should handle empty strings" do
@@ -47,4 +49,39 @@ describe Setting do
47
49
  Setting.flag_false.should be(false)
48
50
  end
49
51
  end
52
+
53
+ context "Test from file" do
54
+ before :each do
55
+ Setting.reload(
56
+ :files => ['sample.yml'],
57
+ :path => File.join(File.dirname(__FILE__)) + '/fixtures'
58
+ )
59
+ end
60
+
61
+ it 'should support [] syntax' do
62
+ Setting['tax']['default'].should == 0.0
63
+ Setting['tax'].should == { 'default' => 0.0, 'california' => 7.5 }
64
+ end
65
+
66
+ it 'should support method invocation syntax' do
67
+ Setting.tax.should == 0.0
68
+
69
+ Setting.tax(:default).should == Setting.tax
70
+ Setting.tax('default').should == Setting.tax
71
+ Setting.tax(:california).should == 7.5
72
+
73
+ Setting.states.should == ['CA', 'WA', 'NY']
74
+ Setting.states(:default).should == Setting.states
75
+ Setting.states(:ship_to).should == ['CA', 'NY']
76
+ end
77
+
78
+ it 'should correctly process Boolean values' do
79
+ Setting.boolean_true?.should be(true)
80
+ Setting.boolean_true.should == 4
81
+ Setting.boolean_false?.should be(false)
82
+ Setting.boolean_false?(:default).should be(false)
83
+ Setting.boolean_false?(:negated).should be(true)
84
+ end
85
+
86
+ end
50
87
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mc-settings
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 0
9
8
  - 1
10
- version: 0.0.1
9
+ - 0
10
+ version: 0.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Edwin Cruz
@@ -16,13 +16,14 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2010-12-17 00:00:00 -08:00
19
+ date: 2010-12-28 00:00:00 -08:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
23
- name: rspec
24
23
  prerelease: false
25
- requirement: &id001 !ruby/object:Gem::Requirement
24
+ type: :development
25
+ name: rspec
26
+ version_requirements: &id001 !ruby/object:Gem::Requirement
26
27
  none: false
27
28
  requirements:
28
29
  - - ">="
@@ -31,12 +32,12 @@ dependencies:
31
32
  segments:
32
33
  - 0
33
34
  version: "0"
34
- type: :development
35
- version_requirements: *id001
35
+ requirement: *id001
36
36
  - !ruby/object:Gem::Dependency
37
- name: bundler
38
37
  prerelease: false
39
- requirement: &id002 !ruby/object:Gem::Requirement
38
+ type: :development
39
+ name: bundler
40
+ version_requirements: &id002 !ruby/object:Gem::Requirement
40
41
  none: false
41
42
  requirements:
42
43
  - - ~>
@@ -47,12 +48,12 @@ dependencies:
47
48
  - 0
48
49
  - 0
49
50
  version: 1.0.0
50
- type: :development
51
- version_requirements: *id002
51
+ requirement: *id002
52
52
  - !ruby/object:Gem::Dependency
53
- name: jeweler
54
53
  prerelease: false
55
- requirement: &id003 !ruby/object:Gem::Requirement
54
+ type: :development
55
+ name: jeweler
56
+ version_requirements: &id003 !ruby/object:Gem::Requirement
56
57
  none: false
57
58
  requirements:
58
59
  - - ~>
@@ -63,12 +64,12 @@ dependencies:
63
64
  - 5
64
65
  - 1
65
66
  version: 1.5.1
66
- type: :development
67
- version_requirements: *id003
67
+ requirement: *id003
68
68
  - !ruby/object:Gem::Dependency
69
- name: rcov
70
69
  prerelease: false
71
- requirement: &id004 !ruby/object:Gem::Requirement
70
+ type: :development
71
+ name: rcov
72
+ version_requirements: &id004 !ruby/object:Gem::Requirement
72
73
  none: false
73
74
  requirements:
74
75
  - - ">="
@@ -77,12 +78,12 @@ dependencies:
77
78
  segments:
78
79
  - 0
79
80
  version: "0"
80
- type: :development
81
- version_requirements: *id004
81
+ requirement: *id004
82
82
  - !ruby/object:Gem::Dependency
83
- name: ruby-debug
84
83
  prerelease: false
85
- requirement: &id005 !ruby/object:Gem::Requirement
84
+ type: :development
85
+ name: ruby-debug
86
+ version_requirements: &id005 !ruby/object:Gem::Requirement
86
87
  none: false
87
88
  requirements:
88
89
  - - ">="
@@ -91,8 +92,7 @@ dependencies:
91
92
  segments:
92
93
  - 0
93
94
  version: "0"
94
- type: :development
95
- version_requirements: *id005
95
+ requirement: *id005
96
96
  description: implement custom keys indenendently of environment
97
97
  email: rubydev@modcloth.com
98
98
  executables: []
@@ -112,6 +112,8 @@ files:
112
112
  - VERSION
113
113
  - lib/mc-settings.rb
114
114
  - lib/setting.rb
115
+ - mc-settings.gemspec
116
+ - spec/fixtures/sample.yml
115
117
  - spec/mc_settings_spec.rb
116
118
  - spec/spec_helper.rb
117
119
  - spec/support/settings_helper.rb