Commit 04fddcaf5d0e889bd53e67ef3c767b6295e99296

Authored by Nate Wiger
Committed by Ben Johnson
1 parent e6fec8ed
Exists in master

real-world bugfixes from Vlad and sub-key errors

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 &quot;Settingslogic&quot; 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 &quot;Settingslogic&quot; 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 &quot;Settingslogic&quot; 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,
... ...
spec/spec_helper.rb
... ... @@ -11,7 +11,7 @@ require &#39;settings3&#39;
11 11  
12 12 # Needed to test Settings3
13 13 def collides
14   - 'collision'
  14 + @collides = 'collision'
15 15 end
16 16  
17 17 Spec::Runner.configure do |config|
... ...