class BetterSettings

Public: Rewrite of BetterSettings to enforce fail-fast and immutability, and avoid extending a core class like Hash which can be problematic.

Constants

RESERVED_METHODS
VALID_SETTING_NAME
VERSION

Attributes

settings[R]

Public Class Methods

new(hash, parent:) click to toggle source

Public: Initializes a new settings object from a Hash or compatible object.

# File lib/better_settings.rb, line 24
def initialize(hash, parent:)
  @settings = hash.to_h.freeze
  @parent = parent

  # Create a getter method for each setting.
  @settings.each { |key, value| create_accessor(key, value) }
end

Private Class Methods

root_settings() click to toggle source

Internal: Methods called at the class level are delegated to this instance.

# File lib/better_settings.rb, line 101
def root_settings
  raise ArgumentError, '`source` must be specified for the settings' unless defined?(@root_settings)

  @root_settings
end
source(file_name, namespace: false, optional: false) click to toggle source

Public: Loads a file as settings (merges it with any previously loaded settings).

# File lib/better_settings.rb, line 81
def source(file_name, namespace: false, optional: false)
  return if !File.exist?(file_name) && optional

  # Load the specified yaml file and instantiate a Settings object.
  settings = new(yaml_to_hash(file_name), parent: file_name)

  # Take one of the settings keys if one is specified.
  settings = settings.public_send(namespace) if namespace

  # Merge settings if a source had previously been specified.
  @root_settings = @root_settings ? @root_settings.merge(settings) : settings

  # Allow to call any settings methods directly on the class.
  singleton_class.extend(Forwardable)
  singleton_class.def_delegators :root_settings, *@root_settings.settings.keys
end
yaml_to_hash(file_name) click to toggle source

Internal: Parses a yml file that can optionally use ERB templating.

# File lib/better_settings.rb, line 108
def yaml_to_hash(file_name)
  return {} if (content = File.open(file_name).read).empty?

  YAML.load(ERB.new(content).result).to_hash
end

Public Instance Methods

merge(other_settings) click to toggle source

Internal: Returns a new Better Settings instance that combines the settings.

# File lib/better_settings.rb, line 33
def merge(other_settings)
  self.class.new(deep_merge(@settings, other_settings.to_h), parent: @parent)
end
method_missing(name, *) click to toggle source

Internal: Display explicit errors for typos and missing settings.

# File lib/better_settings.rb, line 38
def method_missing(name, *)
  raise MissingSetting, "Missing setting '#{ name }' in #{ @parent }"
end

Private Instance Methods

auto_wrap(key, value) click to toggle source

Internal: Wrap nested hashes as settings to allow accessing keys as methods.

# File lib/better_settings.rb, line 45
def auto_wrap(key, value)
  case value
  when Hash then self.class.new(value, parent: "'#{ key }' section in #{ @parent }")
  when Array then value.map { |item| auto_wrap(key, item) }.freeze
  else value.freeze
  end
end
create_accessor(key, value) click to toggle source

Internal: Defines a getter for the specified setting.

# File lib/better_settings.rb, line 54
def create_accessor(key, value)
  raise InvalidSettingKey if !key.is_a?(String) || key !~ VALID_SETTING_NAME || RESERVED_METHODS.include?(key)

  instance_variable_set("@#{ key }", auto_wrap(key, value))
  singleton_class.send(:attr_reader, key)
end
deep_merge(this_hash, other_hash) click to toggle source

Internal: Recursively merges two hashes (in case ActiveSupport is not available).

# File lib/better_settings.rb, line 62
def deep_merge(this_hash, other_hash)
  this_hash.merge(other_hash) do |_key, this_val, other_val|
    if this_val.is_a?(Hash) && other_val.is_a?(Hash)
      deep_merge(this_val, other_val)
    else
      other_val
    end
  end
end