lib/settingslogic.rb in settingslogic-2.0.5 vs lib/settingslogic.rb in settingslogic-2.0.6

- old
+ new

@@ -2,16 +2,26 @@ require "erb" # 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 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) if value.nil? @source else @source = value @@ -25,19 +35,20 @@ @namespace = value end end def [](key) - # Setting.key.value or Setting[:key][:value] or Setting['key']['value'] - fetch(key.to_s,nil) + instance.fetch(key.to_s, nil) end - def []=(key,val) - # Setting[:key] = 'value' for dynamic settings - store(key.to_s,val) + def []=(key, val) + # Setting[:key][:key2] = 'value' for dynamic settings + val = new(val, source) if val.is_a? Hash + instance.store(key.to_s, val) + instance.create_accessor_for(key, val) end - + def load! instance true end @@ -46,16 +57,33 @@ load! end 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| + create_accessor_for(key) + end + end + + def create_accessor_for(key) + return unless key.to_s =~ /^\w+$/ # could have "some-setting:" which blows up eval + instance_eval "def #{key}; instance.send(:#{key}); end" + end + end # Initializes a new settings object. You can initialize an object in any of the following ways: # # Settings.new(:application) # will look for config/application.yml @@ -65,49 +93,67 @@ # # Basically if you pass a symbol it will look for that file in the configs directory of your rails app, # if you are using this in rails. If you pass a string it should be an absolute path to your settings file. # Then you can pass a hash, and it just allows you to access the hash via methods. def initialize(hash_or_file = self.class.source, section = nil) + #puts "new! #{hash_or_file}" case hash_or_file + when nil + raise Errno::ENOENT, "No file specified as Settingslogic source" when Hash self.replace hash_or_file else hash = YAML.load(ERB.new(File.read(hash_or_file)).result).to_hash hash = hash[self.class.namespace] if self.class.namespace self.replace hash 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! end # 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. - def method_missing(key, *args, &block) - begin - value = fetch(key.to_s) - rescue IndexError - raise MissingSetting, "Missing setting '#{key}' in #{@section}" - end + def method_missing(name, *args, &block) + key = name.to_s + raise MissingSetting, "Missing setting '#{key}' in #{@section}" unless has_key? key + value = fetch(key) + create_accessor_for(key) value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in #{@section}") : value 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| - # 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/ - 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 + def [](key) + 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, val) + end + + # 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