Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
solved Vlad collision finally by proxying top-level accessor to instance
  • Loading branch information
Nate Wiger authored and binarylogic committed Feb 13, 2010
1 parent 805df68 commit 77825ec
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 35 deletions.
74 changes: 40 additions & 34 deletions lib/settingslogic.rb
Expand Up @@ -4,7 +4,7 @@
# A simple settings solution using a YAML file. See README for more information.
class Settingslogic < Hash
class MissingSetting < StandardError; end

class << self
def name # :nodoc:
instance.key?("name") ? instance.name : super
Expand Down Expand Up @@ -37,15 +37,14 @@ def namespace(value = nil)
end

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

def []=(key, val)
# Setting[:key][:key2] = 'value' for dynamic settings
val = self.class.new(val, source) if val.is_a? Hash
val = new(val, source) if val.is_a? Hash
instance.store(key.to_s, val)
instance.create_accessor_for(key.to_s, val)
instance.create_accessor_for(key, val)
end

def load!
Expand All @@ -60,12 +59,24 @@ def reload!

private
def instance
@instance ||= new
return @instance if @instance
@instance = new
create_accessors!
@instance
end

def method_missing(name, *args, &block)
instance.send(name, *args, &block)
end

# It would be great to DRY this up somehow, someday, but it's difficult because
# of the singleton pattern. Basically this proxies Setting.foo to Setting.instance.foo
def create_accessors!
instance.each do |key,val|
next unless key.to_s =~ /^\w+$/ # could have "some-setting:" which blows up eval
instance_eval "def #{key}; instance.send(:#{key}); end"
end
end
end

# Initializes a new settings object. You can initialize an object in any of the following ways:
Expand Down Expand Up @@ -103,44 +114,39 @@ def method_missing(name, *args, &block)
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)
create_accessor_for(key, val)
end

private
# 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
# 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.
def create_accessors!
self.each do |key,val|
#puts "accessor_for: #{key}"
create_accessor_for(key)
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}
#puts 'class_eval: #{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
# 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
# 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.
def create_accessors!
self.each do |key,val|
create_accessor_for(key)
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 eval
instance_variable_set("@#{key}", val) if 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
6 changes: 5 additions & 1 deletion spec/settingslogic_spec.rb
Expand Up @@ -38,9 +38,12 @@

it "should not collide with global methods" do
Settings3.nested.collides.does.should == 'not either'
Settings3[:nested] = 'fooey'
Settings3[:nested].should == 'fooey'
Settings3.nested.should == 'fooey'
Settings3.collides.does.should == 'not'
end

it "should raise a helpful error message" do
e = nil
begin
Expand Down Expand Up @@ -77,6 +80,7 @@

Settings.language['erlang'] = {'paradigm' => 'functional'}
Settings.language.erlang.paradigm.should == 'functional'
Settings.respond_to?('erlang').should be_false

Settings.reload!
Settings.language['erlang'].should be_nil
Expand Down

0 comments on commit 77825ec

Please sign in to comment.