Skip to content

Commit

Permalink
real-world bugfixes from Vlad and sub-key errors
Browse files Browse the repository at this point in the history
  • Loading branch information
Nate Wiger authored and binarylogic committed Feb 13, 2010
1 parent e6fec8e commit 04fddca
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 25 deletions.
74 changes: 52 additions & 22 deletions lib/settingslogic.rb
Expand Up @@ -10,6 +10,16 @@ def name # :nodoc:
instance.key?("name") ? instance.name : super instance.key?("name") ? instance.name : super
end 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) def source(value = nil)
if value.nil? if value.nil?
@source @source
Expand All @@ -27,13 +37,15 @@ def namespace(value = nil)
end end


def [](key) def [](key)
# Setting.key.value or Setting[:key][:value] or Setting['key']['value'] # Setting[:key][:key2] or Setting['key']['key2']
fetch(key.to_s,nil) instance.fetch(key.to_s, nil)
end end


def []=(key,val) def []=(key, val)
# Setting[:key] = 'value' for dynamic settings # Setting[:key][:key2] = 'value' for dynamic settings
store(key.to_s,val) 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 end


def load! def load!
Expand Down Expand Up @@ -75,39 +87,57 @@ def initialize(hash_or_file = self.class.source, section = nil)
hash = hash[self.class.namespace] if self.class.namespace hash = hash[self.class.namespace] if self.class.namespace
self.replace hash self.replace hash
end 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! create_accessors!
end end


# Called for dynamically-defined keys, and also the first key deferenced at the top-level, if load! is not used. # 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. # Otherwise, create_accessors! (called by new) will have created actual methods for each key.
def method_missing(key, *args, &block) def method_missing(name, *args, &block)
begin key = name.to_s
value = fetch(key.to_s) raise MissingSetting, "Missing setting '#{key}' in #{@section}" unless has_key? key
rescue IndexError value = fetch(key)
raise MissingSetting, "Missing setting '#{key}' in #{@section}" create_accessor_for(key)
end
value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in #{@section}") : value value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in #{@section}") : value
end 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 private
# This handles naming collisions with Sinatra/Vlad/Capistrano. Since these use a set() # 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 # helper that defines methods in Object, ANY method_missing ANYWHERE picks up the Vlad/Sinatra
# settings! So settings.deploy_to title actually calls Object.deploy_to (from set :deploy_to, "host"), # settings! So settings.deploy_to title actually calls Object.deploy_to (from set :deploy_to, "host"),
# rather than the app_yml['deploy_to'] hash. Jeezus. # rather than the app_yml['deploy_to'] hash. Jeezus.
def create_accessors! def create_accessors!
self.each do |key,val| self.each do |key,val|
# Use instance_eval/class_eval because they're actually more efficient than define_method{} create_accessor_for(key)
# 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
end end
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 end
22 changes: 20 additions & 2 deletions spec/settingslogic_spec.rb
Expand Up @@ -49,7 +49,7 @@
end end
e.should_not be_nil e.should_not be_nil
e.message.should =~ /Missing setting 'missing' in/ e.message.should =~ /Missing setting 'missing' in/

e = nil e = nil
begin begin
Settings.language.missing Settings.language.missing
Expand All @@ -71,14 +71,32 @@
e.message.should =~ /Missing setting 'erlang' in 'language' section/ e.message.should =~ /Missing setting 'erlang' in 'language' section/


Settings.language['erlang'].should be_nil Settings.language['erlang'].should be_nil
Settings.language['erlang'] ||= 5 Settings.language['erlang'] = 5
Settings.language['erlang'].should == 5 Settings.language['erlang'].should == 5


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


Settings.reload! Settings.reload!
Settings.language['erlang'].should be_nil 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 end


# Put this test last or else call to .instance will load @instance, # Put this test last or else call to .instance will load @instance,
Expand Down
2 changes: 1 addition & 1 deletion spec/spec_helper.rb
Expand Up @@ -11,7 +11,7 @@


# Needed to test Settings3 # Needed to test Settings3
def collides def collides
'collision' @collides = 'collision'
end end


Spec::Runner.configure do |config| Spec::Runner.configure do |config|
Expand Down

0 comments on commit 04fddca

Please sign in to comment.