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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/CONTRIBUTING.md +2 -0
  4. data/README.md +60 -10
  5. data/Rakefile +15 -0
  6. data/UPGRADING.md +100 -6
  7. data/lib/hashie/clash.rb +3 -2
  8. data/lib/hashie/dash.rb +25 -25
  9. data/lib/hashie/extensions/block_access.rb +5 -5
  10. data/lib/hashie/extensions/dash/coercion.rb +4 -4
  11. data/lib/hashie/extensions/dash/indifferent_access.rb +5 -5
  12. data/lib/hashie/extensions/dash/lazy.rb +10 -10
  13. data/lib/hashie/extensions/dash/predefined_values.rb +12 -16
  14. data/lib/hashie/extensions/dash/property_translation.rb +22 -14
  15. data/lib/hashie/extensions/deep_find.rb +4 -4
  16. data/lib/hashie/extensions/deep_grep.rb +15 -15
  17. data/lib/hashie/extensions/deep_locate.rb +3 -3
  18. data/lib/hashie/extensions/deep_merge.rb +16 -14
  19. data/lib/hashie/extensions/deep_merge_initializer.rb +3 -3
  20. data/lib/hashie/extensions/indifferent_access.rb +6 -6
  21. data/lib/hashie/extensions/mash/define_accessors.rb +3 -1
  22. data/lib/hashie/extensions/mash/keep_original_keys.rb +2 -2
  23. data/lib/hashie/extensions/mash/permissive_respond_to.rb +1 -1
  24. data/lib/hashie/extensions/mash/required_keys.rb +2 -2
  25. data/lib/hashie/extensions/mash/strict_key_access.rb +2 -2
  26. data/lib/hashie/extensions/method_access.rb +15 -15
  27. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +1 -1
  28. data/lib/hashie/extensions/parsers/yaml_parser.rb +2 -2
  29. data/lib/hashie/extensions/persistable.rb +4 -4
  30. data/lib/hashie/extensions/ruby_version.rb +2 -2
  31. data/lib/hashie/extensions/stringify_keys.rb +4 -3
  32. data/lib/hashie/extensions/symbolize_keys.rb +4 -3
  33. data/lib/hashie/hash.rb +5 -5
  34. data/lib/hashie/mash.rb +20 -16
  35. data/lib/hashie/rash.rb +2 -2
  36. data/lib/hashie/utils.rb +3 -2
  37. data/lib/hashie/version.rb +1 -1
  38. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1623620401412d6612e1b7a1d2963cd9b31d584c3278987fafe45eac8cdd025b
4
- data.tar.gz: f3bb1e89d55110e20bed0b5c76e6e9e817c1e99ad0fed2e7ee7c89cc948baaf8
3
+ metadata.gz: 0fa02edecdffba1e181cfe0e80f6aa55db45f1b6b1cd0fc866b3bbc4ef6abd16
4
+ data.tar.gz: 02fefdce2dce50542155afa916e664f2f06b0d31bdc322fcde3ef70b33260c00
5
5
  SHA512:
6
- metadata.gz: dea36ed10af73ca738634902bf043db652baa251b43066fcb9e309f28c501ac60ea843ef66944fd1b5a5801f37bac385bb2e165daeef9e62498f527387502e2e
7
- data.tar.gz: 1131b72244560c35aaff3c6b3fcc28da13caa67c74b7c6f3dc6628c0cbe0ee74ae85429165d04988d1466ff045ee9198505c48192833ca249260cea3f2310dc8
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, 5.2.0.
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` retains its existing caching and YAML/ERB behavior. No autosave or other serialization formats are provided.
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: <%= ENV.fetch('HASHIE_README_API_KEY') %> # set to 'abcd' below
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: <%= ENV.fetch('HASHIE_README_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` evaluates ERB before parsing YAML. Only use it with trusted files; `YAML.safe_load` does not make ERB evaluation safe. To parse untrusted or plain YAML without evaluating ERB, explicitly select `YamlParser`:
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', parser: Hashie::Extensions::Parsers::YamlParser)
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 (by default: [YamlErbParser](lib/hashie/extensions/parsers/yaml_erb_parser.rb), which evaluates ERB):
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) and [Mash and Rails 4 Strong Parameters](README.md#mash-and-rails-4-strong-parameters) for more details.
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 merge_store(key, *args) # :nodoc:
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
- merge_store(name, *args)
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
- define_getter_for(property_name)
61
- define_setter_for(property_name)
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.define_getter_for(property_name)
137
- return if getters.include?(property_name)
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.define_setter_for(property_name)
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
- assert_required_attributes_set!
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
- assert_property_exists! property
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
- assert_property_required! property, value
198
- assert_property_exists! property
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.count # => 2
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
- update_attributes(attributes)
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
- assert_required_attributes_set!
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 update_attributes(attributes)
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 assert_property_exists!(property)
317
- fail_no_property_error!(property) unless self.class.property?(property)
316
+ def _assert_property_exists!(property)
317
+ _fail_no_property_error!(property) unless self.class.property?(property)
318
318
  end
319
319
 
320
- def assert_required_attributes_set!
320
+ def _assert_required_attributes_set!
321
321
  self.class.required_properties.each_key do |required_property|
322
- assert_property_set!(required_property)
322
+ _assert_property_set!(required_property)
323
323
  end
324
324
  end
325
325
 
326
- def assert_property_set!(property)
327
- fail_property_required_error!(property) if send(property).nil? && _required?(property)
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 assert_property_required!(property, value)
331
- fail_property_required_error!(property) if value.nil? && _required?(property)
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 fail_property_required_error!(property)
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 fail_no_property_error!(property)
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 = block_access_child(self, key)
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 = block_access_child(self, block_access_key(name))
48
- child = block_access_child(child, args.first) unless args.empty?
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 block_access_key(name)
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 block_access_child(parent, key)
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)