hashie 5.2.0 → 6.0.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/CHANGELOG.md +19 -0
- data/CONTRIBUTING.md +2 -0
- data/README.md +60 -10
- data/Rakefile +15 -0
- data/UPGRADING.md +100 -6
- data/lib/hashie/clash.rb +3 -2
- data/lib/hashie/dash.rb +25 -25
- data/lib/hashie/extensions/block_access.rb +5 -5
- data/lib/hashie/extensions/dash/coercion.rb +4 -4
- data/lib/hashie/extensions/dash/indifferent_access.rb +5 -5
- data/lib/hashie/extensions/dash/lazy.rb +10 -10
- data/lib/hashie/extensions/dash/predefined_values.rb +12 -16
- data/lib/hashie/extensions/dash/property_translation.rb +22 -14
- data/lib/hashie/extensions/deep_find.rb +4 -4
- data/lib/hashie/extensions/deep_grep.rb +15 -15
- data/lib/hashie/extensions/deep_locate.rb +3 -3
- data/lib/hashie/extensions/deep_merge.rb +16 -14
- data/lib/hashie/extensions/deep_merge_initializer.rb +3 -3
- data/lib/hashie/extensions/indifferent_access.rb +6 -6
- data/lib/hashie/extensions/mash/define_accessors.rb +3 -1
- data/lib/hashie/extensions/mash/keep_original_keys.rb +2 -2
- data/lib/hashie/extensions/mash/permissive_respond_to.rb +1 -1
- data/lib/hashie/extensions/mash/required_keys.rb +2 -2
- data/lib/hashie/extensions/mash/strict_key_access.rb +2 -2
- data/lib/hashie/extensions/method_access.rb +15 -15
- data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +1 -1
- data/lib/hashie/extensions/parsers/yaml_parser.rb +2 -2
- data/lib/hashie/extensions/persistable.rb +4 -4
- data/lib/hashie/extensions/ruby_version.rb +2 -2
- data/lib/hashie/extensions/stringify_keys.rb +4 -3
- data/lib/hashie/extensions/symbolize_keys.rb +4 -3
- data/lib/hashie/hash.rb +5 -5
- data/lib/hashie/mash.rb +20 -16
- data/lib/hashie/rash.rb +2 -2
- data/lib/hashie/utils.rb +3 -2
- data/lib/hashie/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0fa02edecdffba1e181cfe0e80f6aa55db45f1b6b1cd0fc866b3bbc4ef6abd16
|
|
4
|
+
data.tar.gz: 02fefdce2dce50542155afa916e664f2f06b0d31bdc322fcde3ef70b33260c00
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1b09dff1ca0ecfb032087c56bd3e5472de999f81cd35cdbd8fdcd934aba6a518073ea58d29ccd32d1d4df52a790fada8769ee1ceaa30ee79e26bb49fa4256e7e
|
|
7
|
+
data.tar.gz: 21ea16dfaa5755d4f193d0b6159848aa299bbccdaf42bd068630aa1d565ea2f6fb9c8c7f26b49e6aeadc090f1faf05be1988da8e90f55156129be21b616e689f
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
Any violations of this scheme are considered to be bugs.
|
|
8
8
|
|
|
9
|
+
## [6.0.0] - 2026-11-11
|
|
10
|
+
|
|
11
|
+
[Unreleased]: https://github.com/hashie/hashie/compare/v6.0.0...master
|
|
12
|
+
[6.0.0]: https://github.com/hashie/hashie/compare/v5.2.0...v6.0.0
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
* [#648](https://github.com/hashie/hashie/pull/648): Added manually dispatched releases with `ruby-gem-release` and RubyGems Trusted Publishing - [@dblock](https://github.com/dblock).
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
* [#649](https://github.com/hashie/hashie/pull/649): Updated Danger workflows to `danger/danger-pr-comment` v0.2.0 following its move to the Danger organization - [@dblock](https://github.com/dblock).
|
|
21
|
+
* [#641](https://github.com/hashie/hashie/pull/641): **Breaking:** Changed `DeepFind#deep_find_all` and `deep_select` to return an empty array instead of `nil` when no matches exist, reviving [#548](https://github.com/hashie/hashie/pull/548) - [@punkisdead](https://github.com/punkisdead), [@dblock](https://github.com/dblock).
|
|
22
|
+
* [#639](https://github.com/hashie/hashie/pull/639): **Breaking:** Standardized internal helpers on one leading underscore and prevented Dash/Trash properties and generated Mash accessors from replacing existing methods. Conflicting Dash/Trash properties now require bracket access - [@dblock](https://github.com/dblock).
|
|
23
|
+
* [#637](https://github.com/hashie/hashie/pull/637): **Breaking:** Changed Mash to preserve nested custom Hash subclasses by identity instead of converting or duplicating them; custom values replace existing nested Mashes during merge. Plain Hash conversion and Mash copying remain unchanged, following [#390](https://github.com/hashie/hashie/issues/390), reported by [@schnittchen](https://github.com/schnittchen) - [@dblock](https://github.com/dblock).
|
|
24
|
+
* [#633](https://github.com/hashie/hashie/pull/633): **Breaking:** Changed `DeepMerge` to recursively copy hashes, arrays, and strings while retaining other objects by identity on both sides, following [#572](https://github.com/hashie/hashie/issues/572) - [@apotonick](https://github.com/apotonick), [@michaelherold](https://github.com/michaelherold), [@dblock](https://github.com/dblock).
|
|
25
|
+
* [#632](https://github.com/hashie/hashie/pull/632): **Breaking:** Changed `DeepMerge` to preserve incoming nested hash subclasses, defaults, and instance state directly with `dup.clear`, following [#619](https://github.com/hashie/hashie/pull/619), [#586](https://github.com/hashie/hashie/pull/586), and [#585](https://github.com/hashie/hashie/issues/585) - [@addam128](https://github.com/addam128), [@dblock](https://github.com/dblock).
|
|
26
|
+
* [#631](https://github.com/hashie/hashie/pull/631): **Breaking:** Changed `Mash.load` to use `YamlParser` by default; ERB evaluation now requires explicit `YamlErbParser` selection, following [#575](https://github.com/hashie/hashie/issues/575) and [#625](https://github.com/hashie/hashie/pull/625) - [@bo-tato](https://github.com/bo-tato), [@dblock](https://github.com/dblock).
|
|
27
|
+
|
|
9
28
|
## [5.2.0] - 2026-10-23
|
|
10
29
|
|
|
11
30
|
[5.2.0]: https://github.com/hashie/hashie/compare/v5.1.0...v5.2.0
|
data/CONTRIBUTING.md
CHANGED
|
@@ -57,6 +57,8 @@ RuboCop scans Ruby sources, specs, benchmarks, gem manifests, and Ruby scripts a
|
|
|
57
57
|
|
|
58
58
|
On MRI Ruby 3.2 or newer, RuboCop also checks spelling in Ruby comments and strings and documentation with `rubocop-spellcheck`. The plugin's native dependencies are not installed on JRuby. Fix typos in the source and add intentional project terms to `.spellcheck.txt`, one word per line. Run only the spelling check with `bundle exec rubocop --only Spellcheck/Spelling`. If regenerating vocabulary with `--auto-gen-config`, review the word list so genuine typos are not accepted and preserve the existing `.rubocop_todo.yml` baseline.
|
|
59
59
|
|
|
60
|
+
Name private and protected implementation helpers with one leading underscore. Keep Ruby callbacks and documented extension hooks under their required names. Double-underscore aliases such as `__size` in `MethodAccessWithOverride` are a public feature for accessing overridden methods, not an internal-helper convention. Generated accessors must not replace existing methods, including private and protected helpers.
|
|
61
|
+
|
|
60
62
|
Make sure that `bundle exec rake` completes without errors.
|
|
61
63
|
|
|
62
64
|
#### Write Documentation
|
data/README.md
CHANGED
|
@@ -82,7 +82,7 @@ gem install hashie
|
|
|
82
82
|
|
|
83
83
|
## Stable Release
|
|
84
84
|
|
|
85
|
-
You're reading the documentation for the stable release of Hashie,
|
|
85
|
+
You're reading the documentation for the stable release of Hashie, 6.0.0.
|
|
86
86
|
|
|
87
87
|
## Hash Extensions
|
|
88
88
|
|
|
@@ -535,7 +535,7 @@ Configuration.load('config.yaml').name # => 'updated'
|
|
|
535
535
|
|
|
536
536
|
Files contain plain YAML data, not Ruby class tags or persistence metadata. Loading uses `Psych.safe_load`, permits symbols for declared Dash/Trash properties, rejects aliases and non-hash roots, and never evaluates ERB. String and symbol keys retain their types; Dash/Trash files must use keys accepted by their constructors. Nested Hashie classes are not encoded: constructors such as Mash may reconstruct their own nested types, while Dash retains nested plain hashes. Supported values are hashes, arrays, strings, symbols, numbers, booleans, and `nil`; cycles and unsupported objects are rejected.
|
|
537
537
|
|
|
538
|
-
Including this extension explicitly replaces an inherited class-level `load` on that subclass. Ordinary `Hashie::Mash.load`
|
|
538
|
+
Including this extension explicitly replaces an inherited class-level `load` on that subclass. Ordinary `Hashie::Mash.load` uses cached YAML loading, with ERB evaluation available only through an explicit parser selection. No autosave or other serialization formats are provided.
|
|
539
539
|
|
|
540
540
|
### MethodAccessWithOverride
|
|
541
541
|
|
|
@@ -676,6 +676,12 @@ h2 = MyHash[{ b: 250, c: { c1: 200 } }]
|
|
|
676
676
|
h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val } # => { a: 100, b: 450, c: { c1: 300 } }
|
|
677
677
|
```
|
|
678
678
|
|
|
679
|
+
New nested hashes preserve incoming hash subclasses using `source.dup.clear`. This retains their instance state and hash defaults without requiring a zero-argument constructor. Existing destination hashes retain their own types. Both `deep_merge` and `deep_merge!` apply this policy at every incoming nested-hash level, including hashes inside replacement arrays; neither clears the source. Custom subclasses must support duplication, clearing, and assignment into the copied hash.
|
|
680
|
+
|
|
681
|
+
`deep_merge` recursively copies hash and array values and duplicates strings on both sides. Other values retain their identity: it never calls `dup` or `deep_dup` on arbitrary leaf objects, such as ActiveRecord models. Arrays replace rather than merge existing values, but their elements are copied recursively. Hash keys retain the usual Ruby Hash semantics. Inputs must be acyclic; shared references are not preserved between independently copied containers.
|
|
682
|
+
|
|
683
|
+
`deep_merge!` preserves existing receiver values unless replaced or recursively merged, and applies the same copying policy to incoming values. Conflict blocks receive the original incoming value and, for `deep_merge`, the copied receiver value; their return values are stored unchanged. Mutable leaf objects other than strings remain shared, so this is not a fully independent object-graph clone.
|
|
684
|
+
|
|
679
685
|
### DeepFetch
|
|
680
686
|
|
|
681
687
|
This extension can be mixed in to provide for safe and concise retrieval of deeply nested hash values. In the event that the requested key does not exist a block can be provided and its value will be returned.
|
|
@@ -731,6 +737,10 @@ user.deep_detect(:name) #=> { first: 'Bob', last: 'Boberts' }
|
|
|
731
737
|
|
|
732
738
|
user.deep_find_all(:name) #=> [{ first: 'Bob', last: 'Boberts' }, 'Rubyists', 'Open source enthusiasts']
|
|
733
739
|
user.deep_select(:name) #=> [{ first: 'Bob', last: 'Boberts' }, 'Rubyists', 'Open source enthusiasts']
|
|
740
|
+
|
|
741
|
+
user.deep_find(:missing) #=> nil
|
|
742
|
+
user.deep_find_all(:missing) #=> []
|
|
743
|
+
user.deep_select(:missing) #=> []
|
|
734
744
|
```
|
|
735
745
|
|
|
736
746
|
### DeepGrep
|
|
@@ -820,7 +830,22 @@ end
|
|
|
820
830
|
|
|
821
831
|
## Mash
|
|
822
832
|
|
|
823
|
-
Mash adds method-style access to Hash keys and recursively wraps nested hashes
|
|
833
|
+
Mash adds method-style access to Hash keys and recursively wraps nested plain hashes.
|
|
834
|
+
|
|
835
|
+
Custom Hash subclasses other than Mash are stored unchanged, including when nested in arrays or ordinary hashes. Their methods, defaults, key types, and identity are preserved, and their contents are not recursively converted. Mash values retain their existing Mash-specific copying behavior. To explicitly normalize a custom Hash into nested Mashes, pass it as the root argument to `Hashie::Mash.new(custom_hash)`.
|
|
836
|
+
|
|
837
|
+
```ruby
|
|
838
|
+
class CustomHash < Hash
|
|
839
|
+
end
|
|
840
|
+
|
|
841
|
+
custom = CustomHash[name: 'Ada']
|
|
842
|
+
mash = Hashie::Mash.new(custom: custom, plain: { name: 'Grace' })
|
|
843
|
+
mash.custom.equal?(custom) # => true
|
|
844
|
+
mash.custom[:name] # => 'Ada'
|
|
845
|
+
mash.plain.name # => 'Grace'
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
Merging a custom Hash value replaces an existing nested Mash rather than recursively merging it. Custom Hash values remain shared when copying a Mash with `dup` or `merge`; mutations to those values are visible through every reference.
|
|
824
849
|
|
|
825
850
|
```ruby
|
|
826
851
|
mash = Hashie::Mash.new
|
|
@@ -971,7 +996,7 @@ Use `Mash.load` to read a YAML configuration file:
|
|
|
971
996
|
development:
|
|
972
997
|
api_key: 'api_key'
|
|
973
998
|
production:
|
|
974
|
-
api_key:
|
|
999
|
+
api_key: 'abcd'
|
|
975
1000
|
```
|
|
976
1001
|
|
|
977
1002
|
<!-- hashie:example mash-load -->
|
|
@@ -984,9 +1009,8 @@ File.write('settings/twitter.yml', <<~YAML)
|
|
|
984
1009
|
development:
|
|
985
1010
|
api_key: 'api_key'
|
|
986
1011
|
production:
|
|
987
|
-
api_key:
|
|
1012
|
+
api_key: 'abcd'
|
|
988
1013
|
YAML
|
|
989
|
-
ENV['HASHIE_README_API_KEY'] = 'abcd'
|
|
990
1014
|
|
|
991
1015
|
mash = Hashie::Mash.load('settings/twitter.yml')
|
|
992
1016
|
mash.development.api_key # => 'api_key'
|
|
@@ -1010,14 +1034,24 @@ mash.development.api_key # => 'api_key'
|
|
|
1010
1034
|
|
|
1011
1035
|
The loaded root is frozen, but nested Mashes remain mutable. Repeated loads with the same path object return the cached Mash.
|
|
1012
1036
|
|
|
1013
|
-
By default, `Mash.load`
|
|
1037
|
+
By default, `Mash.load` uses [YamlParser](lib/hashie/extensions/parsers/yaml_parser.rb), which parses YAML without evaluating ERB. Embedded ERB remains literal text when valid YAML, or produces a YAML parsing error when it is not valid YAML:
|
|
1014
1038
|
|
|
1015
1039
|
```ruby
|
|
1016
1040
|
File.write('data.yml', "api_key: <%= not_evaluated %>\n")
|
|
1017
|
-
mash = Hashie::Mash.load('data.yml'
|
|
1041
|
+
mash = Hashie::Mash.load('data.yml')
|
|
1018
1042
|
mash.api_key # => '<%= not_evaluated %>'
|
|
1019
1043
|
```
|
|
1020
1044
|
|
|
1045
|
+
If trusted configuration files need ERB interpolation, explicitly select `YamlErbParser`:
|
|
1046
|
+
|
|
1047
|
+
```ruby
|
|
1048
|
+
File.write('trusted-settings.yml', "api_key: <%= 'abcd' %>\n")
|
|
1049
|
+
mash = Hashie::Mash.load('trusted-settings.yml', parser: Hashie::Extensions::Parsers::YamlErbParser)
|
|
1050
|
+
mash.api_key # => 'abcd'
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
`YamlErbParser` executes Ruby code; `YAML.safe_load` does not make ERB evaluation safe. Only use it with trusted files. Loaded results remain cached by path, not parser or options; use separate Mash subclasses when the same path needs different loading policies.
|
|
1054
|
+
|
|
1021
1055
|
You can access a Mash from another class:
|
|
1022
1056
|
|
|
1023
1057
|
<!-- hashie:example mash-load -->
|
|
@@ -1028,10 +1062,9 @@ end
|
|
|
1028
1062
|
mash = Hashie::Mash.load('settings/twitter.yml')['production']
|
|
1029
1063
|
Twitter.extend mash.to_module # NOTE: if you want another name than settings, call: to_module('my_settings')
|
|
1030
1064
|
Twitter.settings.api_key # => 'abcd'
|
|
1031
|
-
ENV.delete('HASHIE_README_API_KEY')
|
|
1032
1065
|
```
|
|
1033
1066
|
|
|
1034
|
-
You can use another parser
|
|
1067
|
+
You can use another parser instead of the default `YamlParser`:
|
|
1035
1068
|
|
|
1036
1069
|
```text
|
|
1037
1070
|
# data/user.csv
|
|
@@ -1402,6 +1435,23 @@ dash.update_attributes!(description: nil)
|
|
|
1402
1435
|
dash.description #=> 'none'
|
|
1403
1436
|
```
|
|
1404
1437
|
|
|
1438
|
+
Dash and Trash do not replace existing public, protected, or private methods with property accessors. Conflicting names remain available through brackets, including required-property validation and translations:
|
|
1439
|
+
|
|
1440
|
+
```ruby
|
|
1441
|
+
class TypedParameter < Hashie::Trash
|
|
1442
|
+
property :class, from: :type, required: true
|
|
1443
|
+
property :count, default: 0
|
|
1444
|
+
end
|
|
1445
|
+
|
|
1446
|
+
parameter = TypedParameter.new(type: 'query')
|
|
1447
|
+
parameter[:class] # => 'query'
|
|
1448
|
+
parameter.class # => TypedParameter
|
|
1449
|
+
parameter[:count] # => 0
|
|
1450
|
+
parameter.count # => 2 (Hash entry count, not the property value)
|
|
1451
|
+
```
|
|
1452
|
+
|
|
1453
|
+
This also preserves custom readers and writers defined before the property declaration. A translated input key that conflicts with an existing writer uses bracket assignment instead, such as `parameter[:default] = value`; the existing writer retains its original behavior.
|
|
1454
|
+
|
|
1405
1455
|
The names `required` and `required?` can themselves be used as properties, for example when modeling a Swagger parameter. Their values are independent of Dash's `required: true` declaration option; `false` is a valid non-nil value even for a required property.
|
|
1406
1456
|
|
|
1407
1457
|
```ruby
|
data/Rakefile
CHANGED
|
@@ -6,6 +6,21 @@ Bundler.setup
|
|
|
6
6
|
|
|
7
7
|
Bundler::GemHelper.install_tasks
|
|
8
8
|
|
|
9
|
+
if RUBY_ENGINE == 'ruby' && Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.3')
|
|
10
|
+
require_relative 'script/release_preparation'
|
|
11
|
+
require 'ruby-gem-release/workflow/ruby_gem'
|
|
12
|
+
|
|
13
|
+
workflow = RubyGemRelease::Workflow::RubyGem.new(
|
|
14
|
+
version: RubyGemRelease::Version::RubyConstant.new(path: 'lib/hashie/version.rb', constant: 'VERSION'),
|
|
15
|
+
changelog: RubyGemRelease::Changelog::KeepAChangelog.new
|
|
16
|
+
)
|
|
17
|
+
preparation = HashieReleasePreparation.new
|
|
18
|
+
workflow.before(:validate) { |ctx| preparation.validate!(ctx.release, changelog: ctx.workflow.changelog) }
|
|
19
|
+
workflow.before(:prepare_release) { |ctx| preparation.prepare!(ctx.release, changelog: ctx.workflow.changelog) }
|
|
20
|
+
workflow.after(:prepare_next) { |ctx| preparation.prepare_next!(ctx.release) }
|
|
21
|
+
workflow.install_tasks
|
|
22
|
+
end
|
|
23
|
+
|
|
9
24
|
require 'rspec/core/rake_task'
|
|
10
25
|
RSpec::Core::RakeTask.new do |spec|
|
|
11
26
|
spec.pattern = 'spec/**/*_spec.rb'
|
data/UPGRADING.md
CHANGED
|
@@ -1,24 +1,92 @@
|
|
|
1
1
|
Upgrading Hashie
|
|
2
2
|
================
|
|
3
3
|
|
|
4
|
+
### Upgrading to 6.0.0
|
|
5
|
+
|
|
6
|
+
#### YAML loading no longer evaluates ERB by default
|
|
7
|
+
|
|
8
|
+
`Hashie::Mash.load` now defaults to `Hashie::Extensions::Parsers::YamlParser`, introduced in #625, rather than `YamlErbParser`. This is a breaking change for configuration files containing ERB: template expressions are no longer evaluated and may remain literal strings or cause YAML parsing errors.
|
|
9
|
+
|
|
10
|
+
For trusted files that require ERB interpolation, explicitly select the previous parser:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
Hashie::Mash.load('trusted-settings.yml', parser: Hashie::Extensions::Parsers::YamlErbParser)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Do not use `YamlErbParser` with untrusted input: it executes Ruby before parsing YAML. Both parsers still use `YAML.safe_load`, and `permitted_classes`, `permitted_symbols`, and `aliases` retain their existing meanings. Custom parsers, frozen results, and path-based caching are unchanged. Because cached results are keyed only by path, use separate Mash subclasses if the same file needs different parser policies.
|
|
17
|
+
|
|
18
|
+
See [#631](https://github.com/hashie/hashie/pull/631) for more information.
|
|
19
|
+
|
|
20
|
+
#### DeepMerge preserves incoming hash subclasses
|
|
21
|
+
|
|
22
|
+
`Hashie::Extensions::DeepMerge` now preserves the class, instance state, and defaults of incoming nested hashes by default. New destinations use `source.dup.clear` instead of `{}`, so required constructor arguments and required-property Dashes do not require an empty constructor. Existing destination hashes keep their own types; source hashes are never cleared.
|
|
23
|
+
|
|
24
|
+
This is a breaking change for code relying on incoming subclasses becoming plain hashes. Preserved subclasses may enforce custom assignment rules or expose different lookup behavior. Applications requiring plain nested hashes must explicitly normalize their input hash values before merging.
|
|
25
|
+
|
|
26
|
+
The policy applies at every incoming nested-hash level for both `deep_merge` and `deep_merge!`, including hashes inside arrays. Custom hashes must support duplication, clearing, and assignment into the copied hash.
|
|
27
|
+
|
|
28
|
+
See [#632](https://github.com/hashie/hashie/pull/632) for more information.
|
|
29
|
+
|
|
30
|
+
#### DeepMerge value copying
|
|
31
|
+
|
|
32
|
+
`Hashie::Extensions::DeepMerge` now recursively copies hashes, arrays, and strings on both sides, but retains other values by identity. It no longer calls `Hashie::Utils.safe_dup` on receiver leaves or `deep_dup` on incoming leaves. ActiveRecord models therefore retain their identity, primary keys, and persistence state without a custom policy.
|
|
33
|
+
|
|
34
|
+
This is a breaking change: arbitrary receiver objects are now shared, incoming strings are now copied even without ActiveSupport, and receiver arrays are copied recursively rather than shallowly. Arrays still replace conflicting values rather than merging element by element. Incoming hashes inside arrays preserve their subclasses, and existing receiver container subclasses retain their types.
|
|
35
|
+
|
|
36
|
+
If your application needs independent copies of domain objects, explicitly copy them according to their own APIs before merging. Do not assume `deep_merge` clones an entire object graph. `deep_merge!` keeps existing receiver values in place, and conflict-block results are still stored unchanged. Cyclic inputs are not supported.
|
|
37
|
+
|
|
38
|
+
See [#633](https://github.com/hashie/hashie/pull/633) for more information.
|
|
39
|
+
|
|
40
|
+
#### Mash preserves custom Hash values
|
|
41
|
+
|
|
42
|
+
Nested Hash subclasses other than Mash are now stored by identity instead of being converted into Mashes or copied. This applies to initialization, assignment, arrays, copying, replacement, and merges. Plain Hash values are still recursively converted, and existing Mash-specific copying behavior is unchanged.
|
|
43
|
+
|
|
44
|
+
Code relying on `mash.custom.some_key` for a custom Hash value must use that object's own API, such as `mash.custom[:some_key]`, or explicitly convert it with `Hashie::Mash.new(custom_hash)` before insertion. A custom Hash replaces an existing nested Mash during merge rather than recursively merging into it. Because custom values are shared, `dup` and `merge` no longer isolate mutations to them; explicitly copy such values when needed.
|
|
45
|
+
|
|
46
|
+
See [#637](https://github.com/hashie/hashie/pull/637) for more information.
|
|
47
|
+
|
|
48
|
+
#### Dash and Trash accessor collisions
|
|
49
|
+
|
|
50
|
+
Property declarations no longer replace existing methods, regardless of visibility. Read and write conflicting properties with brackets: use `dash[:class]` instead of `dash.class`, and `dash[:count]` instead of `dash.count`. The latter methods retain their normal Ruby/Hash behavior. The same rule applies to translated source writers; use `trash[:source] = value` if `source=` already exists. Existing custom accessors are preserved; required and predefined-value validation now reads stored property values through brackets rather than invoking those accessors.
|
|
51
|
+
|
|
52
|
+
See [#639](https://github.com/hashie/hashie/pull/639) for more information.
|
|
53
|
+
|
|
54
|
+
#### Internal helper names
|
|
55
|
+
|
|
56
|
+
Private and protected implementation helpers consistently use one leading underscore, including `_translations` (formerly `__translations`) and `_convert` (formerly `__convert`). Subclasses overriding renamed helpers must update those overrides and calls. Ruby callbacks, documented extension hooks such as `initialize_attributes`, `convert_key`, and `convert_value`, and public methods keep their existing names. The documented `__method` aliases created by `MethodAccessWithOverride` also remain unchanged; those expose overridden methods rather than internal helpers.
|
|
57
|
+
|
|
58
|
+
Recursive key-conversion helpers, `Hashie::Utils`' integer-class helper, and Clash's merge-storage helper are now private and use underscore-prefixed names. Mash suffix parsing, method-redefinition helpers, and indifferent-hash detection are also private rather than protected. Use the public conversion, builder, and accessor APIs instead of calling these helpers directly.
|
|
59
|
+
|
|
60
|
+
See [#639](https://github.com/hashie/hashie/pull/639) for more information.
|
|
61
|
+
|
|
62
|
+
#### DeepFind results
|
|
63
|
+
|
|
64
|
+
`deep_find_all` and its alias `deep_select` now return `[]` instead of `nil` when no matches exist, matching `Enumerable#find_all`. Replace no-match checks such as `result.nil?` or `if result` with `result.empty?` or `result.any?`. Fallbacks such as `deep_find_all(:key) || []` are no longer necessary. `deep_find` and `deep_detect` still return `nil` when no match exists; matching values, including `nil`, remain in the results array.
|
|
65
|
+
|
|
66
|
+
See [#641](https://github.com/hashie/hashie/pull/641) for more information.
|
|
67
|
+
|
|
4
68
|
### Upgrading to 5.2.0
|
|
5
69
|
|
|
6
70
|
#### Safer YAML loading
|
|
7
71
|
|
|
8
72
|
`Hashie::Mash.load` continues to use `YamlErbParser` by default for compatibility, and that parser evaluates ERB. Do not load untrusted input with the default parser. To opt in to YAML parsing without ERB evaluation, pass `parser: Hashie::Extensions::Parsers::YamlParser`.
|
|
9
73
|
|
|
74
|
+
See [#625](https://github.com/hashie/hashie/pull/625) for more information.
|
|
75
|
+
|
|
10
76
|
#### Ruby version
|
|
11
77
|
|
|
12
78
|
Hashie now requires Ruby 3.0 or newer. Ruby 2.7 is no longer supported; upgrade Ruby before updating Hashie.
|
|
13
79
|
|
|
80
|
+
See [#622](https://github.com/hashie/hashie/pull/622) for more information.
|
|
81
|
+
|
|
14
82
|
#### Exception messages
|
|
15
83
|
|
|
16
|
-
Exception messages now begin with lowercase words, omit trailing periods, quote interpolated values
|
|
17
|
-
with backticks, and use consistent wording. Exception classes and the conditions under which
|
|
18
|
-
exceptions are raised have not changed.
|
|
84
|
+
Exception messages now begin with lowercase words, omit trailing periods, quote interpolated values with backticks, and use consistent wording. Exception classes and the conditions under which exceptions are raised have not changed.
|
|
19
85
|
|
|
20
86
|
Applications and tests that compare exact exception message strings must update their expectations.
|
|
21
87
|
|
|
88
|
+
See [#607](https://github.com/hashie/hashie/pull/607) for more information.
|
|
89
|
+
|
|
22
90
|
### Upgrading to 5.0.0
|
|
23
91
|
|
|
24
92
|
#### Mash initialization key conversion
|
|
@@ -40,6 +108,8 @@ Hashie::Mash.new(
|
|
|
40
108
|
{{:foo=>"bar"}=>"baz", "1"=>"one sym", 1=>"one num"}
|
|
41
109
|
```
|
|
42
110
|
|
|
111
|
+
See [#521](https://github.com/hashie/hashie/pull/521) for more information.
|
|
112
|
+
|
|
43
113
|
#### Mash#dig with numeric keys
|
|
44
114
|
|
|
45
115
|
`Hashie::Mash#dig` no longer considers numeric keys for indifferent access.
|
|
@@ -57,6 +127,8 @@ my_mash.dig(1) # => "a"
|
|
|
57
127
|
my_mash.dig(1) # => nil
|
|
58
128
|
```
|
|
59
129
|
|
|
130
|
+
See [#521](https://github.com/hashie/hashie/pull/521) for more information.
|
|
131
|
+
|
|
60
132
|
### Upgrading to 4.0.0
|
|
61
133
|
|
|
62
134
|
#### Non-destructive Hash methods called on Mash
|
|
@@ -92,14 +164,20 @@ p cool_parents
|
|
|
92
164
|
|
|
93
165
|
This may make places where you had to re-make the Mash redundant, and may cause unintended side effects if your application was expecting a plain old ruby Hash.
|
|
94
166
|
|
|
167
|
+
See [#481](https://github.com/hashie/hashie/pull/481) for more information.
|
|
168
|
+
|
|
95
169
|
#### Ruby 2.6: Mash#merge and Mash#merge!
|
|
96
170
|
|
|
97
171
|
In Ruby > 2.6.0, Hashie now supports passing multiple hash and Mash objects to Mash#merge and Mash#merge!.
|
|
98
172
|
|
|
173
|
+
See [#481](https://github.com/hashie/hashie/pull/481) for more information.
|
|
174
|
+
|
|
99
175
|
#### Hashie::Mash::CannotDisableMashWarnings error class is removed
|
|
100
176
|
|
|
101
177
|
There shouldn't really be a case that anyone was relying on catching this specific error, but if so, they should change it to rescue Hashie::Extensions::KeyConflictWarning::CannotDisableMashWarnings
|
|
102
178
|
|
|
179
|
+
See [#488](https://github.com/hashie/hashie/pull/488) for more information.
|
|
180
|
+
|
|
103
181
|
### Upgrading to 3.7.0
|
|
104
182
|
|
|
105
183
|
#### Mash#load takes options
|
|
@@ -135,12 +213,13 @@ Options can now be passed into `Mash#load`.
|
|
|
135
213
|
Mash.load(filename, permitted_classes: [])
|
|
136
214
|
```
|
|
137
215
|
|
|
216
|
+
See [#474](https://github.com/hashie/hashie/pull/474) for more information.
|
|
217
|
+
|
|
138
218
|
### Upgrading to 3.5.2
|
|
139
219
|
|
|
140
220
|
#### Disable logging in Mash subclasses
|
|
141
221
|
|
|
142
|
-
If you subclass `Hashie::Mash`, you can now disable the logging we do about
|
|
143
|
-
overriding existing methods with keys. This looks like:
|
|
222
|
+
If you subclass `Hashie::Mash`, you can now disable the logging we do about overriding existing methods with keys. This looks like:
|
|
144
223
|
|
|
145
224
|
```ruby
|
|
146
225
|
class MyMash < Hashie::Mash
|
|
@@ -148,6 +227,8 @@ class MyMash < Hashie::Mash
|
|
|
148
227
|
end
|
|
149
228
|
```
|
|
150
229
|
|
|
230
|
+
See [#395](https://github.com/hashie/hashie/pull/395) for more information.
|
|
231
|
+
|
|
151
232
|
### Upgrading to 3.4.7
|
|
152
233
|
|
|
153
234
|
#### Procs as default values for Dash
|
|
@@ -160,6 +241,9 @@ end
|
|
|
160
241
|
|
|
161
242
|
In versions < 3.4.7 `Time.now` will be evaluated when `time` property is accessed directly first time.
|
|
162
243
|
In version >= 3.4.7 `Time.now` is evaluated in time of object initialization.
|
|
244
|
+
|
|
245
|
+
See [#380](https://github.com/hashie/hashie/pull/380) for more information.
|
|
246
|
+
|
|
163
247
|
### Upgrading to 3.4.4
|
|
164
248
|
|
|
165
249
|
#### Mash subclasses and reverse_merge
|
|
@@ -171,6 +255,8 @@ end
|
|
|
171
255
|
|
|
172
256
|
In versions >= 3.4.4 `MyMash#reverse_merge` returns an instance of `MyMash` but in previous versions it was a `Hashie::Mash` instance.
|
|
173
257
|
|
|
258
|
+
See [#322](https://github.com/hashie/hashie/pull/322) for more information.
|
|
259
|
+
|
|
174
260
|
### Upgrading to 3.2.2
|
|
175
261
|
|
|
176
262
|
#### Testing if key defined
|
|
@@ -195,6 +281,8 @@ h.abc? # => true
|
|
|
195
281
|
h.abb? # => false
|
|
196
282
|
```
|
|
197
283
|
|
|
284
|
+
See [#317](https://github.com/hashie/hashie/pull/317) for more information.
|
|
285
|
+
|
|
198
286
|
### Upgrading to 3.2.1
|
|
199
287
|
|
|
200
288
|
#### Possible coercion changes
|
|
@@ -205,12 +293,16 @@ The improvements made to coercions in version 3.2.1 [issue #200](https://github.
|
|
|
205
293
|
|
|
206
294
|
If necessary, `dup` or `clone` your own objects. Do not assume Hashie will do it for you.
|
|
207
295
|
|
|
296
|
+
See [#200](https://github.com/hashie/hashie/pull/200) for more information.
|
|
297
|
+
|
|
208
298
|
**Change**: Failed coercion attempts now raise Hashie::CoercionError.
|
|
209
299
|
|
|
210
300
|
Hashie now raises a Hashie::CoercionError that details on the property that could not be coerced, the source and target type of the coercion, and the internal error. Previously only the internal error was raised.
|
|
211
301
|
|
|
212
302
|
Applications that were attempting to rescuing the internal errors should be updated to rescue Hashie::CoercionError instead.
|
|
213
303
|
|
|
304
|
+
See [#200](https://github.com/hashie/hashie/pull/200) for more information.
|
|
305
|
+
|
|
214
306
|
### Upgrading to 3.0
|
|
215
307
|
|
|
216
308
|
#### Compatibility with Rails 4 Strong Parameters
|
|
@@ -223,7 +315,7 @@ To enable 2.1 compatible behavior with Rails 4, use the [hashie_rails](http://ru
|
|
|
223
315
|
gem 'hashie_rails'
|
|
224
316
|
```
|
|
225
317
|
|
|
226
|
-
See [#154](https://github.com/hashie/hashie/pull/154)
|
|
318
|
+
See [#154](https://github.com/hashie/hashie/pull/154) for more information. Also see [Mash and Rails 4 Strong Parameters](README.md#mash-and-rails-4-strong-parameters).
|
|
227
319
|
|
|
228
320
|
#### Key Conversions in Hashie::Dash and Hashie::Trash
|
|
229
321
|
|
|
@@ -273,6 +365,8 @@ class Person < Hashie::Dash
|
|
|
273
365
|
end
|
|
274
366
|
```
|
|
275
367
|
|
|
368
|
+
See [#152](https://github.com/hashie/hashie/pull/152) for more information.
|
|
369
|
+
|
|
276
370
|
#### Key Conversions in Hashie::Hash#to_hash
|
|
277
371
|
|
|
278
372
|
Version 2.1 or older of Hash#to_hash converted keys to strings automatically.
|
data/lib/hashie/clash.rb
CHANGED
|
@@ -97,7 +97,7 @@ module Hashie
|
|
|
97
97
|
|
|
98
98
|
# @example Merge repeated hash options through chained builder calls
|
|
99
99
|
# Hashie::Clash.new.conditions(active: true).conditions(role: 'admin') # => { conditions: { active: true, role: 'admin' } }
|
|
100
|
-
def
|
|
100
|
+
def _merge_store(key, *args) # :nodoc:
|
|
101
101
|
case args.length
|
|
102
102
|
when 1
|
|
103
103
|
val = args.first
|
|
@@ -109,6 +109,7 @@ module Hashie
|
|
|
109
109
|
self[key.to_sym] = val
|
|
110
110
|
self
|
|
111
111
|
end
|
|
112
|
+
private :_merge_store
|
|
112
113
|
|
|
113
114
|
# @example Build nested options with dynamic method names
|
|
114
115
|
# Hashie::Clash.new.conditions!.active(true)._end!.limit(10) # => { conditions: { active: true }, limit: 10 }
|
|
@@ -127,7 +128,7 @@ module Hashie
|
|
|
127
128
|
raise ChainError, 'tried to chain into a non-hash key'
|
|
128
129
|
end
|
|
129
130
|
elsif args.any?
|
|
130
|
-
|
|
131
|
+
_merge_store(name, *args)
|
|
131
132
|
else
|
|
132
133
|
super
|
|
133
134
|
end
|
data/lib/hashie/dash.rb
CHANGED
|
@@ -57,8 +57,8 @@ module Hashie
|
|
|
57
57
|
defaults.delete property_name
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
_define_getter_for(property_name)
|
|
61
|
+
_define_setter_for(property_name)
|
|
62
62
|
|
|
63
63
|
@subclasses.each { |klass| klass.property(property_name, options) } if defined? @subclasses
|
|
64
64
|
|
|
@@ -133,16 +133,16 @@ module Hashie
|
|
|
133
133
|
required_properties.key? name
|
|
134
134
|
end
|
|
135
135
|
|
|
136
|
-
private_class_method def self.
|
|
137
|
-
return if
|
|
136
|
+
private_class_method def self._define_getter_for(property_name)
|
|
137
|
+
return if method_defined?(property_name) || private_method_defined?(property_name)
|
|
138
138
|
|
|
139
139
|
define_method(property_name) { |&block| self.[](property_name, &block) }
|
|
140
140
|
getters << property_name
|
|
141
141
|
end
|
|
142
142
|
|
|
143
|
-
private_class_method def self.
|
|
143
|
+
private_class_method def self._define_setter_for(property_name)
|
|
144
144
|
setter = :"#{property_name}="
|
|
145
|
-
return if method_defined?(setter)
|
|
145
|
+
return if method_defined?(setter) || private_method_defined?(setter)
|
|
146
146
|
|
|
147
147
|
define_method(setter) { |value| self.[]=(property_name, value) }
|
|
148
148
|
end
|
|
@@ -160,7 +160,7 @@ module Hashie
|
|
|
160
160
|
super(&block)
|
|
161
161
|
|
|
162
162
|
initialize_attributes(attributes)
|
|
163
|
-
|
|
163
|
+
_assert_required_attributes_set!
|
|
164
164
|
end
|
|
165
165
|
|
|
166
166
|
alias _regular_reader []
|
|
@@ -174,7 +174,7 @@ module Hashie
|
|
|
174
174
|
# klass = Class.new(Hashie::Dash) { property :name, default: 'Ada' }
|
|
175
175
|
# klass.new[:name] # => 'Ada'
|
|
176
176
|
def [](property)
|
|
177
|
-
|
|
177
|
+
_assert_property_exists! property
|
|
178
178
|
value = super
|
|
179
179
|
# If the value is a lambda, proc, or whatever answers to call, eval the thing!
|
|
180
180
|
if value.is_a? Proc
|
|
@@ -194,8 +194,8 @@ module Hashie
|
|
|
194
194
|
# dash[:name] = 'Ada'
|
|
195
195
|
# dash.name # => 'Ada'
|
|
196
196
|
def []=(property, value)
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
_assert_property_required! property, value
|
|
198
|
+
_assert_property_exists! property
|
|
199
199
|
super
|
|
200
200
|
end
|
|
201
201
|
|
|
@@ -203,7 +203,7 @@ module Hashie
|
|
|
203
203
|
# klass = Class.new(Hashie::Dash) { property :count }
|
|
204
204
|
# dash = klass.new(count: 2)
|
|
205
205
|
# dash.merge(count: 3) { |_key, old, new| old + new }.to_h # => { count: 5 }
|
|
206
|
-
# dash
|
|
206
|
+
# dash[:count] # => 2
|
|
207
207
|
def merge(other_hash)
|
|
208
208
|
new_dash = dup
|
|
209
209
|
other_hash.each do |k, v|
|
|
@@ -276,7 +276,7 @@ module Hashie
|
|
|
276
276
|
# dash.update_attributes!(name: nil)
|
|
277
277
|
# dash.name # => 'Ada'
|
|
278
278
|
def update_attributes!(attributes)
|
|
279
|
-
|
|
279
|
+
_update_attributes(attributes)
|
|
280
280
|
|
|
281
281
|
self.class.defaults.each_pair do |prop, value|
|
|
282
282
|
next unless fetch(prop, nil).nil?
|
|
@@ -293,7 +293,7 @@ module Hashie
|
|
|
293
293
|
end
|
|
294
294
|
end
|
|
295
295
|
|
|
296
|
-
|
|
296
|
+
_assert_required_attributes_set!
|
|
297
297
|
end
|
|
298
298
|
|
|
299
299
|
private
|
|
@@ -305,7 +305,7 @@ module Hashie
|
|
|
305
305
|
update_attributes!(cleaned_attributes)
|
|
306
306
|
end
|
|
307
307
|
|
|
308
|
-
def
|
|
308
|
+
def _update_attributes(attributes)
|
|
309
309
|
return unless attributes
|
|
310
310
|
|
|
311
311
|
attributes.each_pair do |att, value|
|
|
@@ -313,31 +313,31 @@ module Hashie
|
|
|
313
313
|
end
|
|
314
314
|
end
|
|
315
315
|
|
|
316
|
-
def
|
|
317
|
-
|
|
316
|
+
def _assert_property_exists!(property)
|
|
317
|
+
_fail_no_property_error!(property) unless self.class.property?(property)
|
|
318
318
|
end
|
|
319
319
|
|
|
320
|
-
def
|
|
320
|
+
def _assert_required_attributes_set!
|
|
321
321
|
self.class.required_properties.each_key do |required_property|
|
|
322
|
-
|
|
322
|
+
_assert_property_set!(required_property)
|
|
323
323
|
end
|
|
324
324
|
end
|
|
325
325
|
|
|
326
|
-
def
|
|
327
|
-
|
|
326
|
+
def _assert_property_set!(property)
|
|
327
|
+
_fail_property_required_error!(property) if self[property].nil? && _required?(property)
|
|
328
328
|
end
|
|
329
329
|
|
|
330
|
-
def
|
|
331
|
-
|
|
330
|
+
def _assert_property_required!(property, value)
|
|
331
|
+
_fail_property_required_error!(property) if value.nil? && _required?(property)
|
|
332
332
|
end
|
|
333
333
|
|
|
334
|
-
def
|
|
334
|
+
def _fail_property_required_error!(property)
|
|
335
335
|
message = self.class.required_properties[property][:message]
|
|
336
336
|
raise ArgumentError,
|
|
337
337
|
"the property `#{property}` " + message
|
|
338
338
|
end
|
|
339
339
|
|
|
340
|
-
def
|
|
340
|
+
def _fail_no_property_error!(property)
|
|
341
341
|
raise NoMethodError,
|
|
342
342
|
"the property `#{property}` is not defined for `#{self.class.name || 'anonymous class'}`"
|
|
343
343
|
end
|
|
@@ -348,7 +348,7 @@ module Hashie
|
|
|
348
348
|
condition = self.class.required_properties[property][:condition]
|
|
349
349
|
case condition
|
|
350
350
|
when Proc then !!instance_exec(&condition)
|
|
351
|
-
when Symbol then !!send(condition)
|
|
351
|
+
when Symbol then !!(self.class.property?(condition) ? self[condition] : send(condition))
|
|
352
352
|
else !!condition
|
|
353
353
|
end
|
|
354
354
|
end
|
|
@@ -28,7 +28,7 @@ module Hashie
|
|
|
28
28
|
def [](key)
|
|
29
29
|
return super unless block_given?
|
|
30
30
|
|
|
31
|
-
value =
|
|
31
|
+
value = _block_access_child(self, key)
|
|
32
32
|
yield value
|
|
33
33
|
value
|
|
34
34
|
end
|
|
@@ -44,22 +44,22 @@ module Hashie
|
|
|
44
44
|
|
|
45
45
|
raise ArgumentError, "wrong number of arguments (given `#{args.size}`, expected 0..1)" if args.size > 1
|
|
46
46
|
|
|
47
|
-
child =
|
|
48
|
-
child =
|
|
47
|
+
child = _block_access_child(self, _block_access_key(name))
|
|
48
|
+
child = _block_access_child(child, args.first) unless args.empty?
|
|
49
49
|
block.call(child)
|
|
50
50
|
child
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
private
|
|
54
54
|
|
|
55
|
-
def
|
|
55
|
+
def _block_access_key(name)
|
|
56
56
|
return name if key?(name)
|
|
57
57
|
return name.to_s if key?(name.to_s)
|
|
58
58
|
|
|
59
59
|
respond_to?(:convert_key, true) ? convert_key(name.to_s) : name.to_s
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
def
|
|
62
|
+
def _block_access_child(parent, key)
|
|
63
63
|
parent[key] = parent.class.new unless parent.key?(key)
|
|
64
64
|
child = parent[key]
|
|
65
65
|
return child if child.is_a?(::Hash)
|