class ReadWriteSettings
A simple settings solution using a YAML file. See README for more information.
Public Class Methods
# File lib/readwritesettings.rb, line 36 def [](key) instance.fetch(key.to_s, nil) end
# File lib/readwritesettings.rb, line 40 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
Enables Settings.get('nested.key.name') for dynamic access
# File lib/readwritesettings.rb, line 15 def get(key) parts = key.split('.') curs = self while p = parts.shift curs = curs.send(p) end curs end
# File lib/readwritesettings.rb, line 47 def load! instance true end
# File lib/readwritesettings.rb, line 28 def namespace(value = nil) @namespace ||= value 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 Settings.new("application.yaml") # will look for application.yaml Settings.new("/var/configs/application.yml") # will look for /var/configs/application.yml Settings.new(:config1 => 1, :config2 => 2)
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.
# File lib/readwritesettings.rb, line 94 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 ReadWriteSettings source" when Hash self.replace hash_or_file else file_contents = open(hash_or_file).read hash = file_contents.empty? ? {} : YAML.load(ERB.new(file_contents).result).to_hash if self.class.namespace hash = hash[self.class.namespace] or return missing_key("Missing setting '#{self.class.namespace}' in #{hash_or_file}") end self.replace hash end @section = section || self.class.source # so end of error says "in application.yml" create_accessors! end
# File lib/readwritesettings.rb, line 52 def reload! @instance = nil load! end
# File lib/readwritesettings.rb, line 24 def source(value = nil) @source ||= value end
# File lib/readwritesettings.rb, line 32 def suppress_errors(value = nil) @suppress_errors ||= value end
Private Class Methods
# File lib/readwritesettings.rb, line 77 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
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
# File lib/readwritesettings.rb, line 71 def create_accessors! instance.each do |key,val| create_accessor_for(key) end end
# File lib/readwritesettings.rb, line 58 def instance return @instance if @instance @instance = new create_accessors! @instance end
# File lib/readwritesettings.rb, line 65 def method_missing(name, *args, &block) instance.send(name, *args, &block) end
Public Instance Methods
# File lib/readwritesettings.rb, line 123 def [](key) fetch(key.to_s, nil) end
# File lib/readwritesettings.rb, line 127 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
Use instance_eval/class_eval because they're actually more efficient than define_method{} stackoverflow.com/questions/185947/ruby-definemethod-vs-def bmorearty.wordpress.com/2009/01/09/fun-with-rubys-instance_eval-and-class_eval/
# File lib/readwritesettings.rb, line 207 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) self.class.class_eval <<-EndEval def #{key} return @#{key} if @#{key} return missing_key("Missing setting '#{key}' in #{@section}") unless has_key? '#{key}' value = fetch('#{key}') @#{key} = if value.is_a?(Hash) self.class.new(value, "'#{key}' section in #{@section}") elsif value.is_a?(Array) && value.all?{|v| v.is_a? Hash} value.map{|v| self.class.new(v)} else value end end EndEval 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 hash. Jeezus.
# File lib/readwritesettings.rb, line 198 def create_accessors! self.each do |key,val| create_accessor_for(key) end 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.
# File lib/readwritesettings.rb, line 115 def method_missing(name, *args, &block) key = name.to_s return missing_key("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
# File lib/readwritesettings.rb, line 241 def missing_key(msg) return nil if self.class.suppress_errors raise MissingSetting, msg end
# File lib/readwritesettings.rb, line 164 def nested_value(nested_key) target_settings_field = self settings_key_portions = nested_key.to_s.split(".") parent_key_portions, final_key = settings_key_portions[0..-2], settings_key_portions[-1] parent_key_portions.each do |key_portion| target_settings_field[key_portion] ||= ReadWriteSettings.new({}) target_settings_field = target_settings_field[key_portion] end target_settings_field[final_key] end
# File lib/readwritesettings.rb, line 190 def save(path) File.open(path, "w") { |f| f << to_nested_hash.to_yaml } end
Create a nested structure and set value. For example: set(“foo.bar.tar”, 123) Resulting ReadWriteSettings/Hash: { “foo” => { “bar” => { “tar” => 123 }}}
# File lib/readwritesettings.rb, line 138 def set(nested_key, val) target_settings_field = self settings_key_portions = nested_key.to_s.split(".") parent_key_portions, final_key = settings_key_portions[0..-2], settings_key_portions[-1] parent_key_portions.each do |key_portion| target_settings_field[key_portion] ||= ReadWriteSettings.new({}) target_settings_field = target_settings_field[key_portion] end target_settings_field[final_key] = val create_accessors! end
Like set, but only sets the value if the key is not already set Returns the existing value or the newly-set default value
# File lib/readwritesettings.rb, line 152 def set_default(nested_key, val) target_settings_field = self settings_key_portions = nested_key.to_s.split(".") parent_key_portions, final_key = settings_key_portions[0..-2], settings_key_portions[-1] parent_key_portions.each do |key_portion| target_settings_field[key_portion] ||= ReadWriteSettings.new({}) target_settings_field = target_settings_field[key_portion] end target_settings_field[final_key] ||= val target_settings_field[final_key] end
# File lib/readwritesettings.rb, line 226 def symbolize_keys inject({}) do |memo, tuple| k = (tuple.first.to_sym rescue tuple.first) || tuple.first v = k.is_a?(Symbol) ? send(k) : tuple.last # make sure the value is accessed the same way Settings.foo.bar works memo[k] = v && v.respond_to?(:symbolize_keys) ? v.symbolize_keys : v #recurse for nested hashes memo end end
Returns an instance of a Hash object
# File lib/readwritesettings.rb, line 177 def to_hash Hash[self] end
Convert all nested ReadWriteSettings objects to Hash objects
# File lib/readwritesettings.rb, line 182 def to_nested_hash inject({}) do |hash, key_value| key, value = key_value hash[key] = value.respond_to?(:to_nested_hash) ? value.to_nested_hash : value hash end end