Skip to content

Commit 04fddca

Browse files
Nate Wigerbinarylogic
authored andcommitted
real-world bugfixes from Vlad and sub-key errors
1 parent e6fec8e commit 04fddca

File tree

3 files changed

+73
-25
lines changed

3 files changed

+73
-25
lines changed

lib/settingslogic.rb

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ def name # :nodoc:
1010
instance.key?("name") ? instance.name : super
1111
end
1212

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+
1323
def source(value = nil)
1424
if value.nil?
1525
@source
@@ -27,13 +37,15 @@ def namespace(value = nil)
2737
end
2838

2939
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)
3242
end
3343

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)
3749
end
3850

3951
def load!
@@ -75,39 +87,57 @@ def initialize(hash_or_file = self.class.source, section = nil)
7587
hash = hash[self.class.namespace] if self.class.namespace
7688
self.replace hash
7789
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"
7991
create_accessors!
8092
end
8193

8294
# Called for dynamically-defined keys, and also the first key deferenced at the top-level, if load! is not used.
8395
# 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)
90101
value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in #{@section}") : value
91102
end
92103

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+
93116
private
94117
# This handles naming collisions with Sinatra/Vlad/Capistrano. Since these use a set()
95118
# helper that defines methods in Object, ANY method_missing ANYWHERE picks up the Vlad/Sinatra
96119
# settings! So settings.deploy_to title actually calls Object.deploy_to (from set :deploy_to, "host"),
97120
# rather than the app_yml['deploy_to'] hash. Jeezus.
98121
def create_accessors!
99122
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)
110124
end
111125
end
112126

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+
113143
end

spec/settingslogic_spec.rb

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
end
5050
e.should_not be_nil
5151
e.message.should =~ /Missing setting 'missing' in/
52-
52+
5353
e = nil
5454
begin
5555
Settings.language.missing
@@ -71,14 +71,32 @@
7171
e.message.should =~ /Missing setting 'erlang' in 'language' section/
7272

7373
Settings.language['erlang'].should be_nil
74-
Settings.language['erlang'] ||= 5
74+
Settings.language['erlang'] = 5
7575
Settings.language['erlang'].should == 5
7676

7777
Settings.language['erlang'] = {'paradigm' => 'functional'}
7878
Settings.language.erlang.paradigm.should == 'functional'
7979

8080
Settings.reload!
8181
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
82100
end
83101

84102
# Put this test last or else call to .instance will load @instance,

spec/spec_helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
# Needed to test Settings3
1313
def collides
14-
'collision'
14+
@collides = 'collision'
1515
end
1616

1717
Spec::Runner.configure do |config|

0 commit comments

Comments
 (0)