blueprint_config 1.4.0 → 1.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e1b4cd0cf2aeb8bb83afa7545fd6aca21c103f7902edf56567d0d1bab7fc596
4
- data.tar.gz: a8dcc907c8c5ee3a5cb3073d41accb0b40bf2fa21006af15fceef3ab2e0bb065
3
+ metadata.gz: c1f80d9fbe572d7bbbef333547cb8423732f6ec75765be47e31bcefe3a0a3c0e
4
+ data.tar.gz: 179b6bed1dbc51c9521ec5c6d1ff0951f0d6f5bc1b1436216e0436c1b3b2e86b
5
5
  SHA512:
6
- metadata.gz: 65a35dc0a72caf730aca234e27a24b3f532571fb70480ae12b6ff39d541325d7ab246c43341ead9716a0c5629fafb8695aebbabaae4ce953e8cc4313118d2a6b
7
- data.tar.gz: 6eadf4f45fcb8a9aa1e2dd88f868314469e28d360e4f8014de4fb23f83af3208f20f739a23bde87ab8d6f18bf4437e39e5bb53957d3075cf4949bbf091a4e148
6
+ metadata.gz: bcbe5013a6e6217df4c358a4abbb9ec26fc1f476ffdcff23acad47d06733a99096f3b1bcf6cb114eee06384428a16406a8b188e2c9b2c5992cccfa4e13a55a34
7
+ data.tar.gz: 30bf9fd227b7f6137adc0b66d92d81ed2cec697754995515b96cea57230072f9c00b72074cbfc1b10e72adc5514446824b25068addab9d1da51923228292fff7
@@ -69,4 +69,37 @@
69
69
  </RakeTaskImpl>
70
70
  </option>
71
71
  </component>
72
+ <component name="RakeTasksCache-v2">
73
+ <option name="myRootTask">
74
+ <RakeTaskImpl id="rake">
75
+ <subtasks>
76
+ <RakeTaskImpl description="Build blueprint_config-1.4.0.gem into the pkg directory" fullCommand="build" id="build" />
77
+ <RakeTaskImpl id="build">
78
+ <subtasks>
79
+ <RakeTaskImpl description="Generate SHA512 checksum if blueprint_config-1.4.0.gem into the checksums directory" fullCommand="build:checksum" id="checksum" />
80
+ </subtasks>
81
+ </RakeTaskImpl>
82
+ <RakeTaskImpl description="Remove any temporary products" fullCommand="clean" id="clean" />
83
+ <RakeTaskImpl description="Remove any generated files" fullCommand="clobber" id="clobber" />
84
+ <RakeTaskImpl description="Build and install blueprint_config-1.4.0.gem into system gems" fullCommand="install" id="install" />
85
+ <RakeTaskImpl id="install">
86
+ <subtasks>
87
+ <RakeTaskImpl description="Build and install blueprint_config-1.4.0.gem into system gems without network access" fullCommand="install:local" id="local" />
88
+ </subtasks>
89
+ </RakeTaskImpl>
90
+ <RakeTaskImpl description="Create tag v1.4.0 and build and push blueprint_config-1.4.0.gem to rubygems.org" fullCommand="release[remote]" id="release[remote]" />
91
+ <RakeTaskImpl description="Run RSpec code examples" fullCommand="spec" id="spec" />
92
+ <RakeTaskImpl description="" fullCommand="default" id="default" />
93
+ <RakeTaskImpl description="" fullCommand="release" id="release" />
94
+ <RakeTaskImpl id="release">
95
+ <subtasks>
96
+ <RakeTaskImpl description="" fullCommand="release:guard_clean" id="guard_clean" />
97
+ <RakeTaskImpl description="" fullCommand="release:rubygem_push" id="rubygem_push" />
98
+ <RakeTaskImpl description="" fullCommand="release:source_control_push" id="source_control_push" />
99
+ </subtasks>
100
+ </RakeTaskImpl>
101
+ </subtasks>
102
+ </RakeTaskImpl>
103
+ </option>
104
+ </component>
72
105
  </module>
data/README.md CHANGED
@@ -33,12 +33,135 @@ and load configuration in following order:
33
33
  - settings in database
34
34
  - config/app.local.yml
35
35
 
36
- Settings form database will be available only after rails app initialization.
36
+ Settings from database will be available only after rails app initialization.
37
37
  Everything else can be used in initializers.
38
38
 
39
- ## Accessing configuration variables.
39
+ ## Rails Credentials Handling
40
40
 
41
- You can access configuration variables in any of followining ways:
41
+ BlueprintConfig integrates with Rails credentials system. When Rails is available, it uses `Rails.application.credentials` which automatically handles both global and environment-specific credentials.
42
+
43
+ ### Environment-Specific Credentials (Rails 6+)
44
+
45
+ Rails 6+ supports environment-specific credentials that are automatically merged:
46
+ - Global: `config/credentials.yml.enc` (with `config/master.key`)
47
+ - Environment-specific: `config/credentials/[environment].yml.enc` (with `config/credentials/[environment].key`)
48
+
49
+ Example structure:
50
+ ```
51
+ config/
52
+ ├── credentials.yml.enc # Global credentials
53
+ ├── master.key # Global key (not committed)
54
+ └── credentials/
55
+ ├── development.yml.enc # Development-only credentials
56
+ ├── development.key # Development key (not committed)
57
+ ├── production.yml.enc # Production-only credentials
58
+ └── production.key # Production key (not committed)
59
+ ```
60
+
61
+ When both exist, Rails merges them with environment-specific values taking precedence. BlueprintConfig automatically receives the merged result through `Rails.application.credentials`.
62
+
63
+ Example:
64
+ ```ruby
65
+ # In config/credentials.yml.enc (global):
66
+ aws:
67
+ region: us-east-1
68
+ bucket: myapp
69
+
70
+ # In config/credentials/production.yml.enc:
71
+ aws:
72
+ access_key_id: PROD_KEY_123
73
+ secret_access_key: PROD_SECRET_456
74
+
75
+ # Result in production:
76
+ AppConfig.aws.region # => "us-east-1" (from global)
77
+ AppConfig.aws.bucket # => "myapp" (from global)
78
+ AppConfig.aws.access_key_id # => "PROD_KEY_123" (from production)
79
+ AppConfig.aws.secret_access_key # => "PROD_SECRET_456" (from production)
80
+ ```
81
+
82
+ ## Configuration Recommendations
83
+
84
+ ### Where to Put Your Settings
85
+
86
+ Different types of configuration should be stored in different places based on their nature:
87
+
88
+ #### 1. **Credentials** → Use Rails Credentials
89
+ Sensitive data like API keys, passwords, and tokens:
90
+ ```yaml
91
+ # config/credentials.yml.enc
92
+ stripe:
93
+ secret_key: sk_live_xxxxx
94
+ webhook_secret: whsec_xxxxx
95
+ database:
96
+ password: super_secret_password
97
+ external_api:
98
+ token: bearer_token_xxxxx
99
+ ```
100
+
101
+ #### 2. **Application Configuration** → Use `config/app.yml`
102
+ Rarely changed settings that configure application behavior:
103
+ ```yaml
104
+ # config/app.yml
105
+ smtp:
106
+ server: smtp.example.com
107
+ port: 587
108
+ domain: example.com
109
+ app:
110
+ host: app.example.com
111
+ name: My Application
112
+ support_email: support@example.com
113
+ features:
114
+ signup_enabled: true
115
+ maintenance_mode: false
116
+ ```
117
+
118
+ #### 3. **Dynamic Settings** → Use Database
119
+ Settings that need to change without redeployment:
120
+ ```ruby
121
+ # Stored in settings table, managed via admin interface
122
+ BlueprintConfig::Setting.create(key: 'feature_flags.new_ui', value: 'true')
123
+ BlueprintConfig::Setting.create(key: 'rate_limits.api_calls', value: '1000')
124
+ BlueprintConfig::Setting.create(key: 'maintenance.message', value: 'Back at 3pm')
125
+ ```
126
+
127
+ > **Automatic Reloading**: Database settings are automatically reloaded when changed. The system checks for updates every second and refreshes configuration if database records are newer than the cached values. This means changes take effect immediately without restarting the application.
128
+
129
+ #### 4. **Local Overrides** → Use `config/app.local.yml`
130
+ Development-specific overrides (never commit this file):
131
+ ```yaml
132
+ # config/app.local.yml
133
+ smtp:
134
+ server: localhost
135
+ port: 1025 # Local MailCatcher
136
+ app:
137
+ host: localhost:3000
138
+ debug:
139
+ verbose_logging: true
140
+ ```
141
+
142
+ ### Configuration Precedence
143
+
144
+ Settings are loaded in this order (later sources override earlier ones):
145
+ 1. `config/app.yml` - Base configuration
146
+ 2. Rails credentials - Secure/sensitive data
147
+ 3. ENV variables - Deployment-specific overrides
148
+ 4. Database settings - Runtime-configurable values
149
+ 5. `config/app.local.yml` - Local development overrides
150
+
151
+ ## Accessing Configuration Variables
152
+
153
+ The recommended access style is using dot notation (member access syntax):
154
+
155
+ ```ruby
156
+ # Recommended approach
157
+ AppConfig.action_mailer.smtp_settings.address
158
+ AppConfig.stripe.secret_key
159
+ AppConfig.features.signup_enabled
160
+
161
+ # This provides clean, readable code that mirrors your configuration structure
162
+ ```
163
+
164
+ You can access configuration variables in any of following ways:
42
165
 
43
166
  ### Member access syntax:
44
167
 
@@ -58,7 +181,7 @@ AppConfig.some.var
58
181
  ^^^^
59
182
  ```
60
183
 
61
- If nil is suitable as a default value tyou can use safe navigation operator
184
+ If nil is suitable as a default value you can use safe navigation operator
62
185
 
63
186
  ```ruby
64
187
  irb(main):001> AppConfig.some&.var
@@ -68,7 +191,7 @@ irb(main):001> AppConfig.some&.var
68
191
 
69
192
 
70
193
 
71
- Optionaly you use bang methods to allways raise exception when variable is not defined
194
+ Optionally you can use bang methods to always raise exception when variable is not defined
72
195
 
73
196
  ```ruby
74
197
  irb(main):001> AppConfig.some
@@ -88,7 +211,7 @@ irb(main):002> AppConfig.host?
88
211
  => true
89
212
  ```
90
213
 
91
- Note: Because question mark methods return Boolean you cannot chain em.
214
+ Note: Because question mark methods return Boolean you cannot chain them.
92
215
 
93
216
  ### Hash access syntax
94
217
  You can use both symbols or strings as keys
@@ -128,7 +251,7 @@ irb(main):004> AppConfig.fetch(:var){123}
128
251
 
129
252
  ### Dig
130
253
 
131
- Dig permits to sefely get value in nested structures (including arrays)
254
+ Dig permits to safely get value in nested structures (including arrays)
132
255
 
133
256
  ```ruby
134
257
  irb(main):001> AppConfig.dig(:action_mailer, :smtp_settings, :address)
@@ -146,6 +269,279 @@ irb(main):002> AppConfig.dig!(:action, :smtp_settings, :address)
146
269
 
147
270
  Whenever possible exception message specifies which key is missing.
148
271
 
272
+ ## Testing with Memory Backend
273
+
274
+ In test environments, BlueprintConfig automatically includes a memory backend that allows you to temporarily set configuration values for testing purposes. The memory backend has the highest priority and will override all other configuration sources.
275
+
276
+ ### Setting Values in Tests
277
+
278
+ You can use `AppConfig.set` to temporarily override configuration values:
279
+
280
+ ```ruby
281
+ # Set a single value
282
+ AppConfig.set('smtp.server', 'test.localhost')
283
+
284
+ # Set multiple values with a hash
285
+ AppConfig.set(
286
+ smtp: {
287
+ server: 'localhost',
288
+ port: 1025
289
+ },
290
+ features: {
291
+ new_ui: true
292
+ }
293
+ )
294
+
295
+ # Set deeply nested values
296
+ AppConfig.set('app.mail.settings', {
297
+ from: 'test@example.com',
298
+ reply_to: 'noreply@example.com'
299
+ })
300
+ ```
301
+
302
+ ### RSpec Usage Pattern
303
+
304
+ ```ruby
305
+ RSpec.describe 'MyFeature' do
306
+ context 'with feature enabled' do
307
+ before do
308
+ AppConfig.set('features.new_ui', true)
309
+ end
310
+
311
+ after do
312
+ AppConfig.clear_memory!
313
+ end
314
+
315
+ it 'behaves differently' do
316
+ expect(AppConfig.features.new_ui).to be true
317
+ # test your feature
318
+ end
319
+ end
320
+ end
321
+ ```
322
+
323
+ ### Shared Examples and Contexts
324
+
325
+ ```ruby
326
+ RSpec.shared_context 'with premium features' do
327
+ before do
328
+ AppConfig.set(
329
+ features: {
330
+ premium: true,
331
+ advanced_analytics: true,
332
+ api_limit: 10000
333
+ }
334
+ )
335
+ end
336
+
337
+ after do
338
+ AppConfig.clear_memory!
339
+ end
340
+ end
341
+
342
+ # Use in your specs
343
+ RSpec.describe PremiumService do
344
+ include_context 'with premium features'
345
+
346
+ it 'allows advanced operations' do
347
+ expect(AppConfig.features.premium).to be true
348
+ # test premium functionality
349
+ end
350
+ end
351
+ ```
352
+
353
+ ### Clearing Memory
354
+
355
+ Use `AppConfig.clear_memory!` to clear all memory-stored values:
356
+
357
+ ```ruby
358
+ # In RSpec configuration
359
+ RSpec.configure do |config|
360
+ config.after(:each) do
361
+ AppConfig.clear_memory! if defined?(AppConfig)
362
+ end
363
+ end
364
+
365
+ # Or manually in specific tests
366
+ AppConfig.clear_memory!
367
+ ```
368
+
369
+ ### Important Notes
370
+
371
+ 1. The memory backend is only available in test environment (`Rails.env.test?`)
372
+ 2. Memory values have the highest priority and override all other sources
373
+ 3. Values are stored in memory only and are not persisted
374
+ 4. Remember to clear memory between tests to avoid test pollution
375
+ 5. The `set` method will raise an error if used outside test environment
376
+
377
+ ## Debugging and Inspection
378
+
379
+ ### Viewing Configuration Sources
380
+
381
+ BlueprintConfig tracks where each configuration value comes from. You can use the `source` method to see which backend provided a specific value:
382
+
383
+ ```ruby
384
+ # Get the source of a specific configuration value
385
+ AppConfig.config.source(:smtp, :server)
386
+ # => "BlueprintConfig::Backend::YAML(config/app.yml) smtp.server"
387
+
388
+ AppConfig.config.source(:database, :password)
389
+ # => "BlueprintConfig::Backend::Credentials(global + production) database.password"
390
+
391
+ # In test environment with memory backend
392
+ AppConfig.set('feature.enabled', true)
393
+ AppConfig.config.source(:feature, :enabled)
394
+ # => "BlueprintConfig::Backend::Memory feature.enabled"
395
+
396
+ # For environment variables with specific configuration
397
+ AppConfig.config.source(:api, :key)
398
+ # => "BlueprintConfig::Backend::ENV(whitelist_prefixes: API_, APP_) api.key"
399
+
400
+ # For local overrides
401
+ AppConfig.config.source(:debug, :verbose)
402
+ # => "BlueprintConfig::Backend::YAML(config/app.local.yml) debug.verbose"
403
+
404
+ # For database settings
405
+ AppConfig.config.source(:rate_limit, :max_requests)
406
+ # => "BlueprintConfig::Backend::ActiveRecord(settings table) rate_limit.max_requests"
407
+ ```
408
+
409
+ ### Inspecting All Configuration
410
+
411
+ To view all configuration values as a hash:
412
+
413
+ ```ruby
414
+ # Get all configuration as a Ruby hash
415
+ AppConfig.to_h
416
+ # or
417
+ AppConfig.config.to_h
418
+ # => {
419
+ # smtp: {
420
+ # server: "smtp.gmail.com",
421
+ # port: 587,
422
+ # domain: "example.com"
423
+ # },
424
+ # database: {
425
+ # pool: 5,
426
+ # timeout: 5000
427
+ # },
428
+ # features: {
429
+ # signup_enabled: true,
430
+ # beta_features: false
431
+ # }
432
+ # }
433
+
434
+ # Pretty print configuration in console
435
+ require 'pp'
436
+ pp AppConfig.to_h
437
+
438
+ # Convert to YAML for easier reading
439
+ require 'yaml'
440
+ puts AppConfig.to_h.to_yaml
441
+ # ---
442
+ # :smtp:
443
+ # :server: smtp.gmail.com
444
+ # :port: 587
445
+ # :domain: example.com
446
+ # :database:
447
+ # :pool: 5
448
+ # :timeout: 5000
449
+ # :features:
450
+ # :signup_enabled: true
451
+ # :beta_features: false
452
+ ```
453
+
454
+ ### Viewing Configuration with Sources
455
+
456
+ To see both values and their sources:
457
+
458
+ ```ruby
459
+ # Get configuration with source information
460
+ AppConfig.with_sources
461
+ # or
462
+ AppConfig.config.with_sources
463
+ # => {
464
+ # smtp: {
465
+ # server: {
466
+ # value: "smtp.gmail.com",
467
+ # source: "BlueprintConfig::Backend::YAML(config/app.yml)"
468
+ # },
469
+ # port: {
470
+ # value: 587,
471
+ # source: "BlueprintConfig::Backend::YAML(config/app.yml)"
472
+ # },
473
+ # username: {
474
+ # value: "user@example.com",
475
+ # source: "BlueprintConfig::Backend::Credentials(production)"
476
+ # }
477
+ # },
478
+ # features: {
479
+ # beta_enabled: {
480
+ # value: true,
481
+ # source: "BlueprintConfig::Backend::Memory"
482
+ # }
483
+ # },
484
+ # debug: {
485
+ # verbose: {
486
+ # value: true,
487
+ # source: "BlueprintConfig::Backend::YAML(config/app.local.yml)"
488
+ # }
489
+ # }
490
+ # }
491
+
492
+ # Pretty print with sources
493
+ require 'pp'
494
+ pp AppConfig.config.with_sources
495
+
496
+ # Convert to YAML for easier reading
497
+ require 'yaml'
498
+ puts AppConfig.config.with_sources.to_yaml
499
+ ```
500
+
501
+ ### Checking Available Backends
502
+
503
+ To see which backends are currently loaded:
504
+
505
+ ```ruby
506
+ # List all backends with their configuration
507
+ AppConfig.backends.each do |backend|
508
+ puts backend.source
509
+ end
510
+ # Output:
511
+ # BlueprintConfig::Backend::YAML(config/app.yml)
512
+ # BlueprintConfig::Backend::Credentials(global + production)
513
+ # BlueprintConfig::Backend::ENV(whitelist_prefixes: API_, APP_)
514
+ # BlueprintConfig::Backend::ActiveRecord(settings table)
515
+ # BlueprintConfig::Backend::YAML(config/app.local.yml)
516
+ # BlueprintConfig::Backend::Memory
517
+
518
+ # Check specific backend
519
+ AppConfig.backends[:memory] # => BlueprintConfig::Backend::Memory instance or nil
520
+ AppConfig.backends[:app] # => BlueprintConfig::Backend::YAML instance
521
+ ```
522
+
523
+ ### Configuration Load Order
524
+
525
+ The default configuration load order (later sources override earlier ones):
526
+
527
+ 1. `app` - YAML file (config/app.yml)
528
+ 2. `credentials` - Rails credentials
529
+ 3. `env` - Environment variables
530
+ 4. `db` - Database settings (after Rails initialization)
531
+ 5. `app_local` - Local YAML overrides (config/app.local.yml)
532
+ 6. `memory` - Test-only memory backend (highest priority in test environment)
533
+
534
+ ### Source Tracking Details
535
+
536
+ BlueprintConfig provides detailed source information for debugging:
537
+
538
+ - **YAML files**: Shows the file path (e.g., `config/app.yml`)
539
+ - **Credentials**: Shows whether using global only or merged with environment-specific (e.g., `global + production`)
540
+ - **Environment variables**: Shows any whitelist configuration
541
+ - **Database**: Indicates if connected to settings table
542
+ - **Memory**: Simple indicator for test values
543
+
544
+ Note: While YAML line numbers are not currently tracked, the file path information helps identify which YAML file contains specific values. For credentials, Rails automatically merges global and environment-specific files, and the source indicates which files are in use.
149
545
 
150
546
  ## License
151
547
 
@@ -114,6 +114,14 @@ module BlueprintConfig
114
114
 
115
115
  @updated_at.present? && @updated_at >= max_updated_at
116
116
  end
117
+
118
+ def source
119
+ if @configured && table_exist?
120
+ "#{self.class.name}(settings table)"
121
+ else
122
+ "#{self.class.name}(not available)"
123
+ end
124
+ end
117
125
  end
118
126
  end
119
127
  end
@@ -6,21 +6,40 @@ module BlueprintConfig
6
6
  module Backend
7
7
  class Credentials < Base
8
8
  def load_keys
9
- credentials.to_h
9
+ if defined?(Rails)
10
+ # When Rails is available, use Rails.application.credentials
11
+ # Rails already handles merging global and environment-specific credentials
12
+ Rails.application.credentials.to_h
13
+ else
14
+ # Standalone mode - just load global credentials
15
+ standalone_credentials.to_h
16
+ end
10
17
  end
11
18
 
12
- def credentials
13
- if defined?(Rails)
14
- Rails.application.credentials
19
+ def source
20
+ if defined?(Rails) && Rails.env
21
+ # Check if environment-specific credentials exist
22
+ env_path = "config/credentials/#{Rails.env}.yml.enc"
23
+ if File.exist?(env_path)
24
+ "#{self.class.name}(global + #{Rails.env})"
25
+ else
26
+ "#{self.class.name}(global)"
27
+ end
15
28
  else
16
- ActiveSupport::EncryptedConfiguration.new(
17
- config_path: 'config/credentials.yml.enc',
18
- key_path: 'config/master.key',
19
- env_key: 'RAILS_MASTER_KEY',
20
- raise_if_missing_key: false
21
- )
29
+ "#{self.class.name}"
22
30
  end
23
31
  end
32
+
33
+ private
34
+
35
+ def standalone_credentials
36
+ ActiveSupport::EncryptedConfiguration.new(
37
+ config_path: 'config/credentials.yml.enc',
38
+ key_path: 'config/master.key',
39
+ env_key: 'RAILS_MASTER_KEY',
40
+ raise_if_missing_key: false
41
+ )
42
+ end
24
43
  end
25
44
  end
26
45
  end
@@ -42,6 +42,18 @@ module BlueprintConfig
42
42
 
43
43
  nest_hash(filtered_env, @options[:nest_separator] || '_')
44
44
  end
45
+
46
+ def source
47
+ details = []
48
+ details << "whitelist_keys: #{@options[:whitelist_keys].join(', ')}" if @options[:whitelist_keys]
49
+ details << "whitelist_prefixes: #{@options[:whitelist_prefixes].join(', ')}" if @options[:whitelist_prefixes]
50
+
51
+ if details.any?
52
+ "#{self.class.name}(#{details.join(', ')})"
53
+ else
54
+ self.class.name
55
+ end
56
+ end
45
57
  end
46
58
  end
47
59
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BlueprintConfig
4
+ module Backend
5
+ class Memory < Base
6
+ def initialize
7
+ super
8
+ @store = {}
9
+ end
10
+
11
+ def load_keys
12
+ # Return the flattened store which will be nested by BlueprintConfig
13
+ nest_hash(@store, '.')
14
+ end
15
+
16
+ def set(key, value)
17
+ if value.is_a?(Hash)
18
+ # Handle nested hashes - store them properly for nested access
19
+ nested_hash = flatten_hash(value, key.to_s)
20
+ @store.merge!(nested_hash)
21
+ else
22
+ @store[key.to_s] = value
23
+ end
24
+ end
25
+
26
+ def clear
27
+ @store.clear
28
+ end
29
+
30
+ def fresh?
31
+ true
32
+ end
33
+
34
+ private
35
+
36
+ def flatten_hash(hash, parent_key = '')
37
+ result = {}
38
+ hash.each do |k, v|
39
+ new_key = parent_key.empty? ? k.to_s : "#{parent_key}.#{k}"
40
+ if v.is_a?(Hash)
41
+ result.merge!(flatten_hash(v, new_key))
42
+ else
43
+ result[new_key] = v
44
+ end
45
+ end
46
+ result
47
+ end
48
+ end
49
+ end
50
+ end
@@ -20,6 +20,10 @@ module BlueprintConfig
20
20
  end
21
21
  end
22
22
 
23
+ def source
24
+ "#{self.class.name}(#{@path})"
25
+ end
26
+
23
27
  private
24
28
 
25
29
  def path
@@ -60,5 +60,38 @@ module BlueprintConfig
60
60
  object
61
61
  end
62
62
  end
63
+
64
+ def set(key, value = nil)
65
+ memory_backend = @backends[:memory]
66
+ raise "Memory backend not configured. Only available in test environment." unless memory_backend
67
+
68
+ if key.is_a?(Hash)
69
+ # Handle hash argument: AppConfig.set(foo: { bar: 'baz' })
70
+ key.each do |k, v|
71
+ set(k.to_s, v)
72
+ end
73
+ else
74
+ memory_backend.set(key.to_s, value)
75
+ end
76
+ reload!
77
+ end
78
+
79
+ def clear_memory!
80
+ memory_backend = @backends[:memory]
81
+ return unless memory_backend
82
+
83
+ memory_backend.clear
84
+ reload!
85
+ end
86
+
87
+ def to_h
88
+ reload! unless backends&.fresh?
89
+ config.to_h
90
+ end
91
+
92
+ def with_sources
93
+ reload! unless backends&.fresh?
94
+ config.with_sources
95
+ end
63
96
  end
64
97
  end