hashie 5.1.0 → 5.2.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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +56 -0
  3. data/CONTRIBUTING.md +38 -1
  4. data/README.md +816 -181
  5. data/Rakefile +29 -1
  6. data/UPGRADING.md +18 -0
  7. data/hashie.gemspec +8 -7
  8. data/lib/hashie/array.rb +15 -12
  9. data/lib/hashie/clash.rb +59 -4
  10. data/lib/hashie/dash.rb +144 -16
  11. data/lib/hashie/extensions/active_support/core_ext/hash.rb +38 -0
  12. data/lib/hashie/extensions/array/pretty_inspect.rb +20 -0
  13. data/lib/hashie/extensions/block_access.rb +71 -0
  14. data/lib/hashie/extensions/coercion.rb +180 -19
  15. data/lib/hashie/extensions/dash/coercion.rb +34 -3
  16. data/lib/hashie/extensions/dash/indifferent_access.rb +83 -5
  17. data/lib/hashie/extensions/dash/lazy.rb +170 -0
  18. data/lib/hashie/extensions/dash/predefined_values.rb +69 -9
  19. data/lib/hashie/extensions/dash/property_translation.rb +134 -16
  20. data/lib/hashie/extensions/deep_fetch.rb +21 -9
  21. data/lib/hashie/extensions/deep_find.rb +20 -0
  22. data/lib/hashie/extensions/deep_grep.rb +69 -0
  23. data/lib/hashie/extensions/deep_locate.rb +15 -28
  24. data/lib/hashie/extensions/deep_merge.rb +16 -0
  25. data/lib/hashie/extensions/deep_merge_initializer.rb +42 -0
  26. data/lib/hashie/extensions/ignore_undeclared.rb +21 -3
  27. data/lib/hashie/extensions/indifferent_access.rb +100 -14
  28. data/lib/hashie/extensions/kernel_coercion.rb +58 -0
  29. data/lib/hashie/extensions/key_conflict_warning.rb +39 -4
  30. data/lib/hashie/extensions/key_conversion.rb +9 -0
  31. data/lib/hashie/extensions/mash/define_accessors.rb +71 -8
  32. data/lib/hashie/extensions/mash/keep_original_keys.rb +13 -9
  33. data/lib/hashie/extensions/mash/permissive_respond_to.rb +2 -0
  34. data/lib/hashie/extensions/mash/required_keys.rb +111 -0
  35. data/lib/hashie/extensions/mash/safe_assignment.rb +22 -4
  36. data/lib/hashie/extensions/mash/strict_key_access.rb +65 -0
  37. data/lib/hashie/extensions/mash/symbolize_keys.rb +6 -2
  38. data/lib/hashie/extensions/mash/transform_keys.rb +86 -0
  39. data/lib/hashie/extensions/mash/underscore_keys.rb +32 -0
  40. data/lib/hashie/extensions/merge_initializer.rb +9 -0
  41. data/lib/hashie/extensions/method_access.rb +93 -7
  42. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +28 -35
  43. data/lib/hashie/extensions/parsers/yaml_parser.rb +81 -0
  44. data/lib/hashie/extensions/persistable.rb +137 -0
  45. data/lib/hashie/extensions/pretty_inspect.rb +14 -0
  46. data/lib/hashie/extensions/ruby_version.rb +18 -1
  47. data/lib/hashie/extensions/ruby_version_check.rb +17 -0
  48. data/lib/hashie/extensions/strict_key_access.rb +59 -5
  49. data/lib/hashie/extensions/stringify_keys.rb +72 -17
  50. data/lib/hashie/extensions/symbolize_keys.rb +72 -16
  51. data/lib/hashie/hash.rb +16 -0
  52. data/lib/hashie/logger.rb +18 -1
  53. data/lib/hashie/mash.rb +293 -62
  54. data/lib/hashie/railtie.rb +27 -1
  55. data/lib/hashie/rash.rb +64 -4
  56. data/lib/hashie/trash.rb +8 -0
  57. data/lib/hashie/utils.rb +17 -6
  58. data/lib/hashie/version.rb +3 -1
  59. data/lib/hashie.rb +45 -0
  60. metadata +15 -17
data/lib/hashie/rash.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hashie
2
4
  #
3
5
  # Rash is a Hash whose keys can be Regexps, or Ranges, which will
@@ -20,9 +22,23 @@ module Hashie
20
22
  # If this is too low or too high, you can tune it by
21
23
  # setting: `rash.optimize_every = n`
22
24
  #
25
+ # @example Match strings and numbers using pattern keys
26
+ # rash = Hashie::Rash.new(/^Mr\./ => 'Hello sir!', (1..5) => 'small')
27
+ # rash['Mr. Austin'] # => 'Hello sir!'
28
+ # rash[3] # => 'small'
23
29
  class Rash
30
+ # @example Configure the interval between regular-expression optimizations
31
+ # rash = Hashie::Rash.new
32
+ # rash.optimize_every # => 500
33
+ # rash.optimize_every = 10
34
+ # rash.optimize_every # => 10
24
35
  attr_accessor :optimize_every
25
36
 
37
+ # @example Initialize literal, regular-expression, and range keys
38
+ # rash = Hashie::Rash.new('home' => 'index', /^user/ => 'profile', (1..5) => 'small')
39
+ # rash['home'] # => 'index'
40
+ # rash['user/1'] # => 'profile'
41
+ # rash[2] # => 'small'
26
42
  def initialize(initial = {})
27
43
  @hash = {}
28
44
  @regexes = []
@@ -34,6 +50,11 @@ module Hashie
34
50
  update(initial)
35
51
  end
36
52
 
53
+ # @example Add patterns and overwrite existing literal keys
54
+ # rash = Hashie::Rash.new('home' => 'old')
55
+ # rash.update('home' => 'index', /^user/ => 'profile')
56
+ # rash['home'] # => 'index'
57
+ # rash['user/1'] # => 'profile'
37
58
  def update(other)
38
59
  other.each do |key, value|
39
60
  self[key] = value
@@ -42,6 +63,10 @@ module Hashie
42
63
  self
43
64
  end
44
65
 
66
+ # @example Assign a pattern with a callable value
67
+ # rash = Hashie::Rash.new
68
+ # rash[/^hello (.+)$/] = ->(match) { "Hello, #{match[1]}!" }
69
+ # rash['hello Ada'] # => 'Hello, Ada!'
45
70
  def []=(key, value)
46
71
  case key
47
72
  when Regexp
@@ -56,6 +81,11 @@ module Hashie
56
81
  #
57
82
  # Return the first thing that matches the key.
58
83
  #
84
+ # @example Prefer literal matches and return nil for missing keys
85
+ # rash = Hashie::Rash.new(/^user/ => 'profile', 'user/admin' => 'admin')
86
+ # rash['user/admin'] # => 'admin'
87
+ # rash['user/1'] # => 'profile'
88
+ # rash['missing'] # => nil
59
89
  def [](key)
60
90
  all(key).first
61
91
  end
@@ -63,8 +93,22 @@ module Hashie
63
93
  #
64
94
  # Raise (or yield) unless something matches the key.
65
95
  #
96
+ # @example Fetch a match or compute a fallback
97
+ # rash = Hashie::Rash.new(/^user/ => 'profile')
98
+ # rash.fetch('user/1') # => 'profile'
99
+ # rash.fetch('missing', 'fallback') # => 'fallback'
100
+ # rash.fetch('missing') { |key| "unknown: #{key}" } # => 'unknown: missing'
101
+ #
102
+ # @example Raise for an unmatched key without a fallback
103
+ # error = begin
104
+ # Hashie::Rash.new.fetch('missing')
105
+ # rescue KeyError => exception
106
+ # exception
107
+ # end
108
+ # error.class # => KeyError
109
+ # error.message # => 'key not found: `"missing"`'
66
110
  def fetch(*args)
67
- raise ArgumentError, "Expected 1-2 arguments, got #{args.length}" \
111
+ raise ArgumentError, "expected 1-2 arguments, got `#{args.length}`" \
68
112
  unless (1..2).cover?(args.length)
69
113
 
70
114
  key, default = args
@@ -78,13 +122,19 @@ module Hashie
78
122
  elsif default
79
123
  default
80
124
  else
81
- raise KeyError, "key not found: #{key.inspect}"
125
+ raise KeyError, "key not found: `#{key.inspect}`"
82
126
  end
83
127
  end
84
128
 
85
129
  #
86
130
  # Return everything that matches the query.
87
131
  #
132
+ # @example Enumerate all matching patterns
133
+ # rash = Hashie::Rash.new(/^user/ => 'profile', /admin$/ => 'admin')
134
+ # rash.all('user/admin').to_a # => ['profile', 'admin']
135
+ # values = []
136
+ # rash.all('user/admin') { |value| values << value }
137
+ # values # => ['profile', 'admin']
88
138
  def all(query)
89
139
  return to_enum(:all, query) unless block_given?
90
140
 
@@ -101,6 +151,7 @@ module Hashie
101
151
  @regexes.each do |regex|
102
152
  match = regex.match(query)
103
153
  next unless match
154
+
104
155
  @regex_counts[regex] += 1
105
156
  value = @hash[regex]
106
157
  if value.respond_to? :call
@@ -124,10 +175,18 @@ module Hashie
124
175
  end
125
176
  end
126
177
 
127
- def method_missing(*args, &block)
128
- @hash.send(*args, &block) || super
178
+ # @example Delegate ordinary hash operations to stored entries
179
+ # rash = Hashie::Rash.new('home' => 'index')
180
+ # rash.keys # => ['home']
181
+ # rash.size # => 1
182
+ def method_missing(...)
183
+ @hash.send(...) || super
129
184
  end
130
185
 
186
+ # @example Advertise delegated hash operations
187
+ # rash = Hashie::Rash.new
188
+ # rash.respond_to?(:keys) # => true
189
+ # rash.respond_to?(:unknown_operation) # => false
131
190
  def respond_to_missing?(method_name, _include_private = false)
132
191
  @hash.respond_to?(method_name)
133
192
  end
@@ -136,6 +195,7 @@ module Hashie
136
195
 
137
196
  def optimize_if_necessary!
138
197
  return unless (@lookups += 1) >= @optimize_every
198
+
139
199
  @regexes = @regexes.sort_by { |regex| -@regex_counts[regex] }
140
200
  @lookups = 0
141
201
  end
data/lib/hashie/trash.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'hashie/dash'
2
4
  require 'hashie/extensions/dash/property_translation'
3
5
 
@@ -8,6 +10,12 @@ module Hashie
8
10
  # Trashes are useful when you need to read data from another application,
9
11
  # such as a Java api, where the keys are named differently from how we would
10
12
  # in Ruby.
13
+ #
14
+ # @example Translate incoming property names
15
+ # klass = Class.new(Hashie::Trash) do
16
+ # property :first_name, from: :firstName
17
+ # end
18
+ # klass.new(firstName: 'Ada').first_name # => 'Ada'
11
19
  class Trash < Dash
12
20
  include Hashie::Extensions::Dash::PropertyTranslation
13
21
  end
data/lib/hashie/utils.rb CHANGED
@@ -1,10 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hashie
2
4
  # A collection of helper methods that can be used throughout the gem.
5
+ #
6
+ # @example Duplicate mutable values safely
7
+ # Hashie::Utils.safe_dup(['Ada']) # => ['Ada']
3
8
  module Utils
4
9
  # Describes a method by where it was defined.
5
10
  #
6
11
  # @param bound_method [Method] The method to describe.
7
12
  # @return [String]
13
+ #
14
+ # @example Describe a built-in method
15
+ # Hashie::Utils.method_information('name'.method(:upcase)) # => 'defined in String'
8
16
  def self.method_information(bound_method)
9
17
  if bound_method.source_location
10
18
  "defined at #{bound_method.source_location.join(':')}"
@@ -19,6 +27,14 @@ module Hashie
19
27
  #
20
28
  # @param value [Object] the value to safely duplicate
21
29
  # @return [Object] the duplicated value
30
+ #
31
+ # @example Copy a mutable value without copying immutable symbols
32
+ # original = ['Ada']
33
+ # copy = Hashie::Utils.safe_dup(original)
34
+ # copy << 'Grace'
35
+ # original # => ['Ada']
36
+ # copy # => ['Ada', 'Grace']
37
+ # Hashie::Utils.safe_dup(:name) # => :name
22
38
  def self.safe_dup(value)
23
39
  case value
24
40
  when Complex, FalseClass, NilClass, Rational, Method, Symbol, TrueClass, *integer_classes
@@ -33,12 +49,7 @@ module Hashie
33
49
  # @api private
34
50
  # @return [Array<Class>]
35
51
  def self.integer_classes
36
- @integer_classes ||=
37
- if 0.class == Integer
38
- [Integer]
39
- else
40
- [Fixnum, Bignum] # rubocop:disable Lint/UnifiedInteger
41
- end
52
+ @integer_classes ||= [Integer]
42
53
  end
43
54
  end
44
55
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hashie
2
- VERSION = '5.1.0'.freeze
4
+ VERSION = '5.2.0'
3
5
  end
data/lib/hashie.rb CHANGED
@@ -1,6 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'hashie/logger'
2
4
  require 'hashie/version'
3
5
 
6
+ # Hash-like objects and extensions for structured data.
7
+ #
8
+ # @example Access nested data using a Mash
9
+ # Hashie::Mash.new(author: { name: 'Ada' }).author.name # => 'Ada'
4
10
  module Hashie
5
11
  autoload :Clash, 'hashie/clash'
6
12
  autoload :Dash, 'hashie/dash'
@@ -11,9 +17,17 @@ module Hashie
11
17
  autoload :Array, 'hashie/array'
12
18
  autoload :Utils, 'hashie/utils'
13
19
 
20
+ # Optional capabilities for Hash-like objects.
21
+ #
22
+ # @example Add recursive string keys to an ordinary hash
23
+ # hash = { count: 2 }.extend(Hashie::Extensions::StringifyKeys)
24
+ # hash.stringify_keys # => { 'count' => 2 }
14
25
  module Extensions
26
+ autoload :BlockAccess, 'hashie/extensions/block_access'
15
27
  autoload :Coercion, 'hashie/extensions/coercion'
28
+ autoload :KernelCoercion, 'hashie/extensions/kernel_coercion'
16
29
  autoload :DeepMerge, 'hashie/extensions/deep_merge'
30
+ autoload :DeepMergeInitializer, 'hashie/extensions/deep_merge_initializer'
17
31
  autoload :IgnoreUndeclared, 'hashie/extensions/ignore_undeclared'
18
32
  autoload :IndifferentAccess, 'hashie/extensions/indifferent_access'
19
33
  autoload :MergeInitializer, 'hashie/extensions/merge_initializer'
@@ -21,11 +35,13 @@ module Hashie
21
35
  autoload :MethodQuery, 'hashie/extensions/method_access'
22
36
  autoload :MethodReader, 'hashie/extensions/method_access'
23
37
  autoload :MethodWriter, 'hashie/extensions/method_access'
38
+ autoload :Persistable, 'hashie/extensions/persistable'
24
39
  autoload :StringifyKeys, 'hashie/extensions/stringify_keys'
25
40
  autoload :SymbolizeKeys, 'hashie/extensions/symbolize_keys'
26
41
  autoload :DeepFetch, 'hashie/extensions/deep_fetch'
27
42
  autoload :DeepFind, 'hashie/extensions/deep_find'
28
43
  autoload :DeepLocate, 'hashie/extensions/deep_locate'
44
+ autoload :DeepGrep, 'hashie/extensions/deep_grep'
29
45
  autoload :PrettyInspect, 'hashie/extensions/pretty_inspect'
30
46
  autoload :KeyConversion, 'hashie/extensions/key_conversion'
31
47
  autoload :MethodAccessWithOverride, 'hashie/extensions/method_access'
@@ -33,25 +49,54 @@ module Hashie
33
49
  autoload :RubyVersion, 'hashie/extensions/ruby_version'
34
50
  autoload :RubyVersionCheck, 'hashie/extensions/ruby_version_check'
35
51
 
52
+ # Parsers used when loading persisted hashes.
53
+ #
54
+ # @example Select the YAML parser without ERB evaluation
55
+ # Hashie::Extensions::Parsers::YamlParser.respond_to?(:perform) # => true
36
56
  module Parsers
57
+ autoload :YamlParser, 'hashie/extensions/parsers/yaml_parser'
37
58
  autoload :YamlErbParser, 'hashie/extensions/parsers/yaml_erb_parser'
38
59
  end
39
60
 
61
+ # Optional extensions for defined-property hashes.
62
+ #
63
+ # @example Allow string access to declared symbol properties
64
+ # klass = Class.new(Hashie::Dash) do
65
+ # include Hashie::Extensions::Dash::IndifferentAccess
66
+ # property :name
67
+ # end
68
+ # klass.new(name: 'Ada')['name'] # => 'Ada'
40
69
  module Dash
41
70
  autoload :IndifferentAccess, 'hashie/extensions/dash/indifferent_access'
42
71
  autoload :PropertyTranslation, 'hashie/extensions/dash/property_translation'
43
72
  autoload :Coercion, 'hashie/extensions/dash/coercion'
73
+ autoload :Lazy, 'hashie/extensions/dash/lazy'
44
74
  autoload :PredefinedValues, 'hashie/extensions/dash/predefined_values'
45
75
  end
46
76
 
77
+ # Optional extensions for method-accessible hashes.
78
+ #
79
+ # @example Generate accessors for existing keys
80
+ # mash = Hashie::Mash.new(name: 'Ada')
81
+ # mash.extend(Hashie::Extensions::Mash::DefineAccessors)
82
+ # mash.name # => 'Ada'
47
83
  module Mash
48
84
  autoload :KeepOriginalKeys, 'hashie/extensions/mash/keep_original_keys'
49
85
  autoload :PermissiveRespondTo, 'hashie/extensions/mash/permissive_respond_to'
50
86
  autoload :SafeAssignment, 'hashie/extensions/mash/safe_assignment'
51
87
  autoload :SymbolizeKeys, 'hashie/extensions/mash/symbolize_keys'
52
88
  autoload :DefineAccessors, 'hashie/extensions/mash/define_accessors'
89
+ autoload :StrictKeyAccess, 'hashie/extensions/mash/strict_key_access'
90
+ autoload :RequiredKeys, 'hashie/extensions/mash/required_keys'
91
+ autoload :TransformKeys, 'hashie/extensions/mash/transform_keys'
92
+ autoload :UnderscoreKeys, 'hashie/extensions/mash/underscore_keys'
53
93
  end
54
94
 
95
+ # Optional extensions for arrays.
96
+ #
97
+ # @example Add Hashie-style inspection to an array
98
+ # klass = Class.new(::Array) { include Hashie::Extensions::Array::PrettyInspect }
99
+ # klass.new([1, 2]).inspect # => "#<#{klass} [1, 2]>"
55
100
  module Array
56
101
  autoload :PrettyInspect, 'hashie/extensions/array/pretty_inspect'
57
102
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hashie
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.0
4
+ version: 5.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Bleigh
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2025-12-24 00:00:00.000000000 Z
12
+ date: 2026-10-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: logger
@@ -25,20 +25,6 @@ dependencies:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
27
  version: '0'
28
- - !ruby/object:Gem::Dependency
29
- name: bundler
30
- requirement: !ruby/object:Gem::Requirement
31
- requirements:
32
- - - ">="
33
- - !ruby/object:Gem::Version
34
- version: '0'
35
- type: :development
36
- prerelease: false
37
- version_requirements: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - ">="
40
- - !ruby/object:Gem::Version
41
- version: '0'
42
28
  description: Hashie is a collection of classes and mixins that make hashes more powerful.
43
29
  email:
44
30
  - michael@intridea.com
@@ -61,27 +47,38 @@ files:
61
47
  - lib/hashie/dash.rb
62
48
  - lib/hashie/extensions/active_support/core_ext/hash.rb
63
49
  - lib/hashie/extensions/array/pretty_inspect.rb
50
+ - lib/hashie/extensions/block_access.rb
64
51
  - lib/hashie/extensions/coercion.rb
65
52
  - lib/hashie/extensions/dash/coercion.rb
66
53
  - lib/hashie/extensions/dash/indifferent_access.rb
54
+ - lib/hashie/extensions/dash/lazy.rb
67
55
  - lib/hashie/extensions/dash/predefined_values.rb
68
56
  - lib/hashie/extensions/dash/property_translation.rb
69
57
  - lib/hashie/extensions/deep_fetch.rb
70
58
  - lib/hashie/extensions/deep_find.rb
59
+ - lib/hashie/extensions/deep_grep.rb
71
60
  - lib/hashie/extensions/deep_locate.rb
72
61
  - lib/hashie/extensions/deep_merge.rb
62
+ - lib/hashie/extensions/deep_merge_initializer.rb
73
63
  - lib/hashie/extensions/ignore_undeclared.rb
74
64
  - lib/hashie/extensions/indifferent_access.rb
65
+ - lib/hashie/extensions/kernel_coercion.rb
75
66
  - lib/hashie/extensions/key_conflict_warning.rb
76
67
  - lib/hashie/extensions/key_conversion.rb
77
68
  - lib/hashie/extensions/mash/define_accessors.rb
78
69
  - lib/hashie/extensions/mash/keep_original_keys.rb
79
70
  - lib/hashie/extensions/mash/permissive_respond_to.rb
71
+ - lib/hashie/extensions/mash/required_keys.rb
80
72
  - lib/hashie/extensions/mash/safe_assignment.rb
73
+ - lib/hashie/extensions/mash/strict_key_access.rb
81
74
  - lib/hashie/extensions/mash/symbolize_keys.rb
75
+ - lib/hashie/extensions/mash/transform_keys.rb
76
+ - lib/hashie/extensions/mash/underscore_keys.rb
82
77
  - lib/hashie/extensions/merge_initializer.rb
83
78
  - lib/hashie/extensions/method_access.rb
84
79
  - lib/hashie/extensions/parsers/yaml_erb_parser.rb
80
+ - lib/hashie/extensions/parsers/yaml_parser.rb
81
+ - lib/hashie/extensions/persistable.rb
85
82
  - lib/hashie/extensions/pretty_inspect.rb
86
83
  - lib/hashie/extensions/ruby_version.rb
87
84
  - lib/hashie/extensions/ruby_version_check.rb
@@ -104,6 +101,7 @@ metadata:
104
101
  changelog_uri: https://github.com/hashie/hashie/blob/master/CHANGELOG.md
105
102
  documentation_uri: https://www.rubydoc.info/gems/hashie
106
103
  source_code_uri: https://github.com/hashie/hashie
104
+ rubygems_mfa_required: 'true'
107
105
  post_install_message:
108
106
  rdoc_options: []
109
107
  require_paths:
@@ -112,7 +110,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
112
110
  requirements:
113
111
  - - ">="
114
112
  - !ruby/object:Gem::Version
115
- version: '2.7'
113
+ version: '3.0'
116
114
  required_rubygems_version: !ruby/object:Gem::Requirement
117
115
  requirements:
118
116
  - - ">="