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 | 10 | instance.key?("name") ? instance.name : super |
11 | 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 | 23 | def source(value = nil) |
14 | 24 | if value.nil? |
15 | 25 | @source |
... | ... | @@ -27,13 +37,15 @@ class Settingslogic < Hash |
27 | 37 | end |
28 | 38 | |
29 | 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 | 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 | 49 | end |
38 | 50 | |
39 | 51 | def load! |
... | ... | @@ -75,21 +87,32 @@ class Settingslogic < Hash |
75 | 87 | hash = hash[self.class.namespace] if self.class.namespace |
76 | 88 | self.replace hash |
77 | 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 | 91 | create_accessors! |
80 | 92 | end |
81 | 93 | |
82 | 94 | # Called for dynamically-defined keys, and also the first key deferenced at the top-level, if load! is not used. |
83 | 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 | 101 | value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in #{@section}") : value |
91 | 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 | 116 | private |
94 | 117 | # This handles naming collisions with Sinatra/Vlad/Capistrano. Since these use a set() |
95 | 118 | # helper that defines methods in Object, ANY method_missing ANYWHERE picks up the Vlad/Sinatra |
... | ... | @@ -97,17 +120,24 @@ class Settingslogic < Hash |
97 | 120 | # rather than the app_yml['deploy_to'] hash. Jeezus. |
98 | 121 | def create_accessors! |
99 | 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 | 124 | end |
111 | 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 | 143 | end | ... | ... |
spec/settingslogic_spec.rb
... | ... | @@ -49,7 +49,7 @@ describe "Settingslogic" do |
49 | 49 | end |
50 | 50 | e.should_not be_nil |
51 | 51 | e.message.should =~ /Missing setting 'missing' in/ |
52 | - | |
52 | + | |
53 | 53 | e = nil |
54 | 54 | begin |
55 | 55 | Settings.language.missing |
... | ... | @@ -71,7 +71,7 @@ describe "Settingslogic" do |
71 | 71 | e.message.should =~ /Missing setting 'erlang' in 'language' section/ |
72 | 72 | |
73 | 73 | Settings.language['erlang'].should be_nil |
74 | - Settings.language['erlang'] ||= 5 | |
74 | + Settings.language['erlang'] = 5 | |
75 | 75 | Settings.language['erlang'].should == 5 |
76 | 76 | |
77 | 77 | Settings.language['erlang'] = {'paradigm' => 'functional'} |
... | ... | @@ -79,6 +79,24 @@ describe "Settingslogic" do |
79 | 79 | |
80 | 80 | Settings.reload! |
81 | 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 | 100 | end |
83 | 101 | |
84 | 102 | # Put this test last or else call to .instance will load @instance, | ... | ... |