Class: Settingslogic

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

Overview

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

Defined Under Namespace

Classes: MissingSetting

Constant Summary collapse

VERSION =
'3.0.3'

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(hash_or_file = self.class.source, section = nil) ⇒ 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)

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.



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/settingslogic.rb', line 123

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
    replace hash_or_file
  else
    file_contents = read_file(hash_or_file)
    hash = file_contents.empty? ? {} : parse_yaml_content(file_contents)
    if self.class.namespace
      hash = hash[self.class.namespace] or
        return missing_key("Missing setting '#{self.class.namespace}' in #{hash_or_file}")
    end

    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) ⇒ 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.



146
147
148
149
150
151
152
153
# File 'lib/settingslogic.rb', line 146

def method_missing(name, *_args)
  key = name.to_s
  return missing_key("Missing setting '#{key}' in #{@section}") unless key? key

  value = fetch(key)
  create_accessor_for(key)
  value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in #{@section}") : value
end

Class Attribute Details

.yaml_permitted_classesObject

Configure additional permitted classes for YAML deserialization Default: [Symbol, Date, Time, DateTime, BigDecimal] Example: Settingslogic.yaml_permitted_classes += [MyCustomClass]



20
21
22
# File 'lib/settingslogic.rb', line 20

def yaml_permitted_classes
  @yaml_permitted_classes ||= [Symbol, Date, Time, DateTime, BigDecimal]
end

Class Method Details

.[](key) ⇒ Object



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

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

.[]=(key, val) ⇒ Object



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

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



42
43
44
45
46
47
48
49
# File 'lib/settingslogic.rb', line 42

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

.load!Object



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

def load!
  instance
  true
end

.nameObject

:nodoc:



13
14
15
# File 'lib/settingslogic.rb', line 13

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

.namespace(value = nil) ⇒ Object



55
56
57
# File 'lib/settingslogic.rb', line 55

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

.reload!Object



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

def reload!
  @instance = nil
  load!
end

.source(value = nil) ⇒ Object



51
52
53
# File 'lib/settingslogic.rb', line 51

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

.suppress_errors(value = nil) ⇒ Object



59
60
61
# File 'lib/settingslogic.rb', line 59

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

.use_yaml_unsafe_loadObject



37
38
39
# File 'lib/settingslogic.rb', line 37

def use_yaml_unsafe_load
  @use_yaml_unsafe_load ||= false
end

.use_yaml_unsafe_load=(value) ⇒ Object

DEPRECATED: Temporarily allow unsafe YAML loading for backwards compatibility This option will be removed in v4.0.0 WARNING: This enables arbitrary code execution vulnerabilities!



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

def use_yaml_unsafe_load=(value)
  if value
    warn '[DEPRECATION] Settingslogic.use_yaml_unsafe_load is deprecated and will be removed in v4.0.0. ' \
         'Please migrate to using Settingslogic.yaml_permitted_classes instead.'
  end
  @use_yaml_unsafe_load = value
end

Instance Method Details

#[](key) ⇒ Object



155
156
157
# File 'lib/settingslogic.rb', line 155

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

#[]=(key, val) ⇒ Object



159
160
161
162
163
164
# File 'lib/settingslogic.rb', line 159

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/



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/settingslogic.rb', line 190

def create_accessor_for(key, val = nil)
  return unless /^\w+$/.match?(key.to_s) # could have "some-setting:" which blows up eval

  instance_variable_set("@#{key}", val)
  self.class.class_eval "    def \#{key}\n      return @\#{key} if @\#{key}\n      return missing_key(\"Missing setting '\#{key}' in \#{@section}\") unless key? '\#{key}'\n      value = fetch('\#{key}')\n      @\#{key} = if value.is_a?(Hash)\n        self.class.new(value, \"'\#{key}' section in \#{@section}\")\n      elsif value.is_a?(Array) && value.all?{|v| v.is_a? Hash}\n        value.map{|v| self.class.new(v)}\n      else\n        value\n      end\n    end\n  ENDEVAL\nend\n", __FILE__, __LINE__ + 1

#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.



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

def create_accessors!
  each do |key, _val|
    create_accessor_for(key)
  end
end

#deep_merge(other_hash) ⇒ Object

Deep merge settings (useful for overrides)



245
246
247
# File 'lib/settingslogic.rb', line 245

def deep_merge(other_hash)
  self.class.new(deep_merge_hash(to_hash, other_hash))
end

#deep_merge!(other_hash) ⇒ Object

Deep merge in place



250
251
252
# File 'lib/settingslogic.rb', line 250

def deep_merge!(other_hash)
  replace(deep_merge_hash(to_hash, other_hash))
end

#stringify_keysObject

Convert all keys to strings recursively (Rails compatibility)



232
233
234
235
236
237
238
239
240
241
242
# File 'lib/settingslogic.rb', line 232

def stringify_keys
  each_with_object({}) do |(key, value), memo|
    k = key.to_s
    v = begin
      send(key)
    rescue StandardError
      value
    end
    memo[k] = v.respond_to?(:stringify_keys) ? v.stringify_keys : v
  end
end

#symbolize_keysObject

Convert all keys to symbols recursively



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/settingslogic.rb', line 211

def symbolize_keys
  each_with_object({}) do |(key, value), memo|
    k = begin
      key.to_sym
    rescue StandardError
      key
    end
    # Access the value properly through the accessor method
    v = respond_to?(key) ? send(key) : value
    # Recursively symbolize nested hashes
    memo[k] = if v.is_a?(self.class)
                v.symbolize_keys
              elsif v.respond_to?(:symbolize_keys)
                v.symbolize_keys
              else
                v
              end
  end
end

#to_aryObject

Prevents Array#flatten from trying to expand Settings objects This fixes RSpec issues when Settings objects are included in arrays



173
174
175
# File 'lib/settingslogic.rb', line 173

def to_ary
  nil
end

#to_hashObject

Returns an instance of a Hash object



167
168
169
# File 'lib/settingslogic.rb', line 167

def to_hash
  to_h
end