complex_config 0.25.0 → 0.26.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: a31f4ffa0d9542e26c19f2a3e6e664e996ebc69d4c700832159bbf4448d3e881
4
- data.tar.gz: c81b874b5e802bb66b888727ed63c0573be0ee68e9b141d4ef54478b352d6406
3
+ metadata.gz: 344002a57feb04b6b964c0a22190b91eb6516329583141521bb28b1151801a12
4
+ data.tar.gz: c4827ea899280f0a61fb12aff86e38873e108146a21fddac406417b0a8aac838
5
5
  SHA512:
6
- metadata.gz: 6de56cb12568bbb945ddc06fae9065e98de0e09ba48d18a7bb1c0ec05b47e5e8995f1413a46fb425f0e02392b427ad35f763dd7d668d00a0565ea998b85484f9
7
- data.tar.gz: abb1787b14b12a6565134a95480cff03cc69efac45cbb455c6ddb774a5911fa891b90d91e02c6518b43817e3cc56b9880f8af40f22d213a8e6f5b03081ac375f
6
+ metadata.gz: 81baebce37c27193f528b07c5b5104d3527de3ece471df7493664832b53a5c69a24c120200e5814e34eeeeac9c31c4fbc28ede81ad8957f59cf98533bc40569d
7
+ data.tar.gz: b22c324308a9b432a1134cb357ab98b8656f00c72052d462fcbe7c7c5972b4f7583f29d1fa332c94be80225bd44ebbc4137e77b6b1d7a9f9ab2c87b35b67ac55
@@ -0,0 +1,51 @@
1
+ # Simple workflow for deploying static content to GitHub Pages
2
+ name: Deploy static content to Pages
3
+
4
+ on:
5
+ # Runs on pushes targeting the default branch
6
+ push:
7
+ branches: [ "master" ]
8
+
9
+ # Allows you to run this workflow manually from the Actions tab
10
+ workflow_dispatch:
11
+
12
+ # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13
+ permissions:
14
+ contents: read
15
+ pages: write
16
+ id-token: write
17
+
18
+ # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19
+ # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20
+ concurrency:
21
+ group: "pages"
22
+ cancel-in-progress: false
23
+
24
+ jobs:
25
+ # Single deploy job since we're just deploying
26
+ deploy:
27
+ environment:
28
+ name: github-pages
29
+ url: ${{ steps.deployment.outputs.page_url }}
30
+ runs-on: ubuntu-latest
31
+ steps:
32
+ - name: Checkout
33
+ uses: actions/checkout@v4
34
+ - name: Setup Pages
35
+ uses: actions/configure-pages@v5
36
+ - name: Setup Ruby
37
+ uses: ruby/setup-ruby@v1
38
+ with:
39
+ ruby-version: '3.4'
40
+ - name: Generate Documentation
41
+ run: |
42
+ gem install gem_hadar
43
+ bundle install
44
+ rake doc
45
+ - name: Upload artifact
46
+ uses: actions/upload-pages-artifact@v3
47
+ with:
48
+ path: 'doc'
49
+ - name: Deploy to GitHub Pages
50
+ id: deployment
51
+ uses: actions/deploy-pages@v4
data/CHANGES.md CHANGED
@@ -1,3 +1,55 @@
1
+ # Changes
2
+
3
+ ## 2026-07-10 v0.26.0
4
+
5
+ ### New Features
6
+
7
+ - Added raw data decryption and CLI piping support via
8
+ `ComplexConfig::Provider#decrypt_data`.
9
+ - Enhanced the `complex_config` CLI:
10
+ - Added `-k` flag to specify an explicit key file path.
11
+ - Added `-I` flag to enable streaming decryption from stdin directly to
12
+ stdout.
13
+ - Introduced a GitHub Actions workflow (`static.yml`) for automated
14
+ documentation deployment to GitHub Pages using Ruby **3.4**.
15
+
16
+ ### Improvements & Changes
17
+
18
+ - Renamed `ComplexConfig::Settings#attributes_update_if_nil` to
19
+ `ComplexConfig::Settings#attributes_update_if_undefined` to better reflect
20
+ that it preserves existing keys, including explicit `nil` overrides. A
21
+ deprecation layer was added to the old method.
22
+ - Hardened the encryption CLI by replacing `Tempfile.open` with
23
+ `Tempfile.create` and adding a `.yml` suffix to temporary files for improved
24
+ editor syntax highlighting.
25
+ - Updated dependencies:
26
+ - `gem_hadar` updated from **~> 2.10** to **>= 2.17.1**.
27
+ - `s.rubygems_version` updated from **4.0.2** to **4.0.10**.
28
+ - Updated the Alpine build image to use stable Ruby **4.0** instead of
29
+ **4.0-rc**.
30
+ - Added changelog configuration to the `Rakefile` using `GemHadar`.
31
+
32
+ ### Documentation
33
+
34
+ - Expanded `README.md` with:
35
+ - A guide on securely comparing encrypted secrets using process
36
+ substitution and the `-I` flag.
37
+ - Examples for automation and piping using input redirection (`<`).
38
+ - Critical security warnings regarding plain-text secrets in version
39
+ control.
40
+ - Updated instructions for the `edit` command and `recrypt` backup
41
+ strategies.
42
+ - A link to the API documentation.
43
+ - Added an `@example` block to `ComplexConfig::Provider#decrypt_config`
44
+ documenting the automatic appending of the `.enc` extension.
45
+
46
+ ### Tests
47
+
48
+ - Added test cases in `spec/complex_config/provider_spec.rb` for
49
+ `ComplexConfig::Provider#decrypt_config`.
50
+ - Expanded `spec/complex_config/settings_spec.rb` to cover `attributes_update`,
51
+ `attributes_update_if_undefined`, and the associated deprecation warning.
52
+
1
53
  ## 2025-12-19 v0.25.0
2
54
 
3
55
  - Added support for Ruby **4.0**-rc-alpine image in test matrix
data/README.md CHANGED
@@ -6,6 +6,10 @@ 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
7
  using plugins to return more complex settings values. 🚀
8
8
 
9
+ ## Documentation
10
+
11
+ Complete API documentation is available at: [GitHub.io](https://flori.github.io/complex_config/)
12
+
9
13
  ## Architecture Overview 🏗️
10
14
 
11
15
  ComplexConfig follows a well-defined architectural pattern with clear
@@ -269,8 +273,8 @@ plain text.
269
273
 
270
274
  ```ruby
271
275
  # Write encrypted configuration
272
- ComplexConfig::Provider.write_config('database',
273
- value: { password: 'secret_password' },
276
+ ComplexConfig::Provider.write_config('database',
277
+ value: { password: 'secret_password' },
274
278
  encrypt: :random
275
279
  )
276
280
  # => Returns the random 128-bit master key as hexadecimal string.
@@ -334,22 +338,22 @@ easy access during development and deployment.
334
338
  - **Secure File Operations**: Encrypts/decrypts configuration files with
335
339
  automatic backup handling
336
340
  - **Edit Support**: Opens encrypted files in your preferred editor (`EDITOR`
337
- environment variable or `vi` by default)
341
+ environment variable or `vi` by default)
338
342
  - **Key Management**: Generates new encryption keys and supports key rotation
339
343
  - **Atomic Writes**: Uses secure file writing to prevent data corruption
340
344
 
341
345
  #### Usage Examples 📋
342
346
 
343
347
  ```bash
348
+ # 🛡️ Safely edit an encrypted configuration file
349
+ complex_config edit config/database.yml.enc
350
+
344
351
  # Encrypt a configuration file
345
352
  complex_config encrypt config/database.yml
346
353
 
347
- # Decrypt a configuration file
354
+ # Decrypt a configuration file
348
355
  complex_config decrypt config/database.yml.enc
349
356
 
350
- # Edit an encrypted configuration file
351
- complex_config edit config/database.yml.enc
352
-
353
357
  # Display decrypted content without writing to disk
354
358
  complex_config display config/database.yml.enc
355
359
 
@@ -360,17 +364,51 @@ complex_config new_key
360
364
  complex_config recrypt -o OLD_KEY -n NEW_KEY config/database.yml.enc
361
365
  ```
362
366
 
367
+ #### Automation & Piping 🚀
368
+
369
+ The `complex_config` tool can be used as a filter in shell pipelines by reading
370
+ encrypted data from standard input via the `-I` flag. This is ideal for CI/CD
371
+ pipelines or secret managers where keys are provided via environment variables
372
+ or explicit paths.
373
+
374
+ ```bash
375
+ # Decrypt a file using redirection
376
+ < config/database.yml.enc complex_config decrypt -I
377
+
378
+ # Decrypt using an explicit key file instead of environment variables
379
+ < config/database.yml.enc complex_config decrypt -I -k config/database.yml.key
380
+ ```
381
+
382
+ #### Advanced: Securely Comparing Encrypted Secrets 🧙‍♂️
383
+
384
+ For the ultimate security workflow, you can combine process substitution with
385
+ the piping feature to compare encrypted files without ever writing plain-text
386
+ secrets to your disk. This is particularly useful for diffing a current secret
387
+ against a previous version in Git history.
388
+
389
+ ```bash
390
+ # Diff secrets in the current branch against those stored in the master branch
391
+ diff --color -u <(git show master:config/secrets.yml.enc | complex_config decrypt -I) <(complex_config decrypt -O config/secrets.yml.enc)
392
+ ```
393
+
363
394
  #### Security Considerations ⚠️
364
395
 
365
396
  The script uses symmetric encryption for configuration files. When using
366
397
  encrypted configurations:
367
398
 
368
- 1. **Key Management**: Store your encryption keys securely (never in version
399
+
400
+ 1. **Avoid Manual Decryption**: Strictly avoid using `complex_config decrypt`
401
+ to create plain-text copies of secrets on your filesystem. Plain-text secret
402
+ files are frequently committed to version control by accident (e.g., via
403
+ `git add .`), leading to catastrophic security breaches. Always use
404
+ `complex_config edit`, which uses a tempfile and avoids the creation of a
405
+ unencrypted copy in the repository.
406
+ 2. **Key Management**: Store your encryption keys securely (never in version
369
407
  control)
370
- 2. **File Permissions**: Ensure encrypted `.enc` files have appropriate
408
+ 3. **File Permissions**: Ensure encrypted `.enc` files have appropriate
371
409
  permissions
372
- 3. **Backup Strategy**: The `recrypt` command automatically creates backup
373
- files
410
+ 4. **Backup Strategy**: The `recrypt` command automatically creates backup
411
+ files, you can delete them afterwards if everything worked.
374
412
 
375
413
  #### Integration with ComplexConfig 🔗
376
414
 
data/Rakefile CHANGED
@@ -21,6 +21,14 @@ GemHadar do
21
21
  title "#{name.camelize} -- configuration library"
22
22
  licenses << 'Apache-2.0'
23
23
 
24
+ changelog do
25
+ filename 'CHANGES.md'
26
+ end
27
+
28
+ github_workflows(
29
+ 'static.yml' => {}
30
+ )
31
+
24
32
  clobber 'coverage'
25
33
 
26
34
  dependency 'json'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.25.0
1
+ 0.26.0
data/bin/complex_config CHANGED
@@ -17,7 +17,7 @@ require 'tempfile'
17
17
  require 'fileutils'
18
18
  include FileUtils
19
19
 
20
- $opts = go 'o:n:IOh'
20
+ $opts = go 'o:n:k:IOh'
21
21
 
22
22
  # The usage method displays help information and exits the program
23
23
  #
@@ -44,7 +44,9 @@ def usage(msg: 'Displaying help', rc: 0)
44
44
  Options are
45
45
 
46
46
  -c CONFIG_DIR set CONFIG_DIR (default: "./config")
47
- -I read input from stdin instead of file (encrypt)
47
+ -k KEY_PATH the path to the keyfile used if any (decrypt -I)
48
+ -I read input from stdin instead of file (encrypt/decrypt)
49
+ for decrypt this option implies -O
48
50
  -O write output to stdout instead of file (decrypt)
49
51
  -h this help
50
52
 
@@ -87,7 +89,7 @@ when 'edit'
87
89
  fn = fetch_filename
88
90
  catch :did_not_change do
89
91
  File.secure_write(fn + '.enc') do |f|
90
- Tempfile.open('complex_config') do |t|
92
+ Tempfile.create(%w[ complex_config .yml ]) do |t|
91
93
  config = ComplexConfig::Provider.decrypt_config(fn)
92
94
  t.write config
93
95
  t.flush
@@ -104,10 +106,13 @@ when 'edit'
104
106
  end
105
107
  end
106
108
  when 'decrypt'
107
- fn = fetch_filename
108
- if $opts[?O]
109
+ if $opts[?I]
110
+ $stdout.puts ComplexConfig::Provider.decrypt_data($stdin.read, $opts[?k])
111
+ elsif $opts[?O]
112
+ fn = fetch_filename
109
113
  $stdout.puts ComplexConfig::Provider.decrypt_config(fn)
110
114
  else
115
+ fn = fetch_filename
111
116
  File.exist?(fn) and usage msg: "decrypted config #{fn.inspect} already exists", rc: 1
112
117
  File.secure_write(fn) do |f|
113
118
  f.write ComplexConfig::Provider.decrypt_config(fn)
@@ -1,9 +1,9 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: complex_config 0.25.0 ruby lib
2
+ # stub: complex_config 0.26.0 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "complex_config".freeze
6
- s.version = "0.25.0".freeze
6
+ s.version = "0.26.0".freeze
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
@@ -13,17 +13,17 @@ Gem::Specification.new do |s|
13
13
  s.email = "flori@ping.de".freeze
14
14
  s.executables = ["complex_config".freeze]
15
15
  s.extra_rdoc_files = ["README.md".freeze, "lib/complex_config.rb".freeze, "lib/complex_config/config.rb".freeze, "lib/complex_config/encryption.rb".freeze, "lib/complex_config/errors.rb".freeze, "lib/complex_config/key_source.rb".freeze, "lib/complex_config/plugins.rb".freeze, "lib/complex_config/plugins/enable.rb".freeze, "lib/complex_config/plugins/money.rb".freeze, "lib/complex_config/plugins/uri.rb".freeze, "lib/complex_config/provider.rb".freeze, "lib/complex_config/provider/shortcuts.rb".freeze, "lib/complex_config/proxy.rb".freeze, "lib/complex_config/railtie.rb".freeze, "lib/complex_config/rude.rb".freeze, "lib/complex_config/settings.rb".freeze, "lib/complex_config/shortcuts.rb".freeze, "lib/complex_config/tree.rb".freeze, "lib/complex_config/version.rb".freeze]
16
- s.files = ["CHANGES.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "bin/complex_config".freeze, "complex_config.gemspec".freeze, "config/products.yml".freeze, "lib/complex_config.rb".freeze, "lib/complex_config/config.rb".freeze, "lib/complex_config/encryption.rb".freeze, "lib/complex_config/errors.rb".freeze, "lib/complex_config/key_source.rb".freeze, "lib/complex_config/plugins.rb".freeze, "lib/complex_config/plugins/enable.rb".freeze, "lib/complex_config/plugins/money.rb".freeze, "lib/complex_config/plugins/uri.rb".freeze, "lib/complex_config/provider.rb".freeze, "lib/complex_config/provider/shortcuts.rb".freeze, "lib/complex_config/proxy.rb".freeze, "lib/complex_config/railtie.rb".freeze, "lib/complex_config/rude.rb".freeze, "lib/complex_config/settings.rb".freeze, "lib/complex_config/shortcuts.rb".freeze, "lib/complex_config/tree.rb".freeze, "lib/complex_config/version.rb".freeze, "spec/complex_config/config_spec.rb".freeze, "spec/complex_config/encryption_spec.rb".freeze, "spec/complex_config/key_source_spec.rb".freeze, "spec/complex_config/plugins_spec.rb".freeze, "spec/complex_config/provider_spec.rb".freeze, "spec/complex_config/settings_spec.rb".freeze, "spec/complex_config/shortcuts_spec.rb".freeze, "spec/config/broken_config.yml".freeze, "spec/config/config.yml".freeze, "spec/config/config_with_alias.yml".freeze, "spec/config/empty_config.yml".freeze, "spec/config/legacy_classes.yml".freeze, "spec/config/master.key".freeze, "spec/config/with-key-file.yml.enc".freeze, "spec/config/with-key-file.yml.key".freeze, "spec/config/without-key-file.yml.enc".freeze, "spec/spec_helper.rb".freeze]
16
+ s.files = [".github/workflows/static.yml".freeze, "CHANGES.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "bin/complex_config".freeze, "complex_config.gemspec".freeze, "config/products.yml".freeze, "lib/complex_config.rb".freeze, "lib/complex_config/config.rb".freeze, "lib/complex_config/encryption.rb".freeze, "lib/complex_config/errors.rb".freeze, "lib/complex_config/key_source.rb".freeze, "lib/complex_config/plugins.rb".freeze, "lib/complex_config/plugins/enable.rb".freeze, "lib/complex_config/plugins/money.rb".freeze, "lib/complex_config/plugins/uri.rb".freeze, "lib/complex_config/provider.rb".freeze, "lib/complex_config/provider/shortcuts.rb".freeze, "lib/complex_config/proxy.rb".freeze, "lib/complex_config/railtie.rb".freeze, "lib/complex_config/rude.rb".freeze, "lib/complex_config/settings.rb".freeze, "lib/complex_config/shortcuts.rb".freeze, "lib/complex_config/tree.rb".freeze, "lib/complex_config/version.rb".freeze, "spec/complex_config/config_spec.rb".freeze, "spec/complex_config/encryption_spec.rb".freeze, "spec/complex_config/key_source_spec.rb".freeze, "spec/complex_config/plugins_spec.rb".freeze, "spec/complex_config/provider_spec.rb".freeze, "spec/complex_config/settings_spec.rb".freeze, "spec/complex_config/shortcuts_spec.rb".freeze, "spec/config/broken_config.yml".freeze, "spec/config/config.yml".freeze, "spec/config/config_with_alias.yml".freeze, "spec/config/empty_config.yml".freeze, "spec/config/legacy_classes.yml".freeze, "spec/config/master.key".freeze, "spec/config/with-key-file.yml.enc".freeze, "spec/config/with-key-file.yml.key".freeze, "spec/config/without-key-file.yml.enc".freeze, "spec/spec_helper.rb".freeze]
17
17
  s.homepage = "https://github.com/flori/complex_config".freeze
18
18
  s.licenses = ["Apache-2.0".freeze]
19
19
  s.rdoc_options = ["--title".freeze, "ComplexConfig -- configuration library".freeze, "--main".freeze, "README.md".freeze]
20
- s.rubygems_version = "4.0.2".freeze
20
+ s.rubygems_version = "4.0.10".freeze
21
21
  s.summary = "configuration library".freeze
22
22
  s.test_files = ["spec/complex_config/config_spec.rb".freeze, "spec/complex_config/encryption_spec.rb".freeze, "spec/complex_config/key_source_spec.rb".freeze, "spec/complex_config/plugins_spec.rb".freeze, "spec/complex_config/provider_spec.rb".freeze, "spec/complex_config/settings_spec.rb".freeze, "spec/complex_config/shortcuts_spec.rb".freeze, "spec/spec_helper.rb".freeze]
23
23
 
24
24
  s.specification_version = 4
25
25
 
26
- s.add_development_dependency(%q<gem_hadar>.freeze, ["~> 2.10".freeze])
26
+ s.add_development_dependency(%q<gem_hadar>.freeze, [">= 2.17.1".freeze])
27
27
  s.add_development_dependency(%q<rake>.freeze, [">= 0".freeze])
28
28
  s.add_development_dependency(%q<simplecov>.freeze, [">= 0".freeze])
29
29
  s.add_development_dependency(%q<rspec>.freeze, [">= 0".freeze])
@@ -187,6 +187,12 @@ class ComplexConfig::Provider
187
187
  # The decrypt_config method retrieves decrypted configuration data from an
188
188
  # encrypted file
189
189
  #
190
+ # @example Decrypting a configuration file
191
+ # provider = ComplexConfig::Provider
192
+ # # Note: The method automatically appends '.enc' to the provided path
193
+ # decrypted_yaml = provider.decrypt_config('config/database.yml')
194
+ # puts decrypted_yaml if decrypted_yaml
195
+ #
190
196
  # @param pathname [String, Pathname] the path to the encrypted configuration
191
197
  # file
192
198
  #
@@ -199,6 +205,23 @@ class ComplexConfig::Provider
199
205
  decrypt_config_case(pathname).first
200
206
  end
201
207
 
208
+ # The decrypt_data method decrypts raw ciphertext using the provider's key
209
+ # source
210
+ #
211
+ # This method bypasses filesystem lookups for the encrypted data itself,
212
+ # allowing decryption of raw bytes. It uses the provided pathname to resolve
213
+ # the encryption key; if no pathname is provided, it relies on environment
214
+ # variables or the master key.
215
+ #
216
+ # @param ciphertext [String] the encrypted data to decrypt
217
+ # @param pathname [String, nil] an optional path used to locate a key file (e.g., .key)
218
+ # @return [String, nil] the decrypted plain text, or nil if no valid key was found
219
+ def decrypt_data(ciphertext, pathname = nil)
220
+ ks = key_source(pathname)
221
+ return nil unless ks && ks.key
222
+ ComplexConfig::Encryption.new(ks.key_bytes).decrypt(ciphertext)
223
+ end
224
+
202
225
  # The encrypt_config method encrypts configuration data using a key source
203
226
  #
204
227
  # @param pathname [String, Pathname] the path to the configuration file to be
@@ -264,7 +287,7 @@ class ComplexConfig::Provider
264
287
  shared = shared.to_h
265
288
  settings.each do |key, value|
266
289
  if value.is_a? ComplexConfig::Settings
267
- value.attributes_update_if_nil(shared)
290
+ value.attributes_update_if_undefined(shared)
268
291
  elsif value.nil?
269
292
  settings[key] = ComplexConfig::Settings.build(nil, shared.dup)
270
293
  end
@@ -20,7 +20,6 @@ class ComplexConfig::Settings < BasicObject
20
20
  include ::Tins::AskAndSend
21
21
 
22
22
  class << self
23
-
24
23
  # The [] method converts a hash-like object into a Settings object
25
24
  #
26
25
  # This method serves as a convenience accessor that delegates to the
@@ -202,8 +201,6 @@ class ComplexConfig::Settings < BasicObject
202
201
  #
203
202
  # @param other [Object] the source containing attributes to merge, which can
204
203
  # be any object that responds to to_hash or to_ary
205
- #
206
- # @return [void] Returns nothing
207
204
  def attributes_update(other)
208
205
  unless other.is_a? self.class
209
206
  other = self.class.from_hash(other)
@@ -211,19 +208,19 @@ class ComplexConfig::Settings < BasicObject
211
208
  @table.update(other.table)
212
209
  end
213
210
 
214
- # The attributes_update_if_nil method merges configuration attributes from
215
- # another source, updating only nil values
211
+ # The attributes_update_if_undefined method merges configuration attributes from
212
+ # another source, updating only those that are completely missing (undefined)
213
+ # in this settings object.
216
214
  #
217
- # This method updates the current object's internal table with attributes
218
- # from another source, but only assigns new values when the existing keys
219
- # have nil values. It preserves existing non-nil attribute values while
220
- # allowing nil values to be overridden.
215
+ # This is specifically used for shared settings patterns where environment-specific
216
+ # configurations must be respected. Because it only updates missing keys, any
217
+ # attribute explicitly set—including an explicit `nil` value intended to disable
218
+ # a feature or clear a default—will be preserved and not overwritten by the
219
+ # provided shared configuration source.
221
220
  #
222
221
  # @param other [Object] the source containing attributes to merge, which can
223
222
  # be any object that responds to to_hash or to_ary
224
- #
225
- # @return [void] Returns nothing
226
- def attributes_update_if_nil(other)
223
+ def attributes_update_if_undefined(other)
227
224
  unless other.is_a? self.class
228
225
  other = self.class.from_hash(other)
229
226
  end
@@ -232,6 +229,22 @@ class ComplexConfig::Settings < BasicObject
232
229
  end
233
230
  end
234
231
 
232
+ # The attributes_update_if_nil does the same attributes_update_if_undefined
233
+ # does.
234
+ #
235
+ # @deprecated Use #attributes_update_if_undefined instead. This method is kept
236
+ # for backward compatibility but now delegates to #attributes_update_if_undefined
237
+ # to ensure explicit `nil` overrides are respected.
238
+ #
239
+ # @param other [Object] the source containing attributes to merge, which can
240
+ # be any object that responds to to_hash or to_ary
241
+ def attributes_update_if_nil(other)
242
+ warn '[DEPRECATION] `%{method}` is deprecated. Please use `%{new_method}` instead.' % {
243
+ method: __method__, new_method: :attributes_update_if_undefined
244
+ }
245
+ attributes_update_if_undefined(other)
246
+ end
247
+
235
248
  # The replace_attributes method replaces all attributes with those from the
236
249
  # provided hash
237
250
  #
@@ -447,7 +460,6 @@ class ComplexConfig::Settings < BasicObject
447
460
  # string representation for display purposes
448
461
  #
449
462
  # @param q [PrettyPrint] the pretty printer object to use for formatting
450
- # @return [void] Returns nothing
451
463
  def pretty_print(q)
452
464
  q.text inspect
453
465
  end
@@ -599,9 +611,6 @@ class ComplexConfig::Settings < BasicObject
599
611
  end
600
612
 
601
613
  # The skip method raises a skip exception to bypass plugin execution
602
- #
603
- # @return [void] This method always raises a :skip exception and never
604
- # returns normally
605
614
  def skip
606
615
  throw :skip
607
616
  end
@@ -1,6 +1,6 @@
1
1
  module ComplexConfig
2
2
  # ComplexConfig version
3
- VERSION = '0.25.0'
3
+ VERSION = '0.26.0'
4
4
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
@@ -165,6 +165,18 @@ describe ComplexConfig::Provider do
165
165
  ENV['RAILS_MASTER_KEY'] = nil
166
166
  end
167
167
 
168
+ it 'can decrypt from pathname returning content' do
169
+ described_class.key = key
170
+ expect(described_class.key).to eq key.chomp
171
+ decrypted = described_class.decrypt_config(asset('without-key-file.yml'))
172
+ expect(decrypted).to eq <<~EOT
173
+ ---
174
+ :development:
175
+ :foo:
176
+ :bar: baz
177
+ EOT
178
+ end
179
+
168
180
  it 'can read when key is stored in file' do
169
181
  expect(described_class['with-key-file'].development.foo.bar).to eq 'baz'
170
182
  end
@@ -209,4 +209,53 @@ EOT
209
209
  expect(settings.foo.replaced).to eq true
210
210
  expect(settings.foo).to eq result
211
211
  end
212
+
213
+ it 'can update attributes from a hash' do
214
+ settings.foo.attributes_update(updated: 'yes', baz: 'new')
215
+ expect(settings.foo.updated).to eq 'yes'
216
+ expect(settings.foo.baz).to eq 'new'
217
+ end
218
+
219
+ it 'can update attributes from another Settings object' do
220
+ other = described_class[val: 'new']
221
+ settings.foo.attributes_update(other)
222
+ expect(settings.foo.val).to eq 'new'
223
+ end
224
+
225
+ it 'can update attributes only if they are missing (attributes_update_if_undefined)' do
226
+ # Current settings.foo has { bar: { baz: true }, nix: nil, qux: 'quux' }
227
+ updates = {
228
+ nix: 'filled',
229
+ qux: 'should_stay_quux',
230
+ new_key: 'added'
231
+ }
232
+
233
+ settings.foo.attributes_update_if_undefined(updates)
234
+
235
+ expect(settings.foo.nix).to be_nil
236
+ expect(settings.foo.qux).to eq 'quux' # Key exists and is not nil, should NOT be updated
237
+ expect(settings.foo.new_key).to eq 'added' # Key missing, should be added
238
+ end
239
+
240
+ it 'does not overwrite existing values with attributes_update_if_undefined' do
241
+ s = described_class.new(existing: 'keep')
242
+ updates = { existing: 'change', other: 'add' }
243
+
244
+ s.attributes_update_if_undefined(updates)
245
+
246
+ expect(s.existing).to eq 'keep'
247
+ expect(s.other).to eq 'add'
248
+ end
249
+
250
+ it 'calls attributes_update_if_undefined and warns when using deprecated attributes_update_if_nil' do
251
+ s = described_class.new(existing: 'keep')
252
+ updates = { existing: 'change', other: 'add' }
253
+
254
+ expect {
255
+ s.attributes_update_if_nil(updates)
256
+ }.to output(/DEPRECATION.*attributes_update_if_undefined/).to_stderr
257
+
258
+ expect(s.existing).to eq 'keep'
259
+ expect(s.other).to eq 'add'
260
+ end
212
261
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: complex_config
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.25.0
4
+ version: 0.26.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
@@ -13,16 +13,16 @@ dependencies:
13
13
  name: gem_hadar
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - "~>"
16
+ - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '2.10'
18
+ version: 2.17.1
19
19
  type: :development
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
- - - "~>"
23
+ - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: '2.10'
25
+ version: 2.17.1
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rake
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -203,6 +203,7 @@ extra_rdoc_files:
203
203
  - lib/complex_config/tree.rb
204
204
  - lib/complex_config/version.rb
205
205
  files:
206
+ - ".github/workflows/static.yml"
206
207
  - CHANGES.md
207
208
  - Gemfile
208
209
  - LICENSE
@@ -269,7 +270,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
269
270
  - !ruby/object:Gem::Version
270
271
  version: '0'
271
272
  requirements: []
272
- rubygems_version: 4.0.2
273
+ rubygems_version: 4.0.10
273
274
  specification_version: 4
274
275
  summary: configuration library
275
276
  test_files: