Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for multiple files #20

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.rdoc
Expand Up @@ -40,6 +40,13 @@ this file in a rails app is app/models/settings.rb


I felt adding a settings file in your app was more straightforward, less tricky, and more flexible. I felt adding a settings file in your app was more straightforward, less tricky, and more flexible.


If multiple files are passed on the source line, comma-separated, they will be loaded in order, with settings in later files overriding any existing keys. This allows you to, for instance, maintain a global settings file in source control, while allowing each developer to override individual settings as needed. Files that are specified but which do not exist will simply be ignored. Thus you can safely do the following without requiring the presence of application_local.yml:

class Settings < Settingslogic
source "#{Rails.root}/config/application.yml", "#{Rails.root}/config/application_local.yml"
namespace Rails.env
end

=== 2. Create your settings === 2. Create your settings


Notice above we specified an absolute path to our settings file called "application.yml". This is just a typical YAML file. Notice above we specified an absolute path to our settings file called "application.yml". This is just a typical YAML file.
Expand Down
77 changes: 64 additions & 13 deletions lib/settingslogic.rb
@@ -1,9 +1,23 @@
require "yaml" require "yaml"
require "erb" require "erb"


class Hash
def deep_merge!(other_hash)
other_hash.each_pair do |k,v|
tv = self[k]
self[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_merge!(v) : v
end
self
end
def deep_delete_nil
delete_if{|k, v| v.nil? or v.instance_of?(Hash) && v.deep_delete_nil.empty?}
end
end

# A simple settings solution using a YAML file. See README for more information. # A simple settings solution using a YAML file. See README for more information.
class Settingslogic < Hash class Settingslogic < Hash
class MissingSetting < StandardError; end class MissingSetting < StandardError; end
class InvalidSettingsFile < StandardError; end


class << self class << self
def name # :nodoc: def name # :nodoc:
Expand All @@ -20,11 +34,12 @@ def get(key)
curs curs
end end


def source(value = nil) def source(*value)
if value.nil? #puts "source! #{value}"
@source if value.nil? || value.empty?
@sources
else else
@source = value @sources= value
end end
end end


Expand Down Expand Up @@ -94,24 +109,60 @@ def create_accessor_for(key)
# Basically if you pass a symbol it will look for that file in the configs directory of your rails app, # 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. # 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. # 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) def initialize(hash_or_file_or_array = self.class.source, section = nil)
#puts "new! #{hash_or_file}" #puts "new! #{hash_or_file_or_array.inspect} (section: #{section})"
case hash_or_file case hash_or_file_or_array
when nil when nil
raise Errno::ENOENT, "No file specified as Settingslogic source" raise Errno::ENOENT, "No file specified as Settingslogic source"
when Hash when Hash
self.replace hash_or_file self.replace hash_or_file_or_array
else when Array
hash = YAML.load(ERB.new(File.read(hash_or_file)).result).to_hash hash = {}
if self.class.namespace ignore_load_error = false
hash = hash[self.class.namespace] or raise MissingSetting, "Missing setting '#{self.class.namespace}' in #{hash_or_file}" hash_or_file_or_array.each_with_index do |filename, n|
#puts "loading from #{filename}"
ignore_load_error = (n!=0)
hash.deep_merge!(load_into_hash(filename, ignore_load_error).deep_delete_nil)
end end
self.replace hash self.replace hash
else
hash = load_into_hash(hash_or_file_or_array)
self.replace hash
end end
@section = section || self.class.source # so end of error says "in application.yml" @section = section || self.class.source # so end of error says "in application.yml"
if @section.is_a?(Array)
@section = @section.first # TODO: is there a better way to preserve which file was used?
end
create_accessors! create_accessors!
end end


def load_into_hash(file, ignore_on_error=false)
unless FileTest.exist?(file)
if ignore_on_error
return {}
else
raise InvalidSettingsFile, file
end
end

#puts "\n\nloading into hash from #{file} (namespace: #{self.class.namespace}) (ignore_error: #{ignore_on_error})"
begin
hash = YAML.load(ERB.new(File.read(file)).result).to_hash
rescue Exception => ex
#puts ex.inspect
#puts "ignoring? #{ignore_on_error}"
if ignore_on_error
return {}
else
raise InvalidSettingsFile, file
end
end
if self.class.namespace
hash = hash[self.class.namespace] or raise MissingSetting, "Missing setting '#{self.class.namespace}' in #{file}"
end
hash
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(name, *args, &block) def method_missing(name, *args, &block)
Expand Down
7 changes: 6 additions & 1 deletion spec/settings.yml
Expand Up @@ -6,9 +6,14 @@ setting1:
value: 2 value: 2


setting2: 5 setting2: 5

setting3: <%= 5 * 5 %> setting3: <%= 5 * 5 %>
name: test name: test


going:
going:
and: going

language: language:
haskell: haskell:
paradigm: functional paradigm: functional
Expand All @@ -19,4 +24,4 @@ collides:
does: not does: not
nested: nested:
collides: collides:
does: not either does: not either
15 changes: 15 additions & 0 deletions spec/settings4.rb
@@ -0,0 +1,15 @@
class Settings4 < Settingslogic
source "#{File.dirname(__FILE__)}/settings.yml", "#{File.dirname(__FILE__)}/settings_local.yml"
end

class Settings4a < Settingslogic
source "#{File.dirname(__FILE__)}/settings.yml", "#{File.dirname(__FILE__)}/settings_local_missing.yml", "#{File.dirname(__FILE__)}/settings_invalid.yml"
end

class Settings4b < Settingslogic
source "#{File.dirname(__FILE__)}/settings_local_missing.yml"
end

class Settings4c < Settingslogic
source "#{File.dirname(__FILE__)}/settings_invalid.yml"
end
3 changes: 3 additions & 0 deletions spec/settings_invalid.yml
@@ -0,0 +1,3 @@
setting1: invalid_value
when: nesting

6 changes: 6 additions & 0 deletions spec/settings_local.yml
@@ -0,0 +1,6 @@
setting2: 10

going:
going:
and: gone

28 changes: 28 additions & 0 deletions spec/settingslogic_spec.rb
Expand Up @@ -44,6 +44,34 @@
Settings3.collides.does.should == 'not' Settings3.collides.does.should == 'not'
end end


it "should override with local settings" do
Settings4.setting2.should == 10
end

it "should override with local nested settings" do
Settings4.going.going.and.should == "gone"
end

it "should not raise error for missing or invalid additional files" do
Settings4a.setting1.setting1_child.should == "saweet"
end

it "should raise an error for a missing initial file" do
begin
Settings4b.setting1
rescue => e
e.should be_kind_of Settingslogic::InvalidSettingsFile
end
end

it "should raise an error for an invalid initial file" do
begin
Settings4c.setting1
rescue => e
e.should be_kind_of Settingslogic::InvalidSettingsFile
end
end

it "should raise a helpful error message" do it "should raise a helpful error message" do
e = nil e = nil
begin begin
Expand Down
5 changes: 3 additions & 2 deletions spec/spec_helper.rb
@@ -1,4 +1,4 @@
require 'spec' require 'rspec'
require 'rubygems' require 'rubygems'
require 'ruby-debug' if RUBY_VERSION < '1.9' # ruby-debug does not work on 1.9.1 yet require 'ruby-debug' if RUBY_VERSION < '1.9' # ruby-debug does not work on 1.9.1 yet


Expand All @@ -8,11 +8,12 @@
require 'settings' require 'settings'
require 'settings2' require 'settings2'
require 'settings3' require 'settings3'
require 'settings4'


# Needed to test Settings3 # Needed to test Settings3
Object.send :define_method, 'collides' do Object.send :define_method, 'collides' do
'collision' 'collision'
end end


Spec::Runner.configure do |config| RSpec.configure do |config|
end end