readwritesettings 3.0.1

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.
@@ -0,0 +1,10 @@
1
+ .DS_Store
2
+ *.log
3
+ *.sqlite3
4
+ pkg/*
5
+ coverage/*
6
+ doc/*
7
+ benchmarks/*
8
+ .bundle
9
+ vendor/bundle
10
+ .rvmrc
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ script: bundle exec rake
3
+ rvm:
4
+ - ruby-1.9.3
5
+ - rbx-19mode
6
+ - ruby-2.0.0
7
+ notifications:
8
+ email:
9
+ recipients:
10
+ - drnicwilliams@gmail.com
11
+ on_success: change
12
+ on_failure: always
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,26 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ settingslogic (2.0.9)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.1.3)
10
+ rake (10.0.3)
11
+ rspec (2.12.0)
12
+ rspec-core (~> 2.12.0)
13
+ rspec-expectations (~> 2.12.0)
14
+ rspec-mocks (~> 2.12.0)
15
+ rspec-core (2.12.2)
16
+ rspec-expectations (2.12.1)
17
+ diff-lcs (~> 1.1.3)
18
+ rspec-mocks (2.12.1)
19
+
20
+ PLATFORMS
21
+ ruby
22
+
23
+ DEPENDENCIES
24
+ rake
25
+ rspec
26
+ settingslogic!
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Ben Johnson of Binary Logic (binarylogic.com)
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.
@@ -0,0 +1,196 @@
1
+ # ReadWriteSettings
2
+
3
+ ReadWriteSettings is a simple configuration / settings solution that uses an ERB enabled YAML file. It has been great for
4
+ our apps, maybe you will enjoy it too. ReadWriteSettings works with Rails, Sinatra, or any Ruby project.
5
+
6
+ It is a fork of {ReadWriteSettings}[http://github.com/binarylogic/settingslogic] to support modifications and additional getting/setter methods. Hopefully can merge this fork back into ReadWriteSettings in future and have a single project again.
7
+
8
+ ## Helpful links
9
+
10
+ * <b>Issues:</b> http://github.com/drnic/readwritesettings/issues
11
+ * <b>Source:</b> http://github.com/drnic/readwritesettings
12
+ * [![Build Status](https://travis-ci.org/drnic/readwritesettings.png?branch=master)](https://travis-ci.org/drnic/readwritesettings)
13
+ * [![Code Climate](https://codeclimate.com/github/drnic/readwritesettings.png)](https://codeclimate.com/github/drnic/readwritesettings)
14
+
15
+ ## Installation
16
+
17
+ Add the following to your Gemfile:
18
+
19
+ ``` ruby
20
+ gem "readwritesettings"
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ### 1. Define your class
26
+
27
+ Instead of defining a Settings constant for you, that task is left to you. Simply create a class in your application
28
+ that looks like:
29
+
30
+ ``` ruby
31
+ class Settings < ReadWriteSettings
32
+ source "#{Rails.root}/config/application.yml"
33
+ namespace Rails.env
34
+ end
35
+ ```
36
+
37
+ Name it Settings, name it Config, name it whatever you want. Add as many or as few as you like. A good place to put
38
+ this file in a rails app is app/models/settings.rb
39
+
40
+ I felt adding a settings file in your app was more straightforward, less tricky, and more flexible.
41
+
42
+ Alternately, you can pass a Hash into the constructor.
43
+
44
+ ``` ruby
45
+ Settings = ReadWriteSettings.new({"key" => "value"})
46
+ ```
47
+
48
+ ### 2. Create your settings
49
+
50
+ Notice above we specified an absolute path to our settings file called "application.yml". This is just a typical YAML file.
51
+ Also notice above that we specified a namespace for our environment. A namespace is just an optional string that corresponds
52
+ to a key in the YAML file.
53
+
54
+ Using a namespace allows us to change our configuration depending on our environment:
55
+
56
+ ``` yaml
57
+ # config/application.yml
58
+ defaults: &defaults
59
+ cool:
60
+ saweet: nested settings
61
+ neat_setting: 24
62
+ awesome_setting: <%= "Did you know 5 + 5 = #{5 + 5}?" %>
63
+
64
+ development:
65
+ <<: *defaults
66
+ neat_setting: 800
67
+
68
+ test:
69
+ <<: *defaults
70
+
71
+ production:
72
+ <<: *defaults
73
+ ```
74
+
75
+ _Note_: Certain Ruby/Bundler versions include a version of the Psych YAML parser which incorrectly handles merges (the `<<` in the example above.)
76
+ If your default settings seem to be overwriting your environment-specific settings, including the following lines in your config/boot.rb file may solve the problem:
77
+
78
+ ``` ruby
79
+ require 'yaml'
80
+ YAML::ENGINE.yamler= 'syck'
81
+ ```
82
+
83
+ ### 3. Access your settings
84
+
85
+ ``` ruby
86
+ >> Rails.env
87
+ => "development"
88
+
89
+ >> Settings.cool
90
+ => "#<ReadWriteSettings::Settings ... >"
91
+
92
+ >> Settings.cool.saweet
93
+ => "nested settings"
94
+
95
+ >> Settings.neat_setting
96
+ => 800
97
+
98
+ >> Settings.awesome_setting
99
+ => "Did you know 5 + 5 = 10?"
100
+ ```
101
+
102
+ You can use these settings anywhere, for example in a model:
103
+
104
+ ``` ruby
105
+ class Post < ActiveRecord::Base
106
+ self.per_page = Settings.pagination.posts_per_page
107
+ end
108
+ ```
109
+
110
+ ### 4. Optional / dynamic settings
111
+
112
+ Often, you will want to handle defaults in your application logic itself, to reduce the number of settings
113
+ you need to put in your YAML file. You can access an optional setting by using Hash notation:
114
+
115
+ ``` ruby
116
+ >> Settings.messaging.queue_name
117
+ => Exception: Missing setting 'queue_name' in 'message' section in 'application.yml'
118
+
119
+ >> Settings.messaging['queue_name']
120
+ => nil
121
+
122
+ >> Settings.messaging['queue_name'] ||= 'user_mail'
123
+ => "user_mail"
124
+
125
+ >> Settings.messaging.queue_name
126
+ => "user_mail"
127
+ ```
128
+
129
+ Modifying our model example:
130
+
131
+ ``` ruby
132
+ class Post < ActiveRecord::Base
133
+ self.per_page = Settings.posts['per_page'] || Settings.pagination.per_page
134
+ end
135
+ ```
136
+
137
+ This would allow you to specify a custom value for per_page just for posts, or
138
+ to fall back to your default value if not specified.
139
+
140
+ ### 5. Suppressing Exceptions Conditionally
141
+
142
+ Raising exceptions for missing settings helps highlight configuration problems. However, in a
143
+ Rails app it may make sense to suppress this in production and return nil for missing settings.
144
+ While it's useful to stop and highlight an error in development or test environments, this is
145
+ often not the right answer for production.
146
+
147
+ ``` ruby
148
+ class Settings < ReadWriteSettings
149
+ source "#{Rails.root}/config/application.yml"
150
+ namespace Rails.env
151
+ suppress_errors Rails.env.production?
152
+ end
153
+
154
+ >> Settings.non_existent_key
155
+ => nil
156
+ ```
157
+
158
+ ## Note on Sinatra / Capistrano / Vlad
159
+
160
+ Each of these frameworks uses a +set+ convention for settings, which actually defines methods
161
+ in the global Object namespace:
162
+
163
+ ``` ruby
164
+ set :application, "myapp" # does "def application" globally
165
+ ```
166
+
167
+ This can cause collisions with ReadWriteSettings, since those methods are global. Luckily, the
168
+ solution is to just add a call to load! in your class:
169
+
170
+ ``` ruby
171
+ class Settings < ReadWriteSettings
172
+ source "#{Rails.root}/config/application.yml"
173
+ namespace Rails.env
174
+ load!
175
+ end
176
+ ```
177
+
178
+ It's probably always safest to add load! to your class, since this guarantees settings will be
179
+ loaded at that time, rather than lazily later via method_missing.
180
+
181
+ Finally, you can reload all your settings later as well:
182
+
183
+ ``` ruby
184
+ Settings.reload!
185
+ ```
186
+
187
+ This is useful if you want to support changing your settings YAML without restarting your app.
188
+
189
+ ## History
190
+
191
+ This project was originally created by Ben Johnson and called ReadWriteSettings. A renamed fork was created so that new gem versions could be released with new functionality, by Dr Nic Williams. The latter is not entirely thrilled about this situation; but at least he now has a versioned gem with the new shiny stuff in it.
192
+
193
+ Copyright (c) 2008-2010 {Ben Johnson}[http://github.com/binarylogic] of {Binary Logic}[http://www.binarylogic.com],
194
+ released under the MIT license. Support for optional settings and reloading by {Nate Wiger}[http://nate.wiger.org].
195
+
196
+ Copyright (c) 2013 {Dr Nic Williams}[http://github.com/drnic]
@@ -0,0 +1,7 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new
6
+
7
+ task :default => :spec
@@ -0,0 +1,246 @@
1
+ require "yaml"
2
+ require "erb"
3
+ require 'open-uri'
4
+
5
+ # A simple settings solution using a YAML file. See README for more information.
6
+ class ReadWriteSettings < Hash
7
+ class MissingSetting < StandardError; end
8
+
9
+ class << self
10
+ def name # :nodoc:
11
+ self.superclass != Hash && instance.key?("name") ? instance.name : super
12
+ end
13
+
14
+ # Enables Settings.get('nested.key.name') for dynamic access
15
+ def get(key)
16
+ parts = key.split('.')
17
+ curs = self
18
+ while p = parts.shift
19
+ curs = curs.send(p)
20
+ end
21
+ curs
22
+ end
23
+
24
+ def source(value = nil)
25
+ @source ||= value
26
+ end
27
+
28
+ def namespace(value = nil)
29
+ @namespace ||= value
30
+ end
31
+
32
+ def suppress_errors(value = nil)
33
+ @suppress_errors ||= value
34
+ end
35
+
36
+ def [](key)
37
+ instance.fetch(key.to_s, nil)
38
+ end
39
+
40
+ def []=(key, val)
41
+ # Setting[:key][:key2] = 'value' for dynamic settings
42
+ val = new(val, source) if val.is_a? Hash
43
+ instance.store(key.to_s, val)
44
+ instance.create_accessor_for(key, val)
45
+ end
46
+
47
+ def load!
48
+ instance
49
+ true
50
+ end
51
+
52
+ def reload!
53
+ @instance = nil
54
+ load!
55
+ end
56
+
57
+ private
58
+ def instance
59
+ return @instance if @instance
60
+ @instance = new
61
+ create_accessors!
62
+ @instance
63
+ end
64
+
65
+ def method_missing(name, *args, &block)
66
+ instance.send(name, *args, &block)
67
+ end
68
+
69
+ # It would be great to DRY this up somehow, someday, but it's difficult because
70
+ # of the singleton pattern. Basically this proxies Setting.foo to Setting.instance.foo
71
+ def create_accessors!
72
+ instance.each do |key,val|
73
+ create_accessor_for(key)
74
+ end
75
+ end
76
+
77
+ def create_accessor_for(key)
78
+ return unless key.to_s =~ /^\w+$/ # could have "some-setting:" which blows up eval
79
+ instance_eval "def #{key}; instance.send(:#{key}); end"
80
+ end
81
+
82
+ end
83
+
84
+ # Initializes a new settings object. You can initialize an object in any of the following ways:
85
+ #
86
+ # Settings.new(:application) # will look for config/application.yml
87
+ # Settings.new("application.yaml") # will look for application.yaml
88
+ # Settings.new("/var/configs/application.yml") # will look for /var/configs/application.yml
89
+ # Settings.new(:config1 => 1, :config2 => 2)
90
+ #
91
+ # Basically if you pass a symbol it will look for that file in the configs directory of your rails app,
92
+ # if you are using this in rails. If you pass a string it should be an absolute path to your settings file.
93
+ # Then you can pass a hash, and it just allows you to access the hash via methods.
94
+ def initialize(hash_or_file = self.class.source, section = nil)
95
+ #puts "new! #{hash_or_file}"
96
+ case hash_or_file
97
+ when nil
98
+ raise Errno::ENOENT, "No file specified as ReadWriteSettings source"
99
+ when Hash
100
+ self.replace hash_or_file
101
+ else
102
+ file_contents = open(hash_or_file).read
103
+ hash = file_contents.empty? ? {} : YAML.load(ERB.new(file_contents).result).to_hash
104
+ if self.class.namespace
105
+ hash = hash[self.class.namespace] or return missing_key("Missing setting '#{self.class.namespace}' in #{hash_or_file}")
106
+ end
107
+ self.replace hash
108
+ end
109
+ @section = section || self.class.source # so end of error says "in application.yml"
110
+ create_accessors!
111
+ end
112
+
113
+ # Called for dynamically-defined keys, and also the first key deferenced at the top-level, if load! is not used.
114
+ # Otherwise, create_accessors! (called by new) will have created actual methods for each key.
115
+ def method_missing(name, *args, &block)
116
+ key = name.to_s
117
+ return missing_key("Missing setting '#{key}' in #{@section}") unless has_key? key
118
+ value = fetch(key)
119
+ create_accessor_for(key)
120
+ value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in #{@section}") : value
121
+ end
122
+
123
+ def [](key)
124
+ fetch(key.to_s, nil)
125
+ end
126
+
127
+ def []=(key,val)
128
+ # Setting[:key][:key2] = 'value' for dynamic settings
129
+ val = self.class.new(val, @section) if val.is_a? Hash
130
+ store(key.to_s, val)
131
+ create_accessor_for(key, val)
132
+ end
133
+
134
+ # Create a nested structure and set value.
135
+ # For example: set("foo.bar.tar", 123)
136
+ # Resulting ReadWriteSettings/Hash:
137
+ # { "foo" => { "bar" => { "tar" => 123 }}}
138
+ def set(nested_key, val)
139
+ target_settings_field = self
140
+ settings_key_portions = nested_key.to_s.split(".")
141
+ parent_key_portions, final_key = settings_key_portions[0..-2], settings_key_portions[-1]
142
+ parent_key_portions.each do |key_portion|
143
+ target_settings_field[key_portion] ||= ReadWriteSettings.new({})
144
+ target_settings_field = target_settings_field[key_portion]
145
+ end
146
+ target_settings_field[final_key] = val
147
+ create_accessors!
148
+ end
149
+
150
+ # Like #set, but only sets the value if the key is not already set
151
+ # Returns the existing value or the newly-set default value
152
+ def set_default(nested_key, val)
153
+ target_settings_field = self
154
+ settings_key_portions = nested_key.to_s.split(".")
155
+ parent_key_portions, final_key = settings_key_portions[0..-2], settings_key_portions[-1]
156
+ parent_key_portions.each do |key_portion|
157
+ target_settings_field[key_portion] ||= ReadWriteSettings.new({})
158
+ target_settings_field = target_settings_field[key_portion]
159
+ end
160
+ target_settings_field[final_key] ||= val
161
+ target_settings_field[final_key]
162
+ end
163
+
164
+ def nested_value(nested_key)
165
+ target_settings_field = self
166
+ settings_key_portions = nested_key.to_s.split(".")
167
+ parent_key_portions, final_key = settings_key_portions[0..-2], settings_key_portions[-1]
168
+ parent_key_portions.each do |key_portion|
169
+ target_settings_field[key_portion] ||= ReadWriteSettings.new({})
170
+ target_settings_field = target_settings_field[key_portion]
171
+ end
172
+ target_settings_field[final_key]
173
+ end
174
+ alias :exists? :nested_value
175
+
176
+ # Returns an instance of a Hash object
177
+ def to_hash
178
+ Hash[self]
179
+ end
180
+
181
+ # Convert all nested ReadWriteSettings objects to Hash objects
182
+ def to_nested_hash
183
+ inject({}) do |hash, key_value|
184
+ key, value = key_value
185
+ hash[key] = value.respond_to?(:to_nested_hash) ? value.to_nested_hash : value
186
+ hash
187
+ end
188
+ end
189
+
190
+ def save(path)
191
+ File.open(path, "w") { |f| f << to_nested_hash.to_yaml }
192
+ end
193
+
194
+ # This handles naming collisions with Sinatra/Vlad/Capistrano. Since these use a set()
195
+ # helper that defines methods in Object, ANY method_missing ANYWHERE picks up the Vlad/Sinatra
196
+ # settings! So settings.deploy_to title actually calls Object.deploy_to (from set :deploy_to, "host"),
197
+ # rather than the app_yml['deploy_to'] hash. Jeezus.
198
+ def create_accessors!
199
+ self.each do |key,val|
200
+ create_accessor_for(key)
201
+ end
202
+ end
203
+
204
+ # Use instance_eval/class_eval because they're actually more efficient than define_method{}
205
+ # http://stackoverflow.com/questions/185947/ruby-definemethod-vs-def
206
+ # http://bmorearty.wordpress.com/2009/01/09/fun-with-rubys-instance_eval-and-class_eval/
207
+ def create_accessor_for(key, val=nil)
208
+ return unless key.to_s =~ /^\w+$/ # could have "some-setting:" which blows up eval
209
+ instance_variable_set("@#{key}", val)
210
+ self.class.class_eval <<-EndEval
211
+ def #{key}
212
+ return @#{key} if @#{key}
213
+ return missing_key("Missing setting '#{key}' in #{@section}") unless has_key? '#{key}'
214
+ value = fetch('#{key}')
215
+ @#{key} = if value.is_a?(Hash)
216
+ self.class.new(value, "'#{key}' section in #{@section}")
217
+ elsif value.is_a?(Array) && value.all?{|v| v.is_a? Hash}
218
+ value.map{|v| self.class.new(v)}
219
+ else
220
+ value
221
+ end
222
+ end
223
+ EndEval
224
+ end
225
+
226
+ def symbolize_keys
227
+
228
+ inject({}) do |memo, tuple|
229
+
230
+ k = (tuple.first.to_sym rescue tuple.first) || tuple.first
231
+
232
+ v = k.is_a?(Symbol) ? send(k) : tuple.last # make sure the value is accessed the same way Settings.foo.bar works
233
+
234
+ memo[k] = v && v.respond_to?(:symbolize_keys) ? v.symbolize_keys : v #recurse for nested hashes
235
+
236
+ memo
237
+ end
238
+
239
+ end
240
+
241
+ def missing_key(msg)
242
+ return nil if self.class.suppress_errors
243
+
244
+ raise MissingSetting, msg
245
+ end
246
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "readwritesettings"
6
+ s.version = "3.0.1"
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Dr Nic Williams", "Ben Johnson"]
9
+ s.email = ["drnicwilliams@gmail.com", "bjohnson@binarylogic.com"]
10
+ s.homepage = "http://github.com/drnic/readwritesettings"
11
+ s.summary = %q{A simple settings solution that uses an ERB enabled YAML file and a singleton design pattern.}
12
+ s.description = %q{A simple settings solution that uses an ERB enabled YAML file and a singleton design pattern.}
13
+
14
+ s.add_development_dependency 'rake'
15
+ s.add_development_dependency 'rspec'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+ end
@@ -0,0 +1,284 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/spec_helper")
2
+
3
+ describe "ReadWriteSettings" do
4
+ it "should access settings" do
5
+ Settings.setting2.should == 5
6
+ end
7
+
8
+ it "should access nested settings" do
9
+ Settings.setting1.setting1_child.should == "saweet"
10
+ end
11
+
12
+ it "should access settings in nested arrays" do
13
+ Settings.array.first.name.should == "first"
14
+ end
15
+
16
+ it "should access deep nested settings" do
17
+ Settings.setting1.deep.another.should == "my value"
18
+ end
19
+
20
+ it "should access extra deep nested settings" do
21
+ Settings.setting1.deep.child.value.should == 2
22
+ end
23
+
24
+ it "should enable erb" do
25
+ Settings.setting3.should == 25
26
+ end
27
+
28
+ it "should namespace settings" do
29
+ Settings2.setting1_child.should == "saweet"
30
+ Settings2.deep.another.should == "my value"
31
+ end
32
+
33
+ it "should return the namespace" do
34
+ Settings.namespace.should be_nil
35
+ Settings2.namespace.should == 'setting1'
36
+ end
37
+
38
+ it "should distinguish nested keys" do
39
+ Settings.language.haskell.paradigm.should == 'functional'
40
+ Settings.language.smalltalk.paradigm.should == 'object oriented'
41
+ end
42
+
43
+ it "should not collide with global methods" do
44
+ Settings3.nested.collides.does.should == 'not either'
45
+ Settings3[:nested] = 'fooey'
46
+ Settings3[:nested].should == 'fooey'
47
+ Settings3.nested.should == 'fooey'
48
+ Settings3.collides.does.should == 'not'
49
+ end
50
+
51
+ it "should raise a helpful error message" do
52
+ e = nil
53
+ begin
54
+ Settings.missing
55
+ rescue => e
56
+ e.should be_kind_of ReadWriteSettings::MissingSetting
57
+ end
58
+ e.should_not be_nil
59
+ e.message.should =~ /Missing setting 'missing' in/
60
+
61
+ e = nil
62
+ begin
63
+ Settings.language.missing
64
+ rescue => e
65
+ e.should be_kind_of ReadWriteSettings::MissingSetting
66
+ end
67
+ e.should_not be_nil
68
+ e.message.should =~ /Missing setting 'missing' in 'language' section/
69
+ end
70
+
71
+ it "should handle optional / dynamic settings" do
72
+ e = nil
73
+ begin
74
+ Settings.language.erlang
75
+ rescue => e
76
+ e.should be_kind_of ReadWriteSettings::MissingSetting
77
+ end
78
+ e.should_not be_nil
79
+ e.message.should =~ /Missing setting 'erlang' in 'language' section/
80
+
81
+ Settings.language['erlang'].should be_nil
82
+ Settings.language['erlang'] = 5
83
+ Settings.language['erlang'].should == 5
84
+
85
+ Settings.language['erlang'] = {'paradigm' => 'functional'}
86
+ Settings.language.erlang.paradigm.should == 'functional'
87
+ Settings.respond_to?('erlang').should be_false
88
+
89
+ Settings.reload!
90
+ Settings.language['erlang'].should be_nil
91
+
92
+ Settings.language[:erlang] ||= 5
93
+ Settings.language[:erlang].should == 5
94
+
95
+ Settings.language[:erlang] = {}
96
+ Settings.language[:erlang][:paradigm] = 'functional'
97
+ Settings.language.erlang.paradigm.should == 'functional'
98
+
99
+ Settings[:toplevel] = '42'
100
+ Settings.toplevel.should == '42'
101
+ end
102
+
103
+ it "should raise an error on a nil source argument" do
104
+ class NoSource < ReadWriteSettings; end
105
+ e = nil
106
+ begin
107
+ NoSource.foo.bar
108
+ rescue => e
109
+ e.should be_kind_of Errno::ENOENT
110
+ end
111
+ e.should_not be_nil
112
+ end
113
+
114
+ it "should allow suppressing errors" do
115
+ Settings4.non_existent_key.should be_nil
116
+ end
117
+
118
+ # This one edge case currently does not pass, because it requires very
119
+ # esoteric code in order to make it pass. It was judged not worth fixing,
120
+ # as it introduces significant complexity for minor gain.
121
+ # it "should handle reloading top-level settings"
122
+ # Settings[:inspect] = 'yeah baby'
123
+ # Settings.inspect.should == 'yeah baby'
124
+ # Settings.reload!
125
+ # Settings.inspect.should == 'Settings'
126
+ # end
127
+
128
+ it "should handle oddly-named settings" do
129
+ Settings.language['some-dash-setting#'] = 'dashtastic'
130
+ Settings.language['some-dash-setting#'].should == 'dashtastic'
131
+ end
132
+
133
+ it "should handle settings with nil value" do
134
+ Settings["flag"] = true
135
+ Settings["flag"] = nil
136
+ Settings.flag.should == nil
137
+ end
138
+
139
+ it "should handle settings with false value" do
140
+ Settings["flag"] = true
141
+ Settings["flag"] = false
142
+ Settings.flag.should == false
143
+ end
144
+
145
+ it "should support instance usage as well" do
146
+ settings = SettingsInst.new(Settings.source)
147
+ settings.setting1.setting1_child.should == "saweet"
148
+ end
149
+
150
+ it "should be able to get() a key with dot.notation" do
151
+ Settings.get('setting1.setting1_child').should == "saweet"
152
+ Settings.get('setting1.deep.another').should == "my value"
153
+ Settings.get('setting1.deep.child.value').should == 2
154
+ end
155
+
156
+ # If .name is not a property, delegate to superclass
157
+ it "should respond with Module.name" do
158
+ Settings2.name.should == "Settings2"
159
+ end
160
+
161
+ # If .name is called on ReadWriteSettings itself, handle appropriately
162
+ # by delegating to Hash
163
+ it "should have the parent class always respond with Module.name" do
164
+ ReadWriteSettings.name.should == 'ReadWriteSettings'
165
+ end
166
+
167
+ # If .name is a property, respond with that instead of delegating to superclass
168
+ it "should allow a name setting to be overriden" do
169
+ Settings.name.should == 'test'
170
+ end
171
+
172
+ it "should allow symbolize_keys" do
173
+ Settings.reload!
174
+ result = Settings.language.haskell.symbolize_keys
175
+ result.class.should == Hash
176
+ result.should == {:paradigm => "functional"}
177
+ end
178
+
179
+ it "should allow symbolize_keys on nested hashes" do
180
+ Settings.reload!
181
+ result = Settings.language.symbolize_keys
182
+ result.class.should == Hash
183
+ result.should == {
184
+ :haskell => {:paradigm => "functional"},
185
+ :smalltalk => {:paradigm => "object oriented"}
186
+ }
187
+ end
188
+
189
+ it "should handle empty file" do
190
+ SettingsEmpty.keys.should eql([])
191
+ end
192
+
193
+ # Put this test last or else call to .instance will load @instance,
194
+ # masking bugs.
195
+ it "should be a hash" do
196
+ Settings.send(:instance).should be_is_a(Hash)
197
+ end
198
+
199
+ describe "#to_hash" do
200
+ it "should return a new instance of a Hash object" do
201
+ Settings.to_hash.should be_kind_of(Hash)
202
+ Settings.to_hash.class.name.should == "Hash"
203
+ Settings.to_hash.object_id.should_not == Settings.object_id
204
+ end
205
+ end
206
+
207
+ describe "#to_nested_hash" do
208
+ it "should convert all nested ReadWriteSettings objects to Hash objects" do
209
+ hash = Settings.to_nested_hash
210
+ hash.class.should == Hash
211
+ hash["language"].class.should == Hash
212
+ hash["language"]["haskell"].class.should == Hash
213
+ hash["language"]["haskell"]["paradigm"].class.should == String
214
+ end
215
+ end
216
+
217
+ describe "#save(file)" do
218
+ it "should save ReadWriteSettings object such that it can be reloaded later" do
219
+ Settings.reload!
220
+ Settings["extra"] = {}
221
+ Settings["extra"]["value"] = 123
222
+ Settings.extra.value.should == 123
223
+ Settings.save("/tmp/settings.yml")
224
+
225
+ later_on = ReadWriteSettings.new("/tmp/settings.yml")
226
+ later_on.extra.value.should == 123
227
+ end
228
+ end
229
+
230
+ describe "#set('nested.key', value)" do
231
+ it "works like []= for non-dotted keys" do
232
+ Settings.set("simple", "value")
233
+ Settings.simple.should == "value"
234
+ end
235
+
236
+ it "creates new nested structures" do
237
+ Settings.set("nested.key", "value")
238
+ Settings.nested.key.should == "value"
239
+ end
240
+
241
+ it "reuses existing structures" do
242
+ Settings.set("language.ruby.paradigm", "scripting")
243
+ Settings.language.haskell.paradigm.should == "functional"
244
+ Settings.language.ruby.paradigm.should == "scripting"
245
+ end
246
+ end
247
+
248
+ describe "#set_default('nested.key', value)" do
249
+ it "works like #set if key 'nested.key' missing" do
250
+ Settings.set("nested", {})
251
+ Settings.set_default("nested.key", "default").should == "default"
252
+ Settings.nested.key.should == "default"
253
+ end
254
+
255
+ it "works like #set if key 'nested' missing" do
256
+ Settings.set_default("nested.key", "default").should == "default"
257
+ Settings.nested.key.should == "default"
258
+ end
259
+
260
+ it "does nothing if nested.key is set" do
261
+ Settings.set("nested.key", "value")
262
+ Settings.set_default("nested.key", "default").should == "value"
263
+ Settings.nested.key.should == "value"
264
+ end
265
+ end
266
+
267
+ describe "#exists?('nested.key') && #value('nested.key')" do
268
+ before { Settings.reload! }
269
+ it "returns truthy if nested.key is set" do
270
+ Settings.set("nested.key", "value")
271
+ Settings.nested_value("nested.key").should == "value"
272
+ end
273
+
274
+ it "returns falsy if nested is not set" do
275
+ Settings.nested_value("nested.key").should be_nil
276
+ end
277
+
278
+ it "returns falsy if nested.key is not set" do
279
+ Settings.set("nested", {})
280
+ Settings.exists?("nested.key").should be_nil
281
+ end
282
+ end
283
+
284
+ end
@@ -0,0 +1,6 @@
1
+ class Settings < ReadWriteSettings
2
+ source "#{File.dirname(__FILE__)}/settings.yml"
3
+ end
4
+
5
+ class SettingsInst < ReadWriteSettings
6
+ end
@@ -0,0 +1,28 @@
1
+ setting1:
2
+ setting1_child: saweet
3
+ deep:
4
+ another: my value
5
+ child:
6
+ value: 2
7
+
8
+ setting2: 5
9
+ setting3: <%= 5 * 5 %>
10
+ name: test
11
+
12
+ language:
13
+ haskell:
14
+ paradigm: functional
15
+ smalltalk:
16
+ paradigm: object oriented
17
+
18
+ collides:
19
+ does: not
20
+ nested:
21
+ collides:
22
+ does: not either
23
+
24
+ array:
25
+ -
26
+ name: first
27
+ -
28
+ name: second
@@ -0,0 +1,4 @@
1
+ class Settings2 < ReadWriteSettings
2
+ source "#{File.dirname(__FILE__)}/settings.yml"
3
+ namespace "setting1"
4
+ end
@@ -0,0 +1,4 @@
1
+ class Settings3 < ReadWriteSettings
2
+ source "#{File.dirname(__FILE__)}/settings.yml"
3
+ load! # test of load
4
+ end
@@ -0,0 +1,4 @@
1
+ class Settings4 < ReadWriteSettings
2
+ source "#{File.dirname(__FILE__)}/settings.yml"
3
+ suppress_errors true
4
+ end
@@ -0,0 +1,3 @@
1
+ class SettingsEmpty < ReadWriteSettings
2
+ source "#{File.dirname(__FILE__)}/settings_empty.yml"
3
+ end
File without changes
@@ -0,0 +1,17 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'rspec'
4
+ require 'readwritesettings'
5
+ require 'settings'
6
+ require 'settings2'
7
+ require 'settings3'
8
+ require 'settings4'
9
+ require 'settings_empty'
10
+
11
+ # Needed to test Settings3
12
+ Object.send :define_method, 'collides' do
13
+ 'collision'
14
+ end
15
+
16
+ RSpec.configure do |config|
17
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: readwritesettings
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Dr Nic Williams
9
+ - Ben Johnson
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-05-20 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rake
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: rspec
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :development
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ description: A simple settings solution that uses an ERB enabled YAML file and a singleton
48
+ design pattern.
49
+ email:
50
+ - drnicwilliams@gmail.com
51
+ - bjohnson@binarylogic.com
52
+ executables: []
53
+ extensions: []
54
+ extra_rdoc_files: []
55
+ files:
56
+ - .gitignore
57
+ - .travis.yml
58
+ - Gemfile
59
+ - Gemfile.lock
60
+ - LICENSE
61
+ - README.md
62
+ - Rakefile
63
+ - lib/readwritesettings.rb
64
+ - readwritesettings.gemspec
65
+ - spec/readwritesettings_spec.rb
66
+ - spec/settings.rb
67
+ - spec/settings.yml
68
+ - spec/settings2.rb
69
+ - spec/settings3.rb
70
+ - spec/settings4.rb
71
+ - spec/settings_empty.rb
72
+ - spec/settings_empty.yml
73
+ - spec/spec_helper.rb
74
+ homepage: http://github.com/drnic/readwritesettings
75
+ licenses: []
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project:
94
+ rubygems_version: 1.8.25
95
+ signing_key:
96
+ specification_version: 3
97
+ summary: A simple settings solution that uses an ERB enabled YAML file and a singleton
98
+ design pattern.
99
+ test_files:
100
+ - spec/readwritesettings_spec.rb
101
+ - spec/settings.rb
102
+ - spec/settings.yml
103
+ - spec/settings2.rb
104
+ - spec/settings3.rb
105
+ - spec/settings4.rb
106
+ - spec/settings_empty.rb
107
+ - spec/settings_empty.yml
108
+ - spec/spec_helper.rb