From 04fddcaf5d0e889bd53e67ef3c767b6295e99296 Mon Sep 17 00:00:00 2001 From: Nate Wiger Date: Tue, 9 Feb 2010 08:56:11 +0800 Subject: [PATCH] real-world bugfixes from Vlad and sub-key errors --- lib/settingslogic.rb | 74 ++++++++++++++++++++++++++------------ spec/settingslogic_spec.rb | 22 ++++++++++-- spec/spec_helper.rb | 2 +- 3 files changed, 73 insertions(+), 25 deletions(-) diff --git a/lib/settingslogic.rb b/lib/settingslogic.rb index 76dabf3..5385815 100644 --- a/lib/settingslogic.rb +++ b/lib/settingslogic.rb @@ -10,6 +10,16 @@ def name # :nodoc: instance.key?("name") ? instance.name : super end + # Enables Settings.get('nested.key.name') for dynamic access + def get(key) + parts = key.split('.') + curs = self + while p = parts.shift + curs = curs.send(p) + end + curs + end + def source(value = nil) if value.nil? @source @@ -27,13 +37,15 @@ def namespace(value = nil) end def [](key) - # Setting.key.value or Setting[:key][:value] or Setting['key']['value'] - fetch(key.to_s,nil) + # Setting[:key][:key2] or Setting['key']['key2'] + instance.fetch(key.to_s, nil) end - def []=(key,val) - # Setting[:key] = 'value' for dynamic settings - store(key.to_s,val) + def []=(key, val) + # Setting[:key][:key2] = 'value' for dynamic settings + val = self.class.new(val, source) if val.is_a? Hash + instance.store(key.to_s, val) + instance.create_accessor_for(key.to_s, val) end def load! @@ -75,21 +87,32 @@ def initialize(hash_or_file = self.class.source, section = nil) hash = hash[self.class.namespace] if self.class.namespace self.replace hash end - @section = section || hash_or_file # so end of error says "in application.yml" + @section = section || self.class.source # so end of error says "in application.yml" create_accessors! end # Called for dynamically-defined keys, and also the first key deferenced at the top-level, if load! is not used. # Otherwise, create_accessors! (called by new) will have created actual methods for each key. - def method_missing(key, *args, &block) - begin - value = fetch(key.to_s) - rescue IndexError - raise MissingSetting, "Missing setting '#{key}' in #{@section}" - end + def method_missing(name, *args, &block) + key = name.to_s + raise MissingSetting, "Missing setting '#{key}' in #{@section}" unless has_key? key + value = fetch(key) + create_accessor_for(key) value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in #{@section}") : value end + def [](key) + # Setting[:key][:key2] or Setting['key']['key2'] + fetch(key.to_s, nil) + end + + def []=(key,val) + # Setting[:key][:key2] = 'value' for dynamic settings + val = self.class.new(val, @section) if val.is_a? Hash + store(key.to_s, val) + create_accessor_for(key.to_s, val) + end + private # This handles naming collisions with Sinatra/Vlad/Capistrano. Since these use a set() # helper that defines methods in Object, ANY method_missing ANYWHERE picks up the Vlad/Sinatra @@ -97,17 +120,24 @@ def method_missing(key, *args, &block) # rather than the app_yml['deploy_to'] hash. Jeezus. def create_accessors! self.each do |key,val| - # Use instance_eval/class_eval because they're actually more efficient than define_method{} - # http://stackoverflow.com/questions/185947/ruby-definemethod-vs-def - # http://bmorearty.wordpress.com/2009/01/09/fun-with-rubys-instance_eval-and-class_eval/ - self.class.class_eval <<-EndEval - def #{key} - return @#{key} if @#{key} # cache (performance) - value = fetch('#{key}') - @#{key} = value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in #{@section}") : value - end - EndEval + create_accessor_for(key) end end + # Use instance_eval/class_eval because they're actually more efficient than define_method{} + # http://stackoverflow.com/questions/185947/ruby-definemethod-vs-def + # http://bmorearty.wordpress.com/2009/01/09/fun-with-rubys-instance_eval-and-class_eval/ + def create_accessor_for(key, val=nil) + return unless key.to_s =~ /^\w+$/ # could have "some-setting:" which blows up class_eval + instance_variable_set("@#{key}", val) + self.class.class_eval <<-EndEval + def #{key} + return @#{key} if @#{key} + raise MissingSetting, "Missing setting '#{key}' in #{@section}" unless has_key? '#{key}' + value = fetch('#{key}') + @#{key} = value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in #{@section}") : value + end + EndEval + end + end diff --git a/spec/settingslogic_spec.rb b/spec/settingslogic_spec.rb index f05200d..a3edd3a 100644 --- a/spec/settingslogic_spec.rb +++ b/spec/settingslogic_spec.rb @@ -49,7 +49,7 @@ end e.should_not be_nil e.message.should =~ /Missing setting 'missing' in/ - + e = nil begin Settings.language.missing @@ -71,7 +71,7 @@ e.message.should =~ /Missing setting 'erlang' in 'language' section/ Settings.language['erlang'].should be_nil - Settings.language['erlang'] ||= 5 + Settings.language['erlang'] = 5 Settings.language['erlang'].should == 5 Settings.language['erlang'] = {'paradigm' => 'functional'} @@ -79,6 +79,24 @@ Settings.reload! Settings.language['erlang'].should be_nil + + Settings.language[:erlang] ||= 5 + Settings.language[:erlang].should == 5 + + Settings.language[:erlang] = {} + Settings.language[:erlang][:paradigm] = 'functional' + Settings.language.erlang.paradigm.should == 'functional' + end + + it "should handle badly-named settings" do + Settings.language['some-dash-setting#'] = 'dashtastic' + Settings.language['some-dash-setting#'].should == 'dashtastic' + end + + it "should be able to get() a key with dot.notation" do + Settings.get('setting1.setting1_child').should == "saweet" + Settings.get('setting1.deep.another').should == "my value" + Settings.get('setting1.deep.child.value').should == 2 end # Put this test last or else call to .instance will load @instance, diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 177f94b..10a48ca 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -11,7 +11,7 @@ # Needed to test Settings3 def collides - 'collision' + @collides = 'collision' end Spec::Runner.configure do |config|