class ComplexConfig::Provider

@see ComplexConfig::Encryption
@see ComplexConfig::KeySource
@see ComplexConfig::Proxy
@see ComplexConfig::Settings
@see ComplexConfig::Config
access to configuration data through caching mechanisms.
processing plugins for dynamic attribute resolution, and providing memoized
configuration files from disk, applying environment-specific settings,
configuration data within the ComplexConfig system. It handles loading
The Provider class serves as the central hub for accessing and managing
A provider class that manages configuration loading, caching, and access

def [](name)

Returns:
  • (ComplexConfig::Settings) - the configuration settings object for the

Parameters:
  • name (String) -- the name of the configuration to retrieve
def [](name)
  config pathname(name), name
end

def add_plugin(plugin)

Returns:
  • (self) - Returns self to allow for method chaining

Parameters:
  • plugin (Proc) -- The plugin proc to add to the provider's plugins set
def add_plugin(plugin)
  plugins.add plugin
  self
end

def apply_plugins(setting, id)

Returns:
  • (Object, nil) - The result from the first applicable plugin, or nil

Parameters:
  • id (Object) -- The identifier used when executing plugins
  • setting (ComplexConfig::Settings) -- The settings object to apply
def apply_plugins(setting, id)
  plugins.find do |plugin|
    catch :skip do
      value = setting.instance_exec(id, &plugin) and return value
    end
    nil
  end
end

def config(pathname, name = nil)

Other tags:
    See: ComplexConfig::Settings.build - for Settings object construction
    See: evaluate - for ERB processing
    See: decrypt_config_case - for decryption logic

Raises:
  • (TypeError) - If the configuration data cannot be converted to a hash
  • (ComplexConfig::ConfigurationSyntaxError) - If the YAML syntax in the
  • (ComplexConfig::EncryptionKeyMissing) - If an encrypted configuration
  • (ComplexConfig::ConfigurationFileMissing) - If the configuration file

Returns:
  • (ComplexConfig::Settings) - A Settings object containing the parsed

Parameters:
  • name (String, nil) -- The name to use when building the Settings object,
  • pathname (String, Pathname) -- The path to the configuration file to read
def config(pathname, name = nil)
  datas = []
  path_exist = File.exist?(pathname)
  if path_exist
    datas << IO.binread(pathname)
  end
  decrypted, reason, enc_pathname = decrypt_config_case(pathname)
  case reason
  when :ok
    datas << decrypted
  when :key_missing
    datas.empty? and raise ComplexConfig::EncryptionKeyMissing,
      "encryption key for #{enc_pathname.to_s.inspect} is missing"
  when :file_missing
    datas.empty? and raise ComplexConfig::ConfigurationFileMissing,
      "configuration file #{pathname.to_s.inspect} is missing"
  end
  results = datas.map { |d| evaluate(pathname, d) }
  hashes =
    if ::Psych::VERSION < "4"
      results.map { |r| ::YAML.load(r, pathname) }
    else
      results.map { |r| ::YAML.unsafe_load(r, filename: pathname) }
    end
  settings = ComplexConfig::Settings.build(name, hashes.shift)
  hashes.each { |h| settings.attributes_update(h) }
  if shared = settings.shared?
    shared = shared.to_h
    settings.each do |key, value|
      if value.is_a? ComplexConfig::Settings
        value.attributes_update_if_undefined(shared)
      elsif value.nil?
        settings[key] = ComplexConfig::Settings.build(nil, shared.dup)
      end
    end
  end
  deep_freeze? and settings.deep_freeze
  settings
rescue ::Psych::SyntaxError => e
  raise ComplexConfig::ComplexConfigError.wrap(:ConfigurationSyntaxError, e)
end

def config_dir

Returns:
  • (Pathname) - the configuration directory path
def config_dir
  @config_dir || (defined?(Rails) && Rails.respond_to?(:root) && Rails.root || Pathname.pwd) + 'config'
end

def config_dir=(dir)

Other tags:
    Attr_writer: dir - The path to the configuration directory, or nil to
def config_dir=(dir)
  if dir.nil?
    @config_dir = nil
  else
    @config_dir = Pathname.new(dir)
  end
end

def configure_with(config)

Returns:
  • (self) - returns self for chaining operations

Parameters:
  • config (ComplexConfig::Config) -- the configuration object to apply to
def configure_with(config)
  config.configure(self)
  flush_cache
end

def decrypt_config(pathname)

Other tags:
    See: decrypt_config_case - for the internal implementation that handles the

Returns:
  • (String, nil) - the decrypted configuration content if successful, or

Parameters:
  • pathname (String, Pathname) -- the path to the encrypted configuration

Other tags:
    Example: Decrypting a configuration file -
def decrypt_config(pathname)
  decrypt_config_case(pathname).first
end

def decrypt_config_case(pathname)

Returns:
  • (Array) - An array containing the decrypted data (or nil), a symbol

Parameters:
  • pathname (String, Pathname) -- The path to the configuration file to
def decrypt_config_case(pathname)
  enc_pathname = pathname.to_s + '.enc'
  my_ks        = key_source(pathname)
  if File.exist?(enc_pathname)
    if my_ks.ask_and_send(:key)
      text = IO.binread(enc_pathname)
      decrypted = ComplexConfig::Encryption.new(my_ks.key_bytes).decrypt(text)
      return decrypted, :ok, enc_pathname
    else
      return nil, :key_missing, enc_pathname
    end
  end
  return nil, :file_missing, enc_pathname
end

def decrypt_data(ciphertext, pathname = nil)

Returns:
  • (String, nil) - the decrypted plain text, or nil if no valid key was found

Parameters:
  • pathname (String, nil) -- an optional path used to locate a key file (e.g., .key)
  • ciphertext (String) -- the encrypted data to decrypt
def decrypt_data(ciphertext, pathname = nil)
  ks = key_source(pathname)
  return nil unless ks && ks.key
  ComplexConfig::Encryption.new(ks.key_bytes).decrypt(ciphertext)
end

def deep_freeze=(flag)

Other tags:
    Attr_writer: flag - true to enable deep freezing, false to disable it
def deep_freeze=(flag)
  if @deep_freeze && !flag
    mize_cache_clear
  end
  @deep_freeze = flag
end

def deep_freeze?

Returns:
  • (TrueClass, FalseClass) - true if deep freezing is enabled, false
def deep_freeze?
  !!@deep_freeze
end

def encrypt_config(pathname, config)

Returns:
  • (String) - the base64-encoded encrypted string including the

Parameters:
  • config (Object) -- the configuration object to encrypt
  • pathname (String, Pathname) -- the path to the configuration file to be
def encrypt_config(pathname, config)
  ks = key_source(pathname)
  ComplexConfig::Encryption.new(ks.key_bytes).encrypt(config)
end

def env

Returns:
  • (String) - the name of the current environment
def env
  @env || defined?(Rails) && Rails.respond_to?(:env) && Rails.env ||
    ENV['RAILS_ENV'] ||
    'development'
end

def evaluate(pathname, data)

Returns:
  • (String) - The processed configuration data with all ERB templates

Parameters:
  • data (String) -- The raw configuration data string that may contain
  • pathname (String, Pathname) -- The path to the file being evaluated,
def evaluate(pathname, data)
  erb = ::ERB.new(data, trim_mode: '-')
  erb.filename = pathname.to_s
  erb.result
end

def evaluate(pathname, data)

def evaluate(pathname, data)
  erb = ::ERB.new(data, nil, '-')
  erb.filename = pathname.to_s
  erb.result
end

def exist?(name)

Other tags:
    See: ComplexConfig::EncryptionKeyMissing - when an encryption key is required but not available
    See: ComplexConfig::ConfigurationFileMissing - when a configuration file is missing
    See: config - for the underlying configuration loading logic

Returns:
  • (TrueClass, FalseClass) - true if the configuration file exists and

Parameters:
  • name (String) -- the name of the configuration to check for existence
def exist?(name)
  !!config(pathname(name), name)
rescue ComplexConfig::ConfigurationFileMissing, ComplexConfig::EncryptionKeyMissing
  false
end

def flush_cache

Returns:
  • (ComplexConfig::Provider) - the provider instance itself for chaining operations
def flush_cache
  mize_cache_clear
  self
end

def initialize

freezing by default to ensure configuration immutability.
Sets up the provider with an empty plugins collection and enables deep

Initializes a new provider instance with default settings
def initialize
  @plugins     = Set.new
  @deep_freeze = true
end

def interpret_name_value(name, value)

Raises:
  • (ArgumentError) - if the name parameter is not a string/symbol or

Returns:
  • (Array) - An array containing the normalized name and

Parameters:
  • value (Object, nil) -- The value parameter to be processed, typically a
  • name (String, Symbol, ComplexConfig::Settings) -- The name parameter
def interpret_name_value(name, value)
  if ComplexConfig::Settings === name
    if value
      name = name.name_prefix
    else
      value = name.to_h
      name  = name.name_prefix
    end
  elsif name.respond_to?(:to_sym)
    value = value.to_h
  else
    raise ArgumentError, "name has to be either string/symbol or ComplexConfig::Settings"
  end
  return name, value
end

def key(pathname = nil)

Returns:
  • (String, nil) - The encryption key as a string if found, or nil if no

Parameters:
  • pathname (String, nil) -- The path to a configuration file that may
def key(pathname = nil)
  key_source(pathname).ask_and_send(:key)
end

def key_source(pathname = nil)

Returns:
  • (ComplexConfig::KeySource, nil) - A KeySource object containing the

Parameters:
  • pathname (String, nil) -- The path to a configuration file that may
def key_source(pathname = nil)
  [
    ComplexConfig::KeySource.new(var: @key),
    ComplexConfig::KeySource.new(pathname: pathname),
    ComplexConfig::KeySource.new(env_var: 'COMPLEX_CONFIG_KEY'),
    ComplexConfig::KeySource.new(env_var: 'RAILS_MASTER_KEY'),
    ComplexConfig::KeySource.new(master_key_pathname: master_key_pathname),
  ].find(&:key)
end

def master_key_pathname

Returns:
  • (String, Pathname) - the path to the master key file or a Pathname object
def master_key_pathname
  if @master_key_pathname
    @master_key_pathname
  else
    config_dir + 'master.key'
  end
end

def new_key

Returns:
  • (String) - a randomly generated hexadecimal encryption key
def new_key
  SecureRandom.hex(16)
end

def pathname(name)

Returns:
  • (Pathname) - the full path to the configuration file with .yml extension

Parameters:
  • name (String) -- the name of the configuration file (without extension)
def pathname(name)
  config_dir + "#{name}.yml"
end

def prepare_output(value)

Returns:
  • (String) - the YAML representation of the value

Parameters:
  • value (Object) -- the value to convert to YAML format
def prepare_output(value)
  value.each_with_object({}) do |(k, v), h|
    h[k.to_s] = v
  end.to_yaml
end

def provide_key_source(pathname, encrypt)

Raises:
  • (ComplexConfig::EncryptionKeyInvalid) - if the encryption key is

Returns:
  • (ComplexConfig::KeySource) - A key source object configured with the

Parameters:
  • encrypt (Boolean, Symbol, String) -- Encryption directive that
  • pathname (String, nil) -- The path to a configuration file that may
def provide_key_source(pathname, encrypt)
  ks =
    case encrypt
    when :random
      ComplexConfig::KeySource.new(var: new_key)
    when true
      key_source(pathname)
    when String
      if encrypt =~ /\A\h{32}\z/
        ComplexConfig::KeySource.new(var: encrypt)
      else
        raise ComplexConfig::EncryptionKeyInvalid,
          "encryption key has wrong format, has to be hex number of length "\
          "32, was #{encrypt.inspect}"
      end
    end
  ks or raise ComplexConfig::EncryptionKeyInvalid, "encryption key is missing"
end

def proxy(env = nil)

Returns:
  • (ComplexConfig::Proxy) - A new proxy object for dynamic configuration

Parameters:
  • env (String, nil) -- The environment name to use for configuration lookups,
def proxy(env = nil)
  ComplexConfig::Proxy.new(env)
end

def valid_key?(key)

Returns:
  • (ComplexConfig::KeySource, FalseClass) - returns the KeySource object

Parameters:
  • key (String) -- the encryption key to validate
def valid_key?(key)
  ks = ComplexConfig::KeySource.new(var: key)
  ComplexConfig::Encryption.new(ks.key_bytes)
  ks
rescue
  false
end

def write_config(name, value: nil, encrypt: false, store_key: false)

Other tags:
    See: flush_cache - to clear the configuration cache after writing
    See: provide_key_source - for key management
    See: prepare_output - for data formatting

Returns:
  • (String, Boolean) - The encryption key if stored, otherwise true

Parameters:
  • store_key (Boolean) -- Whether to store the encryption key in a
  • encrypt (Boolean, Symbol, String) -- Whether to encrypt the
  • value (Object, nil) -- The configuration value to write, required when
  • name (String, ComplexConfig::Settings) -- The name of the configuration
def write_config(name, value: nil, encrypt: false, store_key: false)
  name, value = interpret_name_value(name, value)
  config_pathname = pathname(name).to_s
  if encrypt
    ks = provide_key_source(config_pathname, encrypt)
    File.secure_write(config_pathname + '.enc') do |out|
      out.write ComplexConfig::Encryption.new(ks.key_bytes).encrypt(prepare_output(value))
    end
    if store_key
      File.secure_write(config_pathname + '.key') do |out|
        out.write ks.key
      end
    end
    ks.key
  else
    File.secure_write(config_pathname) do |out|
      out.puts prepare_output(value)
    end
    true
  end
ensure
  flush_cache
end