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.
- checksums.yaml +4 -4
- data/CHANGES.md +36 -19
- data/README.md +610 -105
- data/Rakefile +6 -4
- data/VERSION +1 -1
- data/bin/complex_config +31 -1
- data/complex_config.gemspec +4 -3
- data/lib/complex_config/config.rb +46 -1
- data/lib/complex_config/encryption.rb +58 -0
- data/lib/complex_config/errors.rb +88 -0
- data/lib/complex_config/key_source.rb +32 -0
- data/lib/complex_config/provider/shortcuts.rb +34 -0
- data/lib/complex_config/provider.rb +342 -0
- data/lib/complex_config/proxy.rb +32 -0
- data/lib/complex_config/railtie.rb +5 -0
- data/lib/complex_config/settings.rb +374 -2
- data/lib/complex_config/tree.rb +93 -0
- data/lib/complex_config/version.rb +1 -1
- data/lib/complex_config.rb +13 -0
- data/spec/spec_helper.rb +4 -5
- metadata +17 -3
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
|
-
##
|
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
|
-
|
125
|
+
```bash
|
126
|
+
gem install complex_config
|
127
|
+
```
|
14
128
|
|
15
129
|
You can also put this line into your Gemfile
|
16
130
|
|
17
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
124
|
-
|
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
|
-
|
570
|
+
# config.env = 'some_environment'
|
127
571
|
|
128
|
-
|
572
|
+
# config.config_dir = Rails.root + 'config'
|
129
573
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
-
|
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
|
-
|
642
|
+
```ruby
|
643
|
+
ComplexConfig::Provider.add_plugin SomeNamespace::PLUGIN
|
644
|
+
```
|
144
645
|
|
145
646
|
or in the configuration block by calling
|
146
647
|
|
147
|
-
|
148
|
-
|
149
|
-
|
648
|
+
```ruby
|
649
|
+
ComplexConfig.configure do |config|
|
650
|
+
config.add_plugin SomeNamespace::PLUGIN
|
651
|
+
end
|
652
|
+
```
|
150
653
|
|
151
|
-
|
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
|
-
|
663
|
+
```ruby
|
664
|
+
require 'monetize'
|
161
665
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
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).
|