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



457
458
459
460
461
# File 'lib/complex_config/provider.rb', line 457

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



504
505
506
# File 'lib/complex_config/provider.rb', line 504

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



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



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



311
312
313
# File 'lib/complex_config/provider.rb', line 311

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



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.



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.

Raises:

See Also:



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/complex_config/provider.rb', line 260

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

#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



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



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

Examples:

Decrypting a configuration file

provider = ComplexConfig::Provider
# Note: The method automatically appends '.enc' to the provided path
decrypted_yaml = provider.decrypt_config('config/database.yml')
puts decrypted_yaml if decrypted_yaml

See Also:

  • for the internal implementation that handles the actual decryption logic


204
205
206
# File 'lib/complex_config/provider.rb', line 204

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

#decrypt_data(ciphertext, pathname = nil) ⇒ String?

The decrypt_data method decrypts raw ciphertext using the provider's key source

This method bypasses filesystem lookups for the encrypted data itself, allowing decryption of raw bytes. It uses the provided pathname to resolve the encryption key; if no pathname is provided, it relies on environment variables or the master key.



219
220
221
222
223
# File 'lib/complex_config/provider.rb', line 219

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

#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



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



233
234
235
236
# File 'lib/complex_config/provider.rb', line 233

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.



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

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



383
384
385
386
387
# File 'lib/complex_config/provider.rb', line 383

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.



416
417
418
419
# File 'lib/complex_config/provider.rb', line 416

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.



484
485
486
487
488
489
490
491
492
# File 'lib/complex_config/provider.rb', line 484

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



523
524
525
# File 'lib/complex_config/provider.rb', line 523

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.



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



368
369
370
371
372
# File 'lib/complex_config/provider.rb', line 368

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.



401
402
403
# File 'lib/complex_config/provider.rb', line 401

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



537
538
539
540
541
542
543
# File 'lib/complex_config/provider.rb', line 537

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.



336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/complex_config/provider.rb', line 336

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