Class: ReadWriteSettings

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

Overview

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

Defined Under Namespace

Classes: MissingSetting

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(hash_or_file = self.class.source, section = nil) ⇒ ReadWriteSettings

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.



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# 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

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.



115
116
117
118
119
120
121
# 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

Class Method Details

.[](key) ⇒ Object



36
37
38
# File 'lib/readwritesettings.rb', line 36

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

.[]=(key, val) ⇒ Object



40
41
42
43
44
45
# 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

.get(key) ⇒ Object

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



15
16
17
18
19
20
21
22
# 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

.load!Object



47
48
49
50
# File 'lib/readwritesettings.rb', line 47

def load!
  instance
  true
end

.nameObject

:nodoc:



10
11
12
# File 'lib/readwritesettings.rb', line 10

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

.namespace(value = nil) ⇒ Object



28
29
30
# File 'lib/readwritesettings.rb', line 28

def namespace(value = nil)
  @namespace ||= value
end

.reload!Object



52
53
54
55
# File 'lib/readwritesettings.rb', line 52

def reload!
  @instance = nil
  load!
end

.source(value = nil) ⇒ Object



24
25
26
# File 'lib/readwritesettings.rb', line 24

def source(value = nil)
  @source ||= value
end

.suppress_errors(value = nil) ⇒ Object



32
33
34
# File 'lib/readwritesettings.rb', line 32

def suppress_errors(value = nil)
  @suppress_errors ||= value
end

Instance Method Details

#[](key) ⇒ Object



123
124
125
# File 'lib/readwritesettings.rb', line 123

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

#[]=(key, val) ⇒ Object



127
128
129
130
131
132
# 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

#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
219
220
221
222
223
224
# 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

#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/readwritesettings.rb', line 198

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

#missing_key(msg) ⇒ Object

Raises:



241
242
243
244
245
# File 'lib/readwritesettings.rb', line 241

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

  raise MissingSetting, msg
end

#nested_value(nested_key) ⇒ Object Also known as: exists?



164
165
166
167
168
169
170
171
172
173
# 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

#save(path) ⇒ Object



190
191
192
# File 'lib/readwritesettings.rb', line 190

def save(path)
  File.open(path, "w") { |f| f << to_nested_hash.to_yaml }
end

#set(nested_key, val) ⇒ Object

Create a nested structure and set value. For example: set(“foo.bar.tar”, 123) Resulting ReadWriteSettings/Hash: { “foo” => { “bar” => { “tar” => 123 }}}



138
139
140
141
142
143
144
145
146
147
148
# 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

#set_default(nested_key, val) ⇒ Object

Like #set, but only sets the value if the key is not already set Returns the existing value or the newly-set default value



152
153
154
155
156
157
158
159
160
161
162
# 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

#symbolize_keysObject



226
227
228
229
230
231
232
233
234
235
236
237
238
239
# 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

#to_hashObject

Returns an instance of a Hash object



177
178
179
# File 'lib/readwritesettings.rb', line 177

def to_hash
  Hash[self]
end

#to_nested_hashObject

Convert all nested ReadWriteSettings objects to Hash objects



182
183
184
185
186
187
188
# 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