Class: ComplexConfig::Provider

Inherits:
Object
  • Object
show all
Includes:
Shortcuts, Tins::SexySingleton
Defined in:
lib/complex_config/provider.rb,
lib/complex_config/provider/shortcuts.rb

Overview

A provider class that manages configuration loading, caching, and access

The Provider class serves as the central hub for accessing and managing configuration data within the ComplexConfig system. It handles loading configuration files from disk, applying environment-specific settings, processing plugins for dynamic attribute resolution, and providing memoized access to configuration data through caching mechanisms.

Defined Under Namespace

Modules: Shortcuts

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Shortcuts

#complex_config, #complex_config_with_env

Constructor Details

#initializeProvider

Initializes a new provider instance with default settings

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



30
31
32
33
# File 'lib/complex_config/provider.rb', line 30

def initialize
  @plugins     = Set.new
  @deep_freeze = true
end

Instance Attribute Details

#envString

The env method retrieves the current environment name for configuration lookups

This method determines the appropriate environment to use for configuration access by checking various possible sources in order: an explicitly set instance variable, Rails environment if available, the RAILS_ENV environment variable, or falling back to ‘development’ as the default

Returns:

  • (String)

    the name of the current environment



434
435
436
437
438
# File 'lib/complex_config/provider.rb', line 434

def env
  @env || defined?(Rails) && Rails.respond_to?(:env) && Rails.env ||
    ENV['RAILS_ENV'] ||
    'development'
end

#key(pathname = nil) ⇒ String?

The key method retrieves an encryption key from configured sources

This method obtains an encryption key by delegating to the key_source method with the provided pathname, then extracts the actual key value from the returned KeySource object

Parameters:

  • pathname (String, nil) (defaults to: nil)

    The path to a configuration file that may contain a key

Returns:

  • (String, nil)

    The encryption key as a string if found, or nil if no key is available



481
482
483
# File 'lib/complex_config/provider.rb', line 481

def key(pathname = nil)
  key_source(pathname).ask_and_send(:key)
end

#master_key_pathnameString, Pathname

The master_key_pathname method retrieves the configured master key file path

This method returns the explicitly set master key pathname if one has been configured, otherwise it defaults to a ‘master.key’ file within the configuration directory

Returns:

  • (String, Pathname)

    the path to the master key file or a Pathname object representing the default location in the config directory



54
55
56
57
58
59
60
# File 'lib/complex_config/provider.rb', line 54

def master_key_pathname
  if @master_key_pathname
    @master_key_pathname
  else
    config_dir + 'master.key'
  end
end

#pluginsSet<Proc> (readonly)

The plugins attribute reader provides access to the set of plugins registered with this provider

Returns:

  • (Set<Proc>)

    the set containing all registered plugin procs



81
82
83
# File 'lib/complex_config/provider.rb', line 81

def plugins
  @plugins
end

Instance Method Details

#[](name) ⇒ ComplexConfig::Settings

The [] method provides access to configuration data by name

This method serves as a shortcut for retrieving configuration settings by their name, delegating to the config method with the appropriate pathname and name parameters

Parameters:

  • name (String)

    the name of the configuration to retrieve

Returns:



288
289
290
# File 'lib/complex_config/provider.rb', line 288

def [](name)
  config pathname(name), name
end

#add_plugin(plugin) ⇒ self

The add_plugin method adds a new plugin to the provider’s collection of plugins

This method registers a plugin proc with the provider, allowing it to be executed when configuration attributes are accessed and no direct value is found

Parameters:

  • plugin (Proc)

    The plugin proc to add to the provider’s plugins set

Returns:

  • (self)

    Returns self to allow for method chaining



92
93
94
95
# File 'lib/complex_config/provider.rb', line 92

def add_plugin(plugin)
  plugins.add plugin
  self
end

#apply_plugins(setting, id) ⇒ Object?

The apply_plugins method executes registered plugins for a given setting and ID

This method iterates through all registered plugins and attempts to execute each one with the provided setting and ID. It uses catch/throw to handle plugin skipping behavior, returning the first non-skipped plugin result.

Parameters:

Returns:

  • (Object, nil)

    The result from the first applicable plugin, or nil if no plugin applies



138
139
140
141
142
143
144
145
# File 'lib/complex_config/provider.rb', line 138

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

#config(pathname, name = nil) ⇒ ComplexConfig::Settings

The config method reads and processes configuration data from a file

This method loads configuration data from the specified file path, handling both plain YAML files and encrypted YAML files. It processes the configuration data through ERB evaluation, parses it into Ruby objects, and builds a Settings object with appropriate environment-specific values.

Parameters:

  • pathname (String, Pathname)

    The path to the configuration file to read

  • name (String, nil) (defaults to: nil)

    The name to use when building the Settings object, or nil to derive it from the filename

Returns:

  • (ComplexConfig::Settings)

    A Settings object containing the parsed configuration data with environment-specific values

Raises:

See Also:



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/complex_config/provider.rb', line 237

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_nil(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

#config_dirPathname

The config_dir method retrieves the configuration directory path

This method returns the configured configuration directory path, falling back to a default location based on Rails root or the current working directory when no explicit path is set

Returns:

  • (Pathname)

    the configuration directory path



171
172
173
# File 'lib/complex_config/provider.rb', line 171

def config_dir
  @config_dir || (defined?(Rails) && Rails.respond_to?(:root) && Rails.root || Pathname.pwd) + 'config'
end

#config_dir=(dir) ⇒ Object

The config_dir= method sets the configuration directory path for the provider

This setter method assigns a new configuration directory path to the provider instance, allowing it to locate configuration files at the specified location



156
157
158
159
160
161
162
# File 'lib/complex_config/provider.rb', line 156

def config_dir=(dir)
  if dir.nil?
    @config_dir = nil
  else
    @config_dir = Pathname.new(dir)
  end
end

#configure_with(config) ⇒ self

The configure_with method applies the given configuration to this provider instance

This method takes a configuration object and applies its settings to the current provider instance It then flushes the configuration cache to ensure the changes take effect immediately

Parameters:

Returns:

  • (self)

    returns self for chaining operations



72
73
74
75
# File 'lib/complex_config/provider.rb', line 72

def configure_with(config)
  config.configure(self)
  flush_cache
end

#decrypt_config(pathname) ⇒ String?

The decrypt_config method retrieves decrypted configuration data from an encrypted file

Parameters:

  • pathname (String, Pathname)

    the path to the encrypted configuration file

Returns:

  • (String, nil)

    the decrypted configuration content if successful, or nil if decryption fails

See Also:

  • for the internal implementation that handles the actual decryption logic


198
199
200
# File 'lib/complex_config/provider.rb', line 198

def decrypt_config(pathname)
  decrypt_config_case(pathname).first
end

#deep_freeze=(flag) ⇒ Object

The deep_freeze= method sets the deep freezing behavior for configuration objects

This method configures whether configuration settings should be deeply frozen to prevent modification after initialization. When disabling deep freezing, it clears the configuration cache to ensure changes take effect immediately.



106
107
108
109
110
111
# File 'lib/complex_config/provider.rb', line 106

def deep_freeze=(flag)
  if @deep_freeze && !flag
    mize_cache_clear
  end
  @deep_freeze = flag
end

#deep_freeze?TrueClass, FalseClass

The deep_freeze? method checks whether deep freezing is enabled for configuration objects

This method returns a boolean value indicating whether the provider has been configured to deeply freeze configuration settings, preventing modification after initialization

Returns:

  • (TrueClass, FalseClass)

    true if deep freezing is enabled, false otherwise



122
123
124
# File 'lib/complex_config/provider.rb', line 122

def deep_freeze?
  !!@deep_freeze
end

#encrypt_config(pathname, config) ⇒ String

The encrypt_config method encrypts configuration data using a key source

Parameters:

  • pathname (String, Pathname)

    the path to the configuration file to be encrypted

  • config (Object)

    the configuration object to encrypt

Returns:

  • (String)

    the base64-encoded encrypted string including the encrypted data, initialization vector, and authentication tag separated by ‘–’



210
211
212
213
# File 'lib/complex_config/provider.rb', line 210

def encrypt_config(pathname, config)
  ks = key_source(pathname)
  ComplexConfig::Encryption.new(ks.key_bytes).encrypt(config)
end

#evaluate(pathname, data) ⇒ String

The evaluate method processes ERB template data and returns the result

This method takes raw configuration data that may contain ERB templating syntax and evaluates it using Ruby’s built-in ERB processor. It sets up the ERB environment with appropriate trim mode and filename for proper error reporting before executing the template.

Parameters:

  • pathname (String, Pathname)

    The path to the file being evaluated, used for error reporting and debugging purposes

  • data (String)

    The raw configuration data string that may contain ERB templating syntax to be processed

Returns:

  • (String)

    The processed configuration data with all ERB templates evaluated and replaced with their actual values



412
413
414
415
416
# File 'lib/complex_config/provider.rb', line 412

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

#exist?(name) ⇒ TrueClass, FalseClass

The exist? method checks whether a configuration file exists and is accessible

Parameters:

  • name (String)

    the name of the configuration to check for existence

Returns:

  • (TrueClass, FalseClass)

    true if the configuration file exists and can be loaded, false otherwise

See Also:



360
361
362
363
364
# File 'lib/complex_config/provider.rb', line 360

def exist?(name)
  !!config(pathname(name), name)
rescue ComplexConfig::ConfigurationFileMissing, ComplexConfig::EncryptionKeyMissing
  false
end

#flush_cacheComplexConfig::Provider

The flush_cache method clears the configuration cache and returns the receiver

This method invalidates the cached configuration data stored in the provider, ensuring that subsequent configuration accesses will reload the data from source. It is typically used during development when configuration files may have changed or when explicit cache invalidation is required.

Returns:



393
394
395
396
# File 'lib/complex_config/provider.rb', line 393

def flush_cache
  mize_cache_clear
  self
end

#key_source(pathname = nil) ⇒ ComplexConfig::KeySource?

The key_source method retrieves an encryption key from configured sources

This method attempts to find a valid encryption key by checking multiple possible sources in a specific order until one provides a usable key. It prioritizes keys from instance variables, file paths, environment variables, and master key files.

Parameters:

  • pathname (String, nil) (defaults to: nil)

    The path to a configuration file that may contain a key

Returns:

  • (ComplexConfig::KeySource, nil)

    A KeySource object containing the first valid key found, or nil if no key is available



461
462
463
464
465
466
467
468
469
# File 'lib/complex_config/provider.rb', line 461

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

#new_keyString

The new_key method generates a random encryption key

This method creates a secure random key suitable for encryption purposes by generating a hexadecimal string of 32 characters (16 bytes).

Returns:

  • (String)

    a randomly generated hexadecimal encryption key



500
501
502
# File 'lib/complex_config/provider.rb', line 500

def new_key
  SecureRandom.hex(16)
end

#pathname(name) ⇒ Pathname

The pathname method constructs a file path for a configuration file

This method takes a configuration name and returns the full path to the corresponding YAML configuration file by combining the configuration directory with the name and file extension.

Parameters:

  • name (String)

    the name of the configuration file (without extension)

Returns:

  • (Pathname)

    the full path to the configuration file with .yml extension



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

def pathname(name)
  config_dir + "#{name}.yml"
end

#prepare_output(value) ⇒ String

The prepare_output method converts a value into YAML format

This method takes a value and transforms it into a YAML string representation by first converting it to a hash with string keys and then serializing it as YAML

Parameters:

  • value (Object)

    the value to convert to YAML format

Returns:

  • (String)

    the YAML representation of the value



345
346
347
348
349
# File 'lib/complex_config/provider.rb', line 345

def prepare_output(value)
  value.each_with_object({}) do |(k, v), h|
    h[k.to_s] = v
  end.to_yaml
end

#proxy(env = nil) ⇒ ComplexConfig::Proxy

The proxy method creates and returns a new proxy object for dynamic configuration access

This method instantiates a ComplexConfig::Proxy object that defers configuration loading until a method is first called. The proxy supports environment-specific configuration lookups and can handle both direct configuration access and existence checks.

Parameters:

  • env (String, nil) (defaults to: nil)

    The environment name to use for configuration lookups, defaults to nil which will use the default environment

Returns:



378
379
380
# File 'lib/complex_config/provider.rb', line 378

def proxy(env = nil)
  ComplexConfig::Proxy.new(env)
end

#valid_key?(key) ⇒ ComplexConfig::KeySource, FalseClass

The valid_key? method checks whether a given key is valid for encryption purposes

This method attempts to validate an encryption key by creating a KeySource object with the provided key and then trying to instantiate an Encryption object with it to verify the key’s format and validity

Parameters:

  • key (String)

    the encryption key to validate

Returns:



514
515
516
517
518
519
520
# File 'lib/complex_config/provider.rb', line 514

def valid_key?(key)
  ks = ComplexConfig::KeySource.new(var: key)
  ComplexConfig::Encryption.new(ks.key_bytes)
  ks
rescue
  false
end

#write_config(name, value: nil, encrypt: false, store_key: false) ⇒ String, Boolean

The write_config method writes configuration data to a file

This method handles writing configuration data to either a plain YAML file or an encrypted file depending on the encryption settings. It supports storing encryption keys alongside the encrypted configuration file and provides options for specifying the encryption key source.

Parameters:

  • name (String, ComplexConfig::Settings)

    The name of the configuration to write or a Settings object

  • value (Object, nil) (defaults to: nil)

    The configuration value to write, required when name is a string

  • encrypt (Boolean, Symbol, String) (defaults to: false)

    Whether to encrypt the configuration, accepts :random, true, or a hex key string

  • store_key (Boolean) (defaults to: false)

    Whether to store the encryption key in a separate file

Returns:

  • (String, Boolean)

    The encryption key if stored, otherwise true

See Also:



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/complex_config/provider.rb', line 313

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