Class: Settingslogic

Inherits:
Hash
  • Object
show all
Defined in:
lib/settingslogic.rb

Overview

A simple settings solution using a YAML file. See README for more information.

Defined Under Namespace

Classes: InvalidSettingsFile, MissingSetting

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Hash

#deep_delete_nil, #deep_merge!

Constructor Details

#initialize(hash_or_file_or_array = self.class.source, section = nil, options = {}) ⇒ Settingslogic

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)
Settings.new(["defaults.yml", "test.yml"]) # will look for defaults.yml and test.yml and merge them

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 pass an array, it should have strings that are absolute paths to your settings files. Then you can pass a hash, and it just allows you to access the hash via methods.

Options

  • deep_delete_nil: remove nil values from hash ex. :a=>{:b=>nil}.deep_delete_nil => {}

  • replace: if true, replace existing value, by new one, otherwise merge



128
129
130
# File 'lib/settingslogic.rb', line 128

def initialize(hash_or_file_or_array = self.class.source, section = nil, options={})
  load_source(hash_or_file_or_array, section, {:replace => true}.merge(options))
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object

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.



175
176
177
178
179
180
181
# File 'lib/settingslogic.rb', line 175

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

Class Method Details

.[](key) ⇒ Object



64
65
66
# File 'lib/settingslogic.rb', line 64

def [](key)
  instance.fetch(key.to_s, nil)
end

.[]=(key, val) ⇒ Object



68
69
70
71
72
73
# File 'lib/settingslogic.rb', line 68

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

.get(key) ⇒ Object

Enables Settings.get(‘nested.key.name’) for dynamic access



30
31
32
33
34
35
36
37
# File 'lib/settingslogic.rb', line 30

def get(key)
  parts = key.split('.')
  curs = self
  while p = parts.shift
    curs = curs.send(p)
  end
  curs
end

.load!Object



75
76
77
78
# File 'lib/settingslogic.rb', line 75

def load!
  instance
  true
end

.nameObject

:nodoc:



25
26
27
# File 'lib/settingslogic.rb', line 25

def name # :nodoc:
  self.superclass != Hash && instance.key?("name") ? instance.name : super
end

.namespace(value = nil) ⇒ Object



48
49
50
51
52
53
54
# File 'lib/settingslogic.rb', line 48

def namespace(value = nil)
  if value.nil?
    @namespace
  else
    @namespace = value
  end
end

.reload!Object



80
81
82
83
# File 'lib/settingslogic.rb', line 80

def reload!
  @instance = nil
  load!
end

.source(value = nil) ⇒ Object



39
40
41
42
43
44
45
46
# File 'lib/settingslogic.rb', line 39

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

.suppress_errors(value = nil) ⇒ Object



56
57
58
59
60
61
62
# File 'lib/settingslogic.rb', line 56

def suppress_errors(value = nil)
  if value.nil?
    @suppress_errors
  else
    @suppress_errors = value
  end
end

Instance Method Details

#[](key) ⇒ Object



183
184
185
# File 'lib/settingslogic.rb', line 183

def [](key)
  fetch(key.to_s, nil)
end

#[]=(key, val) ⇒ Object



187
188
189
190
191
192
# File 'lib/settingslogic.rb', line 187

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

#create_accessor_for(key, val = nil) ⇒ Object

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/



207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/settingslogic.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) if val
  self.class.class_eval <<-EndEval
    def #{key}
      return @#{key} if @#{key}
      return missing_key("Missing setting '#{key}' in " + @section.to_s) unless has_key? '#{key}'
      value = fetch('#{key}')
      @#{key} = value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in "+ @section.to_s) : value
    end
  EndEval
end

#create_accessors!Object

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.



198
199
200
201
202
# File 'lib/settingslogic.rb', line 198

def create_accessors!
  self.each do |key,val|
    create_accessor_for(key)
  end
end

#load_source(hash_or_file_or_array, section = nil, options = {}) ⇒ Object



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/settingslogic.rb', line 132

def load_source(hash_or_file_or_array, section = nil, options={})
  hash = case hash_or_file_or_array
  when nil
    raise Errno::ENOENT, "No file specified as Settingslogic source"
  when Hash
    hash_or_file_or_array
  when Array
    merge_settings_from_files(hash_or_file_or_array, options)
  else
    merge_settings_from_files([hash_or_file_or_array], options)
  end
  hash.deep_delete_nil if options[:deep_delete_nil]
  options[:replace] ? self.replace(hash) : self.deep_merge!(hash)
  @section = section || self.class.source  # so end of error says "in application.yml"
  create_accessors!
end

#merge_settings_from_files(array, options = {}) ⇒ Object

For each array element - if file exists, parse it to hash if namespace is present take only specified part



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/settingslogic.rb', line 151

def merge_settings_from_files(array, options={})
  hash = array.inject({}) do |sum, file|
    if File.exists?(file)
      begin
        tmp_hash = YAML.load(ERB.new(open(file).read).result).to_hash
        if self.class.namespace
          tmp_hash = tmp_hash[self.class.namespace] || {}
        end
      rescue
        tmp_hash = {}
      # https://github.com/tenderlove/psych/issues/23 (Psych::SyntaxError doesn't inherit from StandardError)
      rescue Psych::SyntaxError
        tmp_hash = {}
      end
      sum.deep_merge!(tmp_hash)
    end
    sum
  end
  raise InvalidSettingsFile, "No correct settings in any of files #{array.inspect}" if hash.empty?
  hash
end

#missing_key(msg) ⇒ Object

Raises:



220
221
222
223
224
# File 'lib/settingslogic.rb', line 220

def missing_key(msg)
  return nil if self.class.suppress_errors

  raise MissingSetting, msg
end