Class: MywayConfig::Base

Inherits:
Anyway::Config
  • Object
show all
Defined in:
lib/myway_config/base.rb

Overview

Base configuration class that extends Anyway::Config with additional features

Provides:

  • ConfigSection coercion for nested configuration
  • XDG config file loading
  • Bundled defaults loading with environment overrides
  • Environment detection helpers
  • Deep merge utilities

Examples:

Define a configuration class (recommended)

class MyApp::Config < MywayConfig::Base
  config_name :myapp
  env_prefix :myapp
  defaults_path File.expand_path('config/defaults.yml', __dir__)
  auto_configure!
end

Manual configuration (when custom coercions are needed)

class MyApp::Config < MywayConfig::Base
  config_name :myapp
  env_prefix :myapp
  defaults_path File.expand_path('config/defaults.yml', __dir__)

  attr_config :database, :api, :log_level

  coerce_types(
    database: config_section_coercion(:database),
    api: config_section_coercion(:api),
    log_level: ->(v) { v.to_s.upcase.to_sym }
  )
end

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source = nil) ⇒ Base

Initialize configuration from defaults, a file path, or a Hash

Examples:

Standard usage (defaults + environment)

config = MyConfig.new

Load from a custom file path

config = MyConfig.new('/path/to/custom.yml')
config = MyConfig.new(Pathname.new('/path/to/custom.yml'))

With Hash overrides

config = MyConfig.new(database: { host: 'custom.local' })

Parameters:

  • source (nil, String, Pathname, Hash) (defaults to: nil)

    configuration source

    • nil: use defaults and environment overrides
    • String/Pathname: path to a YAML config file
    • Hash: direct configuration overrides


263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/myway_config/base.rb', line 263

def initialize(source = nil)
  overrides = case source
              when String, Pathname
                load_from_file(source.to_s)
              when Hash
                source
              when nil
                nil
              else
                raise ConfigurationError, "Invalid source: expected String, Pathname, Hash, or nil"
              end

  self.class.validate_environment!
  super(overrides)
end

Class Method Details

.auto_configure!Object

Auto-configure attributes and coercions from the YAML schema

This method reads the defaults section from the YAML file and automatically generates attr_config declarations and coercions. Hash values become ConfigSection objects with method-style access.

Examples:

Minimal config class

class Xyzzy::Config < MywayConfig::Base
  config_name :xyzzy
  env_prefix  :xyzzy
  defaults_path File.expand_path('config/defaults.yml', __dir__)
  auto_configure!
end

Raises:



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/myway_config/base.rb', line 197

def auto_configure!
  raise ConfigurationError, 'defaults_path must be set before auto_configure!' unless @defaults_path

  coercions = {}

  schema.each do |key, value|
    # Don't pass defaults to attr_config - they come from bundled_defaults loader
    # Passing defaults here would cause them to be applied AFTER env vars
    attr_config key

    coercions[key] = if value.is_a?(Hash)
                       config_section_coercion(key)
                     elsif value.is_a?(Symbol)
                       to_symbol
                     elsif boolean?(value)
                       to_boolean
                     end
  end

  coerce_types(coercions.compact)
  define_environment_predicates
  validate_environment!
end

.boolean?(value) ⇒ Boolean

Check if a value is a boolean

Parameters:

  • value (Object)

    the value to check

Returns:

  • (Boolean)

    true if value is true or false



225
226
227
# File 'lib/myway_config/base.rb', line 225

def boolean?(value)
  value.is_a?(TrueClass) || value.is_a?(FalseClass)
end

.config_sectionProc

Simple ConfigSection coercion without schema defaults

Returns:

  • (Proc)

    coercion proc for use with coerce_types



94
95
96
97
98
99
# File 'lib/myway_config/base.rb', line 94

def config_section
  ->(v) {
    return v if v.is_a?(MywayConfig::ConfigSection)
    MywayConfig::ConfigSection.new(v || {})
  }
end

.config_section_coercion(section_key) ⇒ Proc

Create a coercion that merges incoming value with schema defaults for a section

This ensures environment variables don't lose other defaults.

Parameters:

  • section_key (Symbol)

    the section key in the schema

Returns:

  • (Proc)

    coercion proc for use with coerce_types



79
80
81
82
83
84
85
86
87
88
89
# File 'lib/myway_config/base.rb', line 79

def config_section_coercion(section_key)
  defaults = schema[section_key] || {}
  ->(v) {
    return v if v.is_a?(MywayConfig::ConfigSection)

    incoming = v || {}
    # Deep merge: defaults first, then overlay incoming values
    merged = deep_merge_hashes(defaults.dup, incoming)
    MywayConfig::ConfigSection.new(merged)
  }
end

.deep_merge_hashes(base, overlay) ⇒ Hash

Deep merge helper for coercion

Parameters:

  • base (Hash)

    base hash

  • overlay (Hash)

    overlay hash (takes precedence)

Returns:

  • (Hash)

    merged hash



130
131
132
133
134
135
136
137
138
# File 'lib/myway_config/base.rb', line 130

def deep_merge_hashes(base, overlay)
  base.merge(overlay) do |_key, old_val, new_val|
    if old_val.is_a?(Hash) && new_val.is_a?(Hash)
      deep_merge_hashes(old_val, new_val)
    else
      new_val.nil? ? old_val : new_val
    end
  end
end

.defaults_path(path = nil) ⇒ Object

Register a defaults file path for this config class

Parameters:

  • path (String) (defaults to: nil)

    absolute path to the defaults.yml file

Raises:



45
46
47
48
49
50
51
52
53
# File 'lib/myway_config/base.rb', line 45

def defaults_path(path = nil)
  if path
    raise ConfigurationError, "Defaults file not found: #{path}" unless File.exist?(path)
    @defaults_path = path
    # Register with the loader
    MywayConfig::Loaders::DefaultsLoader.register(config_name, path)
  end
  @defaults_path
end

.define_environment_predicatesArray<Symbol>

Define predicate methods for each environment in the config

Generates instance methods like development?, production?, staging? based on the environment names found in the YAML config file.

Returns:

  • (Array<Symbol>)

    list of defined method names



173
174
175
176
177
178
179
180
181
# File 'lib/myway_config/base.rb', line 173

def define_environment_predicates
  valid_environments.map do |env_name|
    method_name = "#{env_name}?"
    define_method(method_name) do
      self.class.env == env_name.to_s
    end
    method_name.to_sym
  end
end

.envString

Get the current environment

Override this method to customize environment detection. Default priority: RAILS_ENV > RACK_ENV > 'development'

Returns:

  • (String)

    current environment name



146
147
148
149
150
151
# File 'lib/myway_config/base.rb', line 146

def env
  Anyway::Settings.current_environment ||
    ENV['RAILS_ENV'] ||
    ENV['RACK_ENV'] ||
    'development'
end

.schemaHash

Load and cache the schema from defaults file

Returns:

  • (Hash)

    the defaults section from the YAML file



58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/myway_config/base.rb', line 58

def schema
  @schema ||= begin
    return {} unless @defaults_path && File.exist?(@defaults_path)

    content = File.read(@defaults_path)
    raw = YAML.safe_load(
      content,
      permitted_classes: [Symbol],
      symbolize_names: true,
      aliases: true
    ) || {}
    raw[:defaults] || {}
  end
end

.to_booleanProc

Boolean coercion helper

Converts string values "true"/"false" to actual booleans. This is needed for environment variable values.

Returns:

  • (Proc)

    coercion proc that converts to boolean



114
115
116
117
118
119
120
121
122
123
# File 'lib/myway_config/base.rb', line 114

def to_boolean
  ->(v) {
    case v
    when TrueClass, FalseClass then v
    when String then v.downcase == 'true'
    when nil then false
    else !!v
    end
  }
end

.to_symbolProc

Symbol coercion helper

Returns:

  • (Proc)

    coercion proc that converts to symbol



104
105
106
# File 'lib/myway_config/base.rb', line 104

def to_symbol
  ->(v) { v.nil? ? nil : v.to_s.to_sym }
end

.valid_environment?Boolean

Check if current environment is valid

Returns:

  • (Boolean)

    true if environment has a config section



163
164
165
# File 'lib/myway_config/base.rb', line 163

def valid_environment?
  MywayConfig::Loaders::DefaultsLoader.valid_environment?(config_name, env)
end

.valid_environmentsArray<Symbol>

Returns list of valid environment names from bundled defaults

Returns:

  • (Array<Symbol>)

    valid environment names



156
157
158
# File 'lib/myway_config/base.rb', line 156

def valid_environments
  MywayConfig::Loaders::DefaultsLoader.valid_environments(config_name)
end

.validate_environment!void

This method returns an undefined value.

Validate that the current environment is defined in the config

Raises:



233
234
235
236
237
238
239
# File 'lib/myway_config/base.rb', line 233

def validate_environment!
  return unless @defaults_path
  return if valid_environment?

  raise ConfigurationError,
        "Invalid environment '#{env}'. Valid environments: #{valid_environments.join(', ')}"
end

Instance Method Details

#environmentString

Get the current environment name

Returns:

  • (String)


309
310
311
# File 'lib/myway_config/base.rb', line 309

def environment
  self.class.env
end

#valid_environment?Boolean

Check if environment is valid

Returns:

  • (Boolean)


316
317
318
# File 'lib/myway_config/base.rb', line 316

def valid_environment?
  self.class.valid_environment?
end