complex_config 0.22.3 โ†’ 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -1,154 +1,657 @@
1
- # ComplexConfig
1
+ # ComplexConfig ๐Ÿง 
2
2
 
3
- ## Description
3
+ ## Description ๐Ÿ“
4
4
 
5
5
  This library makes your YAML configuration files available via a nice API. It
6
6
  also supports different configurations for each `RAILS_ENV` environment and
7
- using plugins to return more complex settings values.
7
+ using plugins to return more complex settings values. ๐Ÿš€
8
8
 
9
- ## Installation
9
+ ## Architecture Overview ๐Ÿ—๏ธ
10
+
11
+ ComplexConfig follows a well-defined architectural pattern with clear
12
+ separation of concerns between several key components:
13
+
14
+ ### Core Components ๐Ÿงฉ
15
+
16
+ **Provider** (`ComplexConfig::Provider`)
17
+ - The central hub managing configuration loading, caching, and access ๐Ÿ”„
18
+ - Handles environment-specific configuration selection ๐ŸŒ
19
+ - Manages plugin registration and execution ๐Ÿ”Œ
20
+ - Provides memoization for performance optimization โšก
21
+
22
+ **Settings** (`ComplexConfig::Settings`)
23
+ - Represents structured configuration data with nested access ๐Ÿ“Š
24
+ - Implements dynamic attribute access through `method_missing` ๐Ÿง 
25
+ - Supports conversion between different representations (hash, YAML, JSON) ๐Ÿ”„
26
+ - Provides deep freezing for immutability ๐Ÿ”’
27
+
28
+ **Proxy** (`ComplexConfig::Proxy`)
29
+ - Enables lazy evaluation of configuration access โณ
30
+ - Defers loading until first method call ๐Ÿšฆ
31
+ - Supports environment-specific lookups ๐ŸŒ
32
+ - Handles safe existence checking โ“
33
+
34
+ ### Component Interactions ๐Ÿ”„
35
+
36
+ ```mermaid
37
+ graph LR
38
+ A[Provider] --> B[Settings]
39
+ A --> C[Proxy]
40
+ B --> D[Configuration Files]
41
+ C --> B
42
+ A --> E[Plugins]
43
+ E --> B
44
+ ```
45
+
46
+ The Provider acts as the main coordinator, loading configuration files and
47
+ creating Settings objects. The Proxy enables lazy loading, while Plugins can
48
+ augment or transform attribute values at runtime.
49
+
50
+ ### Loading Mechanism ๐Ÿ“ฅ
51
+
52
+ ComplexConfig supports two primary access patterns if required via `require
53
+ "complex_config/rude"`:
54
+
55
+ 1. **Environment-aware access** via `cc.config_name` (uses `RAILS_ENV` by
56
+ default): ๐ŸŒ
57
+
58
+ ```ruby
59
+ # Loads config/products.yml and applies environment-specific settings
60
+ cc.products
61
+ ```
62
+
63
+ 2. **Explicit environment access** via `complex_config.config_name` (skips
64
+ automatic environment namespace): ๐Ÿงช
65
+
66
+ ```ruby
67
+ # Loads config/products.yml without environment prefix
68
+ complex_config.products
69
+ ```
70
+
71
+ The proxy system automatically resolves configuration file paths based on
72
+ method names, mapping `cc.products` to `config/products.yml`.
73
+
74
+ ### Environment Prefix Behavior ๐ŸŒ
75
+
76
+ When using the default `cc` accessor, ComplexConfig automatically applies
77
+ environment-specific configurations. For example, with a YAML file like:
78
+
79
+ ```yaml
80
+ development:
81
+ database:
82
+ host: localhost
83
+ port: 5432
84
+ production:
85
+ database:
86
+ host: db.production.example.com
87
+ port: 5432
88
+ ```
89
+
90
+ Accessing `cc.database.host` will return `"localhost"` in development and
91
+ `"db.production.example.com"` in production, automatically selecting the
92
+ appropriate environment section.
93
+
94
+ ### Rails Integration ๐Ÿš€
95
+
96
+ ComplexConfig integrates seamlessly with Rails application lifecycle. During
97
+ development, when Rails reloads classes (or the user types `reload!` into the
98
+ console), ComplexConfig automatically flushes its internal cache to ensure that
99
+ configuration changes are picked up correctly. This behavior is handled through
100
+ the Railtie integration which hooks into Rails' `to_prepare` callback.
101
+
102
+ ### Caching Strategy ๐Ÿ“ฆ
103
+
104
+ ComplexConfig employs memoization through the `mize` gem to cache expensive
105
+ operations like file loading and parsing. In production environments, this
106
+ caching provides performance benefits, while in development, Rails' reloading
107
+ mechanism ensures configuration changes are respected.
108
+
109
+ ### Design Patterns ๐ŸŽฏ
110
+
111
+ - **Delegation**: Provider delegates to Settings for attribute access ๐Ÿ”„
112
+ - **Strategy Pattern**: Plugins provide different strategies for attribute
113
+ resolution ๐Ÿง 
114
+ - **Lazy Loading**: Proxy defers configuration loading until needed โณ
115
+ - **Singleton**: Provider follows singleton pattern for consistent
116
+ configuration access ๐Ÿ”
117
+
118
+ This architecture enables flexible, performant configuration management while
119
+ maintaining clean separation between concerns.
120
+
121
+ ## Installation ๐Ÿ“ฆ
10
122
 
11
123
  You can use rubygems to fetch the gem and install it for you:
12
124
 
13
- # gem install complex_config
125
+ ```bash
126
+ gem install complex_config
127
+ ```
14
128
 
15
129
  You can also put this line into your Gemfile
16
130
 
17
- gem 'complex_config', require: 'complex_config/rude'
131
+ ```ruby
132
+ gem 'complex_config', require: 'complex_config/rude'
133
+ ```
18
134
 
19
135
  and bundle. This command will enable all the default plugins and make the `cc`
20
136
  and `complex_config` shortcuts available. The configurations are expected to be
21
137
  in the `config` subdirectory according to the rails convention.
22
138
 
23
- ## Usage
139
+ ## Usage ๐Ÿ› ๏ธ
24
140
 
25
141
  Given a config file like this and named `config/products.yml`
26
142
 
27
- development:
28
- flux_capacitor:
29
- version_20:
30
- name: Flux Capacitor Version 2.0
31
- price_in_cents: 12_000_00
32
- manual_pdf_url: "http://brown-inc.com/manuals/fc_20.pdf"
33
- components:
34
- - Miniature Chrono-Levitation Chamber (mCLC)
35
- - Single Gravitational Displacement Coil (SGDC)
36
- - Simple Quantum Flux Transducer (SQFT)
37
- - Basic Time-Space Navigation System (BTN)
38
- pro_version:
39
- name: Flux Capacitor Professional
40
- price_in_cents: 23_000_00
41
- manual_pdf_url: "http://brown-inc.com/manuals/fc_pro.pdf"
42
- components:
43
- - Advanced Chrono-Levitation Chamber (ACL)
44
- - Dual Gravitational Displacement Coils (DGDCs)
45
- - Advanced Quantum Flux Transducer (AQFT)
46
- - Professional Time-Space Navigation System (PTNS)
47
- enterprise_version:
48
- name: Flux Capacitor Enterpise
49
- price_in_cents: 1_600_000_00
50
- manual_pdf_url: "http://brown-inc.com/manuals/fc_enterprise.pdf"
51
- components:
52
- - Super-Advanced Chrono-Levitation Chamber (SACL)
53
- - Quadruple Gravitational Displacement Coils (QGDCs)
54
- - Ultra-Advanced Quantum Flux Transducer (UAQFT)
55
- - Enterprise Time-Space Navigation System (ETNS)
56
- test:
57
- flux_capacitor:
58
- test_version:
59
- name: Yadayada
60
- price_in_cents: 6_66
61
- manual_pdf_url: "http://staging.brown-inc.com/manuals/fc_10.pdf"
62
- components:
63
- - Experimental Chrono-Levitation Chamber (ECLC)
64
- - Modular Gravitational Displacement Coils (MGDCs)
65
- - Variable Quantum Flux Transducer (VQFT)
66
- - Development Time-Space Navigation System (DTNS)
67
-
68
- and using `require "complex_config/rude"` in the `"development"` environment you
69
- can now access the configuration.
70
-
71
- ### Accessing configuration settings
143
+ ```yaml
144
+ development:
145
+ flux_capacitor:
146
+ version_20:
147
+ name: Flux Capacitor Version 2.0
148
+ price_in_cents: 12_000_00
149
+ manual_pdf_url: "http://brown-inc.com/manuals/fc_20.pdf"
150
+ components:
151
+ - Miniature Chrono-Levitation Chamber (mCLC)
152
+ - Single Gravitational Displacement Coil (SGDC)
153
+ - Simple Quantum Flux Transducer (SQFT)
154
+ - Basic Time-Space Navigation System (BTN)
155
+ pro_version:
156
+ name: Flux Capacitor Professional
157
+ price_in_cents: 23_000_00
158
+ manual_pdf_url: "http://brown-inc.com/manuals/fc_pro.pdf"
159
+ components:
160
+ - Advanced Chrono-Levitation Chamber (ACL)
161
+ - Dual Gravitational Displacement Coils (DGDCs)
162
+ - Advanced Quantum Flux Transducer (AQFT)
163
+ - Professional Time-Space Navigation System (PTNS)
164
+ enterprise_version:
165
+ name: Flux Capacitor Enterpise
166
+ price_in_cents: 1_600_000_00
167
+ manual_pdf_url: "http://brown-inc.com/manuals/fc_enterprise.pdf"
168
+ components:
169
+ - Super-Advanced Chrono-Levitation Chamber (SACL)
170
+ - Quadruple Gravitational Displacement Coils (QGDCs)
171
+ - Ultra-Advanced Quantum Flux Transducer (UAQFT)
172
+ - Enterprise Time-Space Navigation System (ETNS)
173
+ test:
174
+ flux_capacitor:
175
+ test_version:
176
+ name: Yadayada
177
+ price_in_cents: 6_66
178
+ manual_pdf_url: "http://staging.brown-inc.com/manuals/fc_10.pdf"
179
+ components:
180
+ - Experimental Chrono-Levitation Chamber (ECLC)
181
+ - Modular Gravitational Displacement Coils (MGDCs)
182
+ - Variable Quantum Flux Transducer (VQFT)
183
+ - Development Time-Space Navigation System (DTNS)
184
+ ```
185
+
186
+ and using `require "complex_config/rude"` in the `"development"` environment
187
+ you can now access the configuration.
188
+
189
+ ### Accessing configuration settings ๐Ÿ“ฆ
72
190
 
73
191
  Fetching the name of a product:
74
192
 
75
- > cc.products.flux_capacitor.enterprise_version.name => "Flux Capacitor Enterpise"
193
+ ```ruby
194
+ cc.products.flux_capacitor.enterprise_version.name # => "Flux Capacitor Enterpise"
195
+ ```
76
196
 
77
- If the name of configuration file isn't valid ruby method name syntax you can also
78
- use `cc(:products).flux_capacitorโ€ฆ` to avoid this problem.
197
+ If the name of configuration file isn't valid ruby method name syntax you can
198
+ also use `cc(:products).flux_capacitorโ€ฆ` to avoid this problem.
79
199
 
80
200
  Fetching the price of a product in cents:
81
201
 
82
- > cc.products.flux_capacitor.enterprise_version.price_in_cents => 160000000
202
+ ```ruby
203
+ cc.products.flux_capacitor.enterprise_version.price_in_cents # => 160000000
204
+ ```
83
205
 
84
206
  Fetching the price of a product and using the ComplexConfig::Plugins::MONEY
85
207
  plugin to format it:
86
208
 
87
- > cc.products.flux_capacitor.enterprise_version.price.format => "โ‚ฌ1,600,000.00"
209
+ ```ruby
210
+ cc.products.flux_capacitor.enterprise_version.price.format # => "โ‚ฌ1,600,000.00"
211
+ ```
88
212
 
89
213
  Fetching the URL of a product manual as a string:
90
214
 
91
- > cc.products.flux_capacitor.enterprise_version.manual_pdf_url => "http://brown-inc.com/manuals/fc_enterprise.pdf"
215
+ ```ruby
216
+ cc.products.flux_capacitor.enterprise_version.manual_pdf_url
217
+ # => "http://brown-inc.com/manuals/fc_enterprise.pdf"
218
+ ```
92
219
 
93
220
  Fetching the URL of a product manual and using the ComplexConfig::Plugins::URI
94
221
  plugin return an URI instance:
95
222
 
96
- > cc.products.flux_capacitor.enterprise_version.manual_pdf_uri => #<URI::HTTP:0x007ff626d2a2e8 URL:http://brown-inc.com/manuals/fc_enterprise.pdf>
223
+ ```ruby
224
+ cc.products.flux_capacitor.enterprise_version.manual_pdf_uri
225
+ # => #<URI::HTTP:0x007ff626d2a2e8 URL:http://brown-inc.com/manuals/fc_enterprise.pdf>
226
+ ```
97
227
 
98
228
  You can also fetch config settings from a different environment:
99
229
 
100
- >> pp cc.products(:test); nil
101
- products
102
- โ””โ”€ flux_capacitor
103
- โ””โ”€ test_version
104
- โ”œโ”€ name = "Yadayada"
105
- โ”œโ”€ price_in_cents = 666
106
- โ”œโ”€ manual_pdf_url = "http://staging.brown-inc.com/manuals/fc_10.pdf"
107
- โ””โ”€ components
108
- โ”œโ”€ "Experimental Chrono-Levitation Chamber (ECLC)"
109
- โ”œโ”€ "Modular Gravitational Displacement Coils (MGDCs)"
110
- โ”œโ”€ "Variable Quantum Flux Transducer (VQFT)"
111
- โ””โ”€ "Development Time-Space Navigation System (DTNS)"kk
112
-
113
- Calling `complex_config.products.` instead of `cc(โ€ฆ)` would skip the implicite
230
+ ```ruby
231
+ pp cc.products(:test); nil
232
+ ```
233
+
234
+ and output them.
235
+
236
+ ```
237
+ products
238
+ โ””โ”€ flux_capacitor
239
+ โ””โ”€ test_version
240
+ โ”œโ”€ name = "Yadayada"
241
+ โ”œโ”€ price_in_cents = 666
242
+ โ”œโ”€ manual_pdf_url = "http://staging.brown-inc.com/manuals/fc_10.pdf"
243
+ โ””โ”€ components
244
+ โ”œโ”€ "Experimental Chrono-Levitation Chamber (ECLC)"
245
+ โ”œโ”€ "Modular Gravitational Displacement Coils (MGDCs)"
246
+ โ”œโ”€ "Variable Quantum Flux Transducer (VQFT)"
247
+ โ””โ”€ "Development Time-Space Navigation System (DTNS)"
248
+ ```
249
+
250
+ Calling `complex_config.products.` instead of `cc(โ€ฆ)` would skip the implicit
114
251
  namespacing via the `RAILS_ENV` environment, so
115
252
  `complex_config(:products).test.flux_capacitor` returns the same settings
116
253
  object.
117
254
 
118
- ### Configuration
255
+ ## Encryption Support in ComplexConfig ๐Ÿ›ก๏ธ
256
+
257
+ ComplexConfig provides robust encryption capabilities for securing sensitive
258
+ configuration data, with built-in compatibility with Rails' secret encryption
259
+ system.
260
+
261
+ ### Secure Configuration Files ๐Ÿ”
262
+
263
+ The library supports encrypting YAML configuration files using AES-128-GCM
264
+ encryption. This allows you to store sensitive information like API keys,
265
+ passwords, and other secrets in your version control without exposing them in
266
+ plain text.
267
+
268
+ #### Basic Usage ๐Ÿ› ๏ธ
269
+
270
+ ```ruby
271
+ # Write encrypted configuration
272
+ ComplexConfig::Provider.write_config('database',
273
+ value: { password: 'secret_password' },
274
+ encrypt: :random
275
+ )
276
+ # => Returns the random 128-bit master key as hexadecimal string.
277
+ ```
278
+
279
+ This creates `config/database.yml.enc` which contains the encrypted data and
280
+ returns the master key, which you can provide via an environment variable for
281
+ later read access.
282
+
283
+ #### Key Management ๐Ÿ”‘
284
+
285
+ ComplexConfig supports multiple key sources in priority order:
286
+
287
+ 1. **Explicit key setting**: Direct assignment via `config.key = 'your-key'`
288
+ 2. **Environment variables**: `COMPLEX_CONFIG_KEY` or `RAILS_MASTER_KEY`
289
+ 3. **Key files**: Files with `.key` extension alongside encrypted files
290
+ 4. **Master key files**: `config/master.key` (Rails-compatible)
291
+
292
+ #### Rails Integration ๐Ÿš€
293
+
294
+ ComplexConfig is fully compatible with Rails' secret encryption system:
295
+
296
+ ```ruby
297
+ # Works seamlessly with Rails master.key
298
+ ENV['RAILS_MASTER_KEY'] = '0123456789abcdef0123456789abcdef' # Do not use this key.
299
+ # Encrypted files created by Rails can be read by ComplexConfig
300
+ ```
301
+
302
+ #### Encryption Process ๐Ÿ”
303
+
304
+ The system uses OpenSSL's AES-128-GCM cipher mode which provides both
305
+ confidentiality and authenticity. The encryption process:
306
+
307
+ 1. **Marshals** the configuration object into binary format
308
+ 2. **Encrypts** using AES-128-GCM with a randomly generated IV
309
+ 3. **Authenticates** with an authentication tag to ensure integrity
310
+ 4. **Encodes** all components in base64 and combines them with `--` separators
311
+
312
+ #### Security Features ๐Ÿ›ก๏ธ
313
+
314
+ - **Authenticated Encryption**: Ensures data hasn't been tampered with
315
+ - **Secure Key Handling**: Validates key length requirements (16 bytes for
316
+ AES-128)
317
+ - **Multiple Sources**: Flexible key management for different deployment
318
+ scenarios
319
+ - **Atomic Writes**: Uses secure file writing to prevent corruption during
320
+ encryption operations
321
+
322
+ This approach allows you to maintain sensitive configuration data securely
323
+ while keeping the same familiar YAML-based workflow that developers expect.
324
+
325
+ ### Command-Line Interface and Encryption Management ๐Ÿ›ก๏ธ
326
+
327
+ The `complex_config` executable provides a convenient command-line interface
328
+ for managing encrypted configuration files. This tool is particularly useful
329
+ when you need to securely store sensitive configuration data while maintaining
330
+ easy access during development and deployment.
331
+
332
+ #### Features โš™๏ธ
333
+
334
+ - **Secure File Operations**: Encrypts/decrypts configuration files with
335
+ automatic backup handling
336
+ - **Edit Support**: Opens encrypted files in your preferred editor (`EDITOR`
337
+ environment variable or `vi` by default)
338
+ - **Key Management**: Generates new encryption keys and supports key rotation
339
+ - **Atomic Writes**: Uses secure file writing to prevent data corruption
340
+
341
+ #### Usage Examples ๐Ÿ“‹
342
+
343
+ ```bash
344
+ # Encrypt a configuration file
345
+ complex_config encrypt config/database.yml
119
346
 
120
- You can complex\_config by passing a block to its configure method, which you
347
+ # Decrypt a configuration file
348
+ complex_config decrypt config/database.yml.enc
349
+
350
+ # Edit an encrypted configuration file
351
+ complex_config edit config/database.yml.enc
352
+
353
+ # Display decrypted content without writing to disk
354
+ complex_config display config/database.yml.enc
355
+
356
+ # Generate a new encryption key
357
+ complex_config new_key
358
+
359
+ # Recrypt a file with a different key
360
+ complex_config recrypt -o OLD_KEY -n NEW_KEY config/database.yml.enc
361
+ ```
362
+
363
+ #### Security Considerations โš ๏ธ
364
+
365
+ The script uses symmetric encryption for configuration files. When using
366
+ encrypted configurations:
367
+
368
+ 1. **Key Management**: Store your encryption keys securely (never in version
369
+ control)
370
+ 2. **File Permissions**: Ensure encrypted `.enc` files have appropriate
371
+ permissions
372
+ 3. **Backup Strategy**: The `recrypt` command automatically creates backup
373
+ files
374
+
375
+ #### Integration with ComplexConfig ๐Ÿ”—
376
+
377
+ When you use the `complex_config` executable, it works with the same encryption
378
+ mechanism that ComplexConfig uses internally for its encrypted configuration
379
+ files. This ensures consistency between your application's configuration
380
+ loading and your manual encryption/decryption workflows.
381
+
382
+ The tool is particularly valuable in development environments where you want to
383
+ maintain sensitive configurations without committing them to version control
384
+ while still being able to easily edit and manage them during development.
385
+
386
+ #### Environment Variables ๐Ÿ“
387
+
388
+ - `EDITOR`: Sets the preferred text editor for editing encrypted files
389
+ (defaults to `vi`)
390
+ - `COMPLEX_CONFIG_KEY`: Can be set to provide a default encryption key
391
+
392
+ This command-line tool provides a bridge between secure configuration
393
+ management and practical workflow needs, making it easier to maintain encrypted
394
+ configurations without sacrificing usability.
395
+
396
+ ## Debugging and Troubleshooting ๐Ÿ”
397
+
398
+ ComplexConfig provides several built-in methods for inspecting and debugging
399
+ configuration data:
400
+
401
+ ### Inspecting Configuration Structure ๐Ÿงฎ
402
+
403
+ To see all available attributes and their values in a structured format:
404
+ ```ruby
405
+ puts cc.products.flux_capacitor.enterprise_version.attributes_list
406
+ ```
407
+
408
+ This outputs a representation showing all configuration paths and values listed
409
+ like this:
410
+
411
+ ```
412
+ name = "Flux Capacitor Enterpise"
413
+ price_in_cents = 160000000
414
+ manual_pdf_url = "http://brown-inc.com/manuals/fc_enterprise.pdf"
415
+ components[0] = "Super-Advanced Chrono-Levitation Chamber (SACL)"
416
+ components[1] = "Quadruple Gravitational Displacement Coils (QGDCs)"
417
+ components[2] = "Ultra-Advanced Quantum Flux Transducer (UAQFT)"
418
+ components[3] = "Enterprise Time-Space Navigation System (ETNS)"
419
+ ```
420
+
421
+ ### Visual Tree Representation ๐ŸŒฒ
422
+
423
+ For a more visual representation of the configuration hierarchy:
424
+
425
+ ```ruby
426
+ puts cc.products.flux_capacitor.enterprise_version
427
+ ```
428
+
429
+ This displays the configuration in a tree format:
430
+ ```
431
+ products.flux_capacitor.enterprise_version
432
+ โ”œโ”€ name = "Flux Capacitor Enterpise"
433
+ โ”œโ”€ price_in_cents = 160000000
434
+ โ”œโ”€ manual_pdf_url = "http://brown-inc.com/manuals/fc_enterprise.pdf"
435
+ โ””โ”€ components
436
+ โ”œโ”€ "Super-Advanced Chrono-Levitation Chamber (SACL)"
437
+ โ”œโ”€ "Quadruple Gravitational Displacement Coils (QGDCs)"
438
+ โ”œโ”€ "Ultra-Advanced Quantum Flux Transducer (UAQFT)"
439
+ โ””โ”€ "Enterprise Time-Space Navigation System (ETNS)"
440
+ ```
441
+
442
+ These debugging methods are particularly useful during development when you
443
+ need to verify that your configuration files are loaded correctly and contain
444
+ the expected values.
445
+
446
+ ## Error Handling โš ๏ธ
447
+
448
+ ComplexConfig provides a comprehensive error handling system with specific
449
+ exceptions for different failure scenarios, following Ruby conventions for
450
+ predictable behavior.
451
+
452
+ ### Exception Hierarchy ๐Ÿ“š
453
+
454
+ The library defines a clear exception hierarchy that inherits from
455
+ `ComplexConfig::ComplexConfigError`:
456
+
457
+ ```mermaid
458
+ classDiagram
459
+ class ComplexConfigError {
460
+ <<abstract>>
461
+ +message
462
+ }
463
+
464
+ class AttributeMissing
465
+ class ConfigurationFileMissing
466
+ class ConfigurationSyntaxError
467
+ class EncryptionError {
468
+ <<abstract>>
469
+ }
470
+ class EncryptionKeyInvalid
471
+ class EncryptionKeyMissing
472
+ class DecryptionFailed
473
+
474
+ ComplexConfigError <|-- AttributeMissing
475
+ ComplexConfigError <|-- ConfigurationFileMissing
476
+ ComplexConfigError <|-- ConfigurationSyntaxError
477
+ ComplexConfigError <|-- EncryptionError
478
+ EncryptionError <|-- EncryptionKeyInvalid
479
+ EncryptionError <|-- EncryptionKeyMissing
480
+ EncryptionError <|-- DecryptionFailed
481
+ ```
482
+
483
+ ### Error Scenarios ๐Ÿšจ
484
+
485
+ #### Configuration File Access ๐Ÿ“
486
+
487
+ When a configuration file is missing, `ConfigurationFileMissing` is raised.
488
+ This includes both regular `.yml` files and encrypted `.yml.enc` files:
489
+
490
+ ```ruby
491
+ # Raises ComplexConfig::ConfigurationFileMissing if config/products.yml doesn't exist
492
+ cc.products.flux_capacitor.enterprise_version.name
493
+ ```
494
+
495
+ #### YAML Syntax Errors ๐Ÿงพ
496
+
497
+ Invalid YAML syntax in configuration files raises `ConfigurationSyntaxError`,
498
+ which wraps the underlying Psych::SyntaxError to provide context:
499
+
500
+ ```ruby
501
+ # Raises ComplexConfig::ConfigurationSyntaxError for malformed YAML
502
+ cc.products # ... with invalid YAML content
503
+ ```
504
+
505
+ #### Attribute Access Errors โŒ
506
+
507
+ Accessing non-existent attributes raises `AttributeMissing`:
508
+
509
+ ```ruby
510
+ # Raises ComplexConfig::AttributeMissing if 'nonexistent_attribute' doesn't exist
511
+ cc.products.nonexistent_attribute
512
+ ```
513
+
514
+ #### Encryption Errors ๐Ÿ”
515
+
516
+ When using encrypted configuration files without proper encryption keys,
517
+ `EncryptionKeyMissing` is raised:
518
+
519
+ ```ruby
520
+ # Raises ComplexConfig::EncryptionKeyMissing when no key is available for .enc files
521
+ cc.products # ... with encrypted config but missing key
522
+ ```
523
+
524
+ ### Safe Access Patterns ๐Ÿ›ก๏ธ
525
+
526
+ ComplexConfig supports safe access patterns to avoid exceptions in conditional
527
+ contexts:
528
+
529
+ ```ruby
530
+ # Using method names ending with '?' returns nil instead of raising exceptions
531
+ cc.products?(:test) # Returns nil if 'test' environment doesn't exist
532
+ cc.products.flux_capacitor.enterprise_version.name? # Returns nil if name attribute missing
533
+
534
+ # Safe access to configuration that may not exist
535
+ if cc.products?(:test)
536
+ # Safe to access test config here
537
+ end
538
+ ```
539
+
540
+ ### Error Recovery ๐Ÿ› ๏ธ
541
+
542
+ For robust applications, consider wrapping critical configuration access in
543
+ exception handlers:
544
+
545
+ ```ruby
546
+ begin
547
+ price = cc.products.flux_capacitor.enterprise_version.price_in_cents
548
+ rescue ComplexConfig::ConfigurationFileMissing => e
549
+ Rails.logger.warn "Configuration file missing: #{e.message}"
550
+ # Provide default value or fallback behavior
551
+ rescue ComplexConfig::ConfigurationSyntaxError => e
552
+ Rails.logger.error "Invalid YAML in configuration: #{e.message}"
553
+ # Handle invalid syntax appropriately
554
+ end
555
+ ```
556
+
557
+ The error handling system ensures that configuration loading and access
558
+ failures are predictable and can be handled gracefully by application code.
559
+
560
+ ## Configuration โš™๏ธ
561
+
562
+ You can `complex_config` by passing a block to its configure method, which you
121
563
  can for example do in a rails config/initializers file:
122
564
 
123
- ComplexConfig.configure do |config|
124
- config.deep_freeze = !Rails.env.test? # allow modification during tests b/c of stubs etc.
565
+ ```ruby
566
+ ComplexConfig.configure do |config|
567
+ # Allow modification during tests b/c of stubs etc.
568
+ config.deep_freeze = !Rails.env.test?
125
569
 
126
- # config.env = 'some_environment'
570
+ # config.env = 'some_environment'
127
571
 
128
- # config.config_dir = Rails.root + 'config'
572
+ # config.config_dir = Rails.root + 'config'
129
573
 
130
- config.add_plugin -> id do
131
- if base64_string = ask_and_send("#{id}_base64")
132
- Base64.decode64 base64_string
133
- else
134
- skip
135
- end
136
- end
574
+ config.add_plugin -> id do
575
+ if base64_string = ask_and_send("#{id}_base64")
576
+ Base64.decode64 base64_string
577
+ else
578
+ skip
137
579
  end
580
+ end
581
+ end
582
+ ```
583
+
584
+ - **`#env`**: Explicitly sets the environment instead of auto-detecting from
585
+ `RAILS_ENV`
586
+
587
+ - **`#config_dir`**: Changes the directory where configuration files are loaded
588
+ from instead of using the default `config` folder
589
+
590
+ - **`add_plugin`** registers custom lambdas that can transform configuration
591
+ values at runtime, allowing for intelligent data processing like base64
592
+ decoding or automatic type conversion when accessing config attributes. See
593
+ [Adding plugins](#adding-plugins-) below.
138
594
 
139
- ### Adding plugins
595
+ ๐Ÿ“ **Note** the `deep_freeze` setting, that is just enabled during testing and
596
+ is explained in the [next section](#frozen-configuration-safety-and-optimization-).
597
+
598
+ ### Frozen Configuration: Safety and Optimization ๐Ÿ”’
599
+
600
+ The `deep_freeze` setting controls whether configuration objects are deeply
601
+ frozen after initialization. When enabled (default), this provides several
602
+ important benefits:
603
+
604
+ #### Safety Benefits ๐Ÿ›ก๏ธ
605
+
606
+ - **Immutability**: Configuration values cannot be modified after loading,
607
+ preventing accidental runtime changes ๐Ÿšซ
608
+ - **Thread Safety**: Frozen configurations can be safely shared across threads
609
+ without synchronization ๐Ÿ”„
610
+ - **Security**: Protects against runtime tampering of critical configuration
611
+ data ๐Ÿ”’
612
+
613
+ #### Performance Benefits โšก
614
+
615
+ - **Memory Efficiency**: Ruby's garbage collector can optimize frozen objects
616
+ more effectively ๐Ÿง 
617
+ - **Cache Efficiency**: Immutable objects can be cached more aggressively ๐Ÿ’พ
618
+ - **CPU Optimization**: Ruby's internal optimizations for frozen objects can
619
+ provide performance improvements, if configuration settings are accessed
620
+ often. โš™๏ธ
621
+ - **Predictable Behavior**: Eliminates potential race conditions and state
622
+ corruption ๐Ÿงญ
623
+
624
+ #### Development Considerations ๐Ÿงช
625
+
626
+ In test environments, you might disable deep freezing to allow for easier
627
+ testing and modification:
628
+
629
+ ```ruby
630
+ ComplexConfig.configure do |config|
631
+ config.deep_freeze = false
632
+ end
633
+ ```
634
+
635
+ This setting is particularly important in production environments where
636
+ configuration stability and performance are paramount.
637
+
638
+ ### Adding plugins ๐Ÿ”Œ
140
639
 
141
640
  You can add your own plugins by calling
142
641
 
143
- ComplexConfig::Provider.add_plugin SomeNamespace::PLUGIN
642
+ ```ruby
643
+ ComplexConfig::Provider.add_plugin SomeNamespace::PLUGIN
644
+ ```
144
645
 
145
646
  or in the configuration block by calling
146
647
 
147
- ComplexConfig.configure do |config|
148
- config.add_plugin SomeNamespace::PLUGIN
149
- end
648
+ ```ruby
649
+ ComplexConfig.configure do |config|
650
+ config.add_plugin SomeNamespace::PLUGIN
651
+ end
652
+ ```
150
653
 
151
- ### Implementing your own plugins
654
+ #### Implementing your own plugins ๐Ÿ› ๏ธ
152
655
 
153
656
  A plugin is just a lambda expression with a single argument `id` which
154
657
  identifies the attribute that is being accessed. If it calls `skip` it won't
@@ -157,28 +660,30 @@ returns a value instead.
157
660
 
158
661
  Here is the `ComplexConfig::Plugins::MONEY` plugin for example:
159
662
 
160
- require 'monetize'
663
+ ```ruby
664
+ require 'monetize'
161
665
 
162
- module ComplexConfig::Plugins
163
- MONEY = -> id do
164
- if cents = ask_and_send("#{id}_in_cents")
165
- Money.new(cents)
166
- else
167
- skip
168
- end
169
- end
666
+ module ComplexConfig::Plugins
667
+ MONEY = -> id do
668
+ if cents = ask_and_send("#{id}_in_cents")
669
+ Money.new(cents)
670
+ else
671
+ skip
170
672
  end
673
+ end
674
+ end
675
+ ```
171
676
 
172
- ## Download
677
+ ## Download ๐Ÿ“ฅ
173
678
 
174
679
  The homepage of this library is located at
175
680
 
176
681
  * https://github.com/flori/complex_config
177
682
 
178
- ## Author
683
+ ## Author ๐Ÿ‘จโ€๐Ÿ’ป
179
684
 
180
685
  [Florian Frank](mailto:flori@ping.de)
181
686
 
182
- ## License
687
+ ## License ๐Ÿ“„
183
688
 
184
- This software is licensed under the Apache 2.0 license.
689
+ This software is licensed under the [Apache 2.0 license](LICENSE).