Commit 04fddcaf5d0e889bd53e67ef3c767b6295e99296
Committed by
Ben Johnson
1 parent
e6fec8ed
Exists in
master
real-world bugfixes from Vlad and sub-key errors
Showing
3 changed files
with
73 additions
and
25 deletions
Show diff stats
lib/settingslogic.rb
@@ -10,6 +10,16 @@ class Settingslogic < Hash | @@ -10,6 +10,16 @@ class Settingslogic < Hash | ||
10 | instance.key?("name") ? instance.name : super | 10 | instance.key?("name") ? instance.name : super |
11 | end | 11 | end |
12 | 12 | ||
13 | + # Enables Settings.get('nested.key.name') for dynamic access | ||
14 | + def get(key) | ||
15 | + parts = key.split('.') | ||
16 | + curs = self | ||
17 | + while p = parts.shift | ||
18 | + curs = curs.send(p) | ||
19 | + end | ||
20 | + curs | ||
21 | + end | ||
22 | + | ||
13 | def source(value = nil) | 23 | def source(value = nil) |
14 | if value.nil? | 24 | if value.nil? |
15 | @source | 25 | @source |
@@ -27,13 +37,15 @@ class Settingslogic < Hash | @@ -27,13 +37,15 @@ class Settingslogic < Hash | ||
27 | end | 37 | end |
28 | 38 | ||
29 | def [](key) | 39 | def [](key) |
30 | - # Setting.key.value or Setting[:key][:value] or Setting['key']['value'] | ||
31 | - fetch(key.to_s,nil) | 40 | + # Setting[:key][:key2] or Setting['key']['key2'] |
41 | + instance.fetch(key.to_s, nil) | ||
32 | end | 42 | end |
33 | 43 | ||
34 | - def []=(key,val) | ||
35 | - # Setting[:key] = 'value' for dynamic settings | ||
36 | - store(key.to_s,val) | 44 | + def []=(key, val) |
45 | + # Setting[:key][:key2] = 'value' for dynamic settings | ||
46 | + val = self.class.new(val, source) if val.is_a? Hash | ||
47 | + instance.store(key.to_s, val) | ||
48 | + instance.create_accessor_for(key.to_s, val) | ||
37 | end | 49 | end |
38 | 50 | ||
39 | def load! | 51 | def load! |
@@ -75,21 +87,32 @@ class Settingslogic < Hash | @@ -75,21 +87,32 @@ class Settingslogic < Hash | ||
75 | hash = hash[self.class.namespace] if self.class.namespace | 87 | hash = hash[self.class.namespace] if self.class.namespace |
76 | self.replace hash | 88 | self.replace hash |
77 | end | 89 | end |
78 | - @section = section || hash_or_file # so end of error says "in application.yml" | 90 | + @section = section || self.class.source # so end of error says "in application.yml" |
79 | create_accessors! | 91 | create_accessors! |
80 | end | 92 | end |
81 | 93 | ||
82 | # Called for dynamically-defined keys, and also the first key deferenced at the top-level, if load! is not used. | 94 | # Called for dynamically-defined keys, and also the first key deferenced at the top-level, if load! is not used. |
83 | # Otherwise, create_accessors! (called by new) will have created actual methods for each key. | 95 | # Otherwise, create_accessors! (called by new) will have created actual methods for each key. |
84 | - def method_missing(key, *args, &block) | ||
85 | - begin | ||
86 | - value = fetch(key.to_s) | ||
87 | - rescue IndexError | ||
88 | - raise MissingSetting, "Missing setting '#{key}' in #{@section}" | ||
89 | - end | 96 | + def method_missing(name, *args, &block) |
97 | + key = name.to_s | ||
98 | + raise MissingSetting, "Missing setting '#{key}' in #{@section}" unless has_key? key | ||
99 | + value = fetch(key) | ||
100 | + create_accessor_for(key) | ||
90 | value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in #{@section}") : value | 101 | value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in #{@section}") : value |
91 | end | 102 | end |
92 | 103 | ||
104 | + def [](key) | ||
105 | + # Setting[:key][:key2] or Setting['key']['key2'] | ||
106 | + fetch(key.to_s, nil) | ||
107 | + end | ||
108 | + | ||
109 | + def []=(key,val) | ||
110 | + # Setting[:key][:key2] = 'value' for dynamic settings | ||
111 | + val = self.class.new(val, @section) if val.is_a? Hash | ||
112 | + store(key.to_s, val) | ||
113 | + create_accessor_for(key.to_s, val) | ||
114 | + end | ||
115 | + | ||
93 | private | 116 | private |
94 | # This handles naming collisions with Sinatra/Vlad/Capistrano. Since these use a set() | 117 | # This handles naming collisions with Sinatra/Vlad/Capistrano. Since these use a set() |
95 | # helper that defines methods in Object, ANY method_missing ANYWHERE picks up the Vlad/Sinatra | 118 | # helper that defines methods in Object, ANY method_missing ANYWHERE picks up the Vlad/Sinatra |
@@ -97,17 +120,24 @@ class Settingslogic < Hash | @@ -97,17 +120,24 @@ class Settingslogic < Hash | ||
97 | # rather than the app_yml['deploy_to'] hash. Jeezus. | 120 | # rather than the app_yml['deploy_to'] hash. Jeezus. |
98 | def create_accessors! | 121 | def create_accessors! |
99 | self.each do |key,val| | 122 | self.each do |key,val| |
100 | - # Use instance_eval/class_eval because they're actually more efficient than define_method{} | ||
101 | - # http://stackoverflow.com/questions/185947/ruby-definemethod-vs-def | ||
102 | - # http://bmorearty.wordpress.com/2009/01/09/fun-with-rubys-instance_eval-and-class_eval/ | ||
103 | - self.class.class_eval <<-EndEval | ||
104 | - def #{key} | ||
105 | - return @#{key} if @#{key} # cache (performance) | ||
106 | - value = fetch('#{key}') | ||
107 | - @#{key} = value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in #{@section}") : value | ||
108 | - end | ||
109 | - EndEval | 123 | + create_accessor_for(key) |
110 | end | 124 | end |
111 | end | 125 | end |
112 | 126 | ||
127 | + # Use instance_eval/class_eval because they're actually more efficient than define_method{} | ||
128 | + # http://stackoverflow.com/questions/185947/ruby-definemethod-vs-def | ||
129 | + # http://bmorearty.wordpress.com/2009/01/09/fun-with-rubys-instance_eval-and-class_eval/ | ||
130 | + def create_accessor_for(key, val=nil) | ||
131 | + return unless key.to_s =~ /^\w+$/ # could have "some-setting:" which blows up class_eval | ||
132 | + instance_variable_set("@#{key}", val) | ||
133 | + self.class.class_eval <<-EndEval | ||
134 | + def #{key} | ||
135 | + return @#{key} if @#{key} | ||
136 | + raise MissingSetting, "Missing setting '#{key}' in #{@section}" unless has_key? '#{key}' | ||
137 | + value = fetch('#{key}') | ||
138 | + @#{key} = value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in #{@section}") : value | ||
139 | + end | ||
140 | + EndEval | ||
141 | + end | ||
142 | + | ||
113 | end | 143 | end |
spec/settingslogic_spec.rb
@@ -49,7 +49,7 @@ describe "Settingslogic" do | @@ -49,7 +49,7 @@ describe "Settingslogic" do | ||
49 | end | 49 | end |
50 | e.should_not be_nil | 50 | e.should_not be_nil |
51 | e.message.should =~ /Missing setting 'missing' in/ | 51 | e.message.should =~ /Missing setting 'missing' in/ |
52 | - | 52 | + |
53 | e = nil | 53 | e = nil |
54 | begin | 54 | begin |
55 | Settings.language.missing | 55 | Settings.language.missing |
@@ -71,7 +71,7 @@ describe "Settingslogic" do | @@ -71,7 +71,7 @@ describe "Settingslogic" do | ||
71 | e.message.should =~ /Missing setting 'erlang' in 'language' section/ | 71 | e.message.should =~ /Missing setting 'erlang' in 'language' section/ |
72 | 72 | ||
73 | Settings.language['erlang'].should be_nil | 73 | Settings.language['erlang'].should be_nil |
74 | - Settings.language['erlang'] ||= 5 | 74 | + Settings.language['erlang'] = 5 |
75 | Settings.language['erlang'].should == 5 | 75 | Settings.language['erlang'].should == 5 |
76 | 76 | ||
77 | Settings.language['erlang'] = {'paradigm' => 'functional'} | 77 | Settings.language['erlang'] = {'paradigm' => 'functional'} |
@@ -79,6 +79,24 @@ describe "Settingslogic" do | @@ -79,6 +79,24 @@ describe "Settingslogic" do | ||
79 | 79 | ||
80 | Settings.reload! | 80 | Settings.reload! |
81 | Settings.language['erlang'].should be_nil | 81 | Settings.language['erlang'].should be_nil |
82 | + | ||
83 | + Settings.language[:erlang] ||= 5 | ||
84 | + Settings.language[:erlang].should == 5 | ||
85 | + | ||
86 | + Settings.language[:erlang] = {} | ||
87 | + Settings.language[:erlang][:paradigm] = 'functional' | ||
88 | + Settings.language.erlang.paradigm.should == 'functional' | ||
89 | + end | ||
90 | + | ||
91 | + it "should handle badly-named settings" do | ||
92 | + Settings.language['some-dash-setting#'] = 'dashtastic' | ||
93 | + Settings.language['some-dash-setting#'].should == 'dashtastic' | ||
94 | + end | ||
95 | + | ||
96 | + it "should be able to get() a key with dot.notation" do | ||
97 | + Settings.get('setting1.setting1_child').should == "saweet" | ||
98 | + Settings.get('setting1.deep.another').should == "my value" | ||
99 | + Settings.get('setting1.deep.child.value').should == 2 | ||
82 | end | 100 | end |
83 | 101 | ||
84 | # Put this test last or else call to .instance will load @instance, | 102 | # Put this test last or else call to .instance will load @instance, |
spec/spec_helper.rb
@@ -11,7 +11,7 @@ require 'settings3' | @@ -11,7 +11,7 @@ require 'settings3' | ||
11 | 11 | ||
12 | # Needed to test Settings3 | 12 | # Needed to test Settings3 |
13 | def collides | 13 | def collides |
14 | - 'collision' | 14 | + @collides = 'collision' |
15 | end | 15 | end |
16 | 16 | ||
17 | Spec::Runner.configure do |config| | 17 | Spec::Runner.configure do |config| |