hashie 4.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +138 -53
  3. data/CONTRIBUTING.md +38 -1
  4. data/LICENSE +1 -1
  5. data/README.md +920 -205
  6. data/Rakefile +32 -1
  7. data/UPGRADING.md +56 -0
  8. data/hashie.gemspec +10 -5
  9. data/lib/hashie/array.rb +15 -12
  10. data/lib/hashie/clash.rb +59 -4
  11. data/lib/hashie/dash.rb +160 -32
  12. data/lib/hashie/extensions/active_support/core_ext/hash.rb +38 -0
  13. data/lib/hashie/extensions/array/pretty_inspect.rb +20 -0
  14. data/lib/hashie/extensions/block_access.rb +71 -0
  15. data/lib/hashie/extensions/coercion.rb +180 -26
  16. data/lib/hashie/extensions/dash/coercion.rb +34 -3
  17. data/lib/hashie/extensions/dash/indifferent_access.rb +90 -3
  18. data/lib/hashie/extensions/dash/lazy.rb +170 -0
  19. data/lib/hashie/extensions/dash/predefined_values.rb +148 -0
  20. data/lib/hashie/extensions/dash/property_translation.rb +138 -15
  21. data/lib/hashie/extensions/deep_fetch.rb +21 -9
  22. data/lib/hashie/extensions/deep_find.rb +20 -0
  23. data/lib/hashie/extensions/deep_grep.rb +69 -0
  24. data/lib/hashie/extensions/deep_locate.rb +16 -29
  25. data/lib/hashie/extensions/deep_merge.rb +16 -0
  26. data/lib/hashie/extensions/deep_merge_initializer.rb +42 -0
  27. data/lib/hashie/extensions/ignore_undeclared.rb +24 -7
  28. data/lib/hashie/extensions/indifferent_access.rb +124 -5
  29. data/lib/hashie/extensions/kernel_coercion.rb +58 -0
  30. data/lib/hashie/extensions/key_conflict_warning.rb +39 -4
  31. data/lib/hashie/extensions/key_conversion.rb +9 -0
  32. data/lib/hashie/extensions/mash/define_accessors.rb +71 -8
  33. data/lib/hashie/extensions/mash/keep_original_keys.rb +13 -9
  34. data/lib/hashie/extensions/mash/permissive_respond_to.rb +2 -0
  35. data/lib/hashie/extensions/mash/required_keys.rb +111 -0
  36. data/lib/hashie/extensions/mash/safe_assignment.rb +22 -4
  37. data/lib/hashie/extensions/mash/strict_key_access.rb +65 -0
  38. data/lib/hashie/extensions/mash/symbolize_keys.rb +11 -7
  39. data/lib/hashie/extensions/mash/transform_keys.rb +86 -0
  40. data/lib/hashie/extensions/mash/underscore_keys.rb +32 -0
  41. data/lib/hashie/extensions/merge_initializer.rb +9 -0
  42. data/lib/hashie/extensions/method_access.rb +94 -8
  43. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +28 -35
  44. data/lib/hashie/extensions/parsers/yaml_parser.rb +81 -0
  45. data/lib/hashie/extensions/persistable.rb +137 -0
  46. data/lib/hashie/extensions/pretty_inspect.rb +14 -0
  47. data/lib/hashie/extensions/ruby_version.rb +18 -1
  48. data/lib/hashie/extensions/ruby_version_check.rb +17 -0
  49. data/lib/hashie/extensions/strict_key_access.rb +59 -5
  50. data/lib/hashie/extensions/stringify_keys.rb +72 -17
  51. data/lib/hashie/extensions/symbolize_keys.rb +84 -17
  52. data/lib/hashie/hash.rb +18 -2
  53. data/lib/hashie/logger.rb +18 -1
  54. data/lib/hashie/mash.rb +314 -82
  55. data/lib/hashie/railtie.rb +27 -1
  56. data/lib/hashie/rash.rb +64 -4
  57. data/lib/hashie/trash.rb +8 -0
  58. data/lib/hashie/utils.rb +17 -6
  59. data/lib/hashie/version.rb +3 -1
  60. data/lib/hashie.rb +46 -0
  61. metadata +22 -9
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hashie
2
4
  module Extensions
3
5
  # IndifferentAccess gives you the ability to not care
@@ -23,6 +25,13 @@ module Hashie
23
25
  # h['baz'] # => 'blip'
24
26
  #
25
27
  module IndifferentAccess
28
+ # @api private
29
+ def self.convert_key(key)
30
+ return key.name if key.is_a?(Symbol)
31
+
32
+ key.to_s
33
+ end
34
+
26
35
  def self.included(base)
27
36
  Hashie::Extensions::Dash::IndifferentAccess.maybe_extend(base)
28
37
 
@@ -54,6 +63,12 @@ module Hashie
54
63
  # This will inject indifferent access into an instance of
55
64
  # a hash without modifying the actual class. This is what
56
65
  # allows IndifferentAccess to spread to sub-hashes.
66
+ #
67
+ # @example Convert an existing hash in place
68
+ # hash = { user: { name: 'Ada' } }
69
+ # Hashie::Extensions::IndifferentAccess.inject!(hash)
70
+ # hash['user'][:name] # => 'Ada'
71
+ # hash.class # => Hash
57
72
  def self.inject!(hash)
58
73
  (class << hash; self; end).send :include, IndifferentAccess
59
74
  hash.convert!
@@ -61,24 +76,44 @@ module Hashie
61
76
 
62
77
  # Injects indifferent access into a duplicate of the hash
63
78
  # provided. See #inject!
79
+ #
80
+ # @example Leave the original hash's top-level keys unchanged
81
+ # original = { name: 'Ada' }
82
+ # hash = Hashie::Extensions::IndifferentAccess.inject(original)
83
+ # hash['name'] # => 'Ada'
84
+ # original # => { name: 'Ada' }
64
85
  def self.inject(hash)
65
86
  inject!(hash.dup)
66
87
  end
67
88
 
89
+ # @example Normalize symbol keys
90
+ # hash = Hashie::Extensions::IndifferentAccess.inject({})
91
+ # hash.convert_key(:name) # => 'name'
68
92
  def convert_key(key)
69
- key.to_s
93
+ IndifferentAccess.convert_key(key)
70
94
  end
71
95
 
72
96
  # Iterates through the keys and values, reconverting them to
73
97
  # their proper indifferent state. Used when IndifferentAccess
74
98
  # is injecting itself into member hashes.
99
+ #
100
+ # @example Normalize keys inserted through the original writer
101
+ # hash = Hashie::Extensions::IndifferentAccess.inject({})
102
+ # hash.regular_writer(:name, 'Ada')
103
+ # hash.convert!
104
+ # hash.keys # => ['name']
105
+ # hash[:name] # => 'Ada'
75
106
  def convert!
76
107
  keys.each do |k| # rubocop:disable Performance/HashEachMethods
77
- regular_writer convert_key(k), indifferent_value(regular_delete(k))
108
+ indifferent_writer k, regular_delete(k)
78
109
  end
79
110
  self
80
111
  end
81
112
 
113
+ # @example Make hashes nested in arrays indifferent
114
+ # hash = Hashie::Extensions::IndifferentAccess.inject({})
115
+ # value = hash.indifferent_value([{ name: 'Ada' }])
116
+ # value.first['name'] # => 'Ada'
82
117
  def indifferent_value(value)
83
118
  if hash_lacking_indifference?(value)
84
119
  IndifferentAccess.inject!(value)
@@ -89,58 +124,142 @@ module Hashie
89
124
  end
90
125
  end
91
126
 
127
+ # @example Return an existing value or the configured default
128
+ # hash = Hashie::Extensions::IndifferentAccess.inject({ name: 'Ada' })
129
+ # hash.default = 'unknown'
130
+ # hash.indifferent_default(:name) # => 'Ada'
131
+ # hash.indifferent_default(:missing) # => 'unknown'
92
132
  def indifferent_default(key = nil)
93
133
  return self[convert_key(key)] if key?(key)
134
+
94
135
  regular_default(key)
95
136
  end
96
137
 
138
+ # @example Normalize incoming keys and nested values
139
+ # hash = Hashie::Extensions::IndifferentAccess.inject({})
140
+ # hash.indifferent_update(user: { name: 'Ada' })
141
+ # hash['user']['name'] # => 'Ada'
97
142
  def indifferent_update(other_hash)
98
143
  return regular_update(other_hash) if hash_with_indifference?(other_hash)
144
+
99
145
  other_hash.each_pair do |k, v|
100
146
  self[k] = v
101
147
  end
102
148
  end
103
149
 
150
+ # @example Store a symbol key as a string
151
+ # hash = Hashie::Extensions::IndifferentAccess.inject({})
152
+ # hash.indifferent_writer(:name, 'Ada')
153
+ # hash # => { 'name' => 'Ada' }
104
154
  def indifferent_writer(key, value)
105
155
  regular_writer convert_key(key), indifferent_value(value)
106
156
  end
107
157
 
108
- def indifferent_fetch(key, *args, &block)
109
- regular_fetch convert_key(key), *args, &block
158
+ # @example Fetch with either key type and support fallbacks
159
+ # hash = Hashie::Extensions::IndifferentAccess.inject({ name: 'Ada' })
160
+ # hash.indifferent_fetch(:name) # => 'Ada'
161
+ # hash.indifferent_fetch(:missing, 'unknown') # => 'unknown'
162
+ def indifferent_fetch(key, ...)
163
+ regular_fetch(convert_key(key), ...)
110
164
  end
111
165
 
166
+ # @example Delete a string key through a symbol
167
+ # hash = Hashie::Extensions::IndifferentAccess.inject({ name: 'Ada' })
168
+ # hash.indifferent_delete(:name) # => 'Ada'
169
+ # hash # => {}
112
170
  def indifferent_delete(key)
113
171
  regular_delete convert_key(key)
114
172
  end
115
173
 
174
+ # @example Recognize both key representations
175
+ # hash = Hashie::Extensions::IndifferentAccess.inject({ name: 'Ada' })
176
+ # hash.indifferent_key?(:name) # => true
177
+ # hash.indifferent_key?('name') # => true
116
178
  def indifferent_key?(key)
117
179
  regular_key? convert_key(key)
118
180
  end
119
181
 
182
+ # @example Retrieve values using mixed key types
183
+ # hash = Hashie::Extensions::IndifferentAccess.inject({ name: 'Ada', age: 42 })
184
+ # hash.indifferent_values_at(:name, 'age', :missing) # => ['Ada', 42, nil]
120
185
  def indifferent_values_at(*indices)
121
186
  indices.map { |i| self[i] }
122
187
  end
123
188
 
189
+ # @example Identify an indifferent hash
190
+ # hash = Hashie::Extensions::IndifferentAccess.inject({})
191
+ # hash.indifferent_access? # => true
124
192
  def indifferent_access?
125
193
  true
126
194
  end
127
195
 
196
+ # @example Replace existing data and convert incoming keys
197
+ # hash = Hashie::Extensions::IndifferentAccess.inject({ old: true })
198
+ # hash.indifferent_replace(name: 'Ada')
199
+ # hash # => { 'name' => 'Ada' }
128
200
  def indifferent_replace(other_hash)
129
201
  (keys - other_hash.keys).each { |key| delete(key) }
130
202
  other_hash.each { |key, value| self[key] = value }
131
203
  self
132
204
  end
133
205
 
206
+ # @example Merge without changing the original
207
+ # hash = Hashie::Extensions::IndifferentAccess.inject({ name: 'Ada' })
208
+ # merged = hash.merge(age: 42)
209
+ # merged[:age] # => 42
210
+ # merged.indifferent_access? # => true
211
+ # hash.key?(:age) # => false
134
212
  def merge(*args)
135
213
  result = super
136
- IndifferentAccess.inject!(result) if hash_lacking_indifference?(result)
214
+ return IndifferentAccess.inject!(result) if hash_lacking_indifference?(result)
215
+
137
216
  result.convert!
138
217
  end
139
218
 
219
+ # @example Merge and normalize in place
220
+ # hash = Hashie::Extensions::IndifferentAccess.inject({ name: 'Ada' })
221
+ # hash.merge!(age: 42)
222
+ # hash['age'] # => 42
223
+ # hash.keys.sort # => ['age', 'name']
140
224
  def merge!(*)
141
225
  super.convert!
142
226
  end
143
227
 
228
+ # @example Export a regular hash, preserving its default
229
+ # hash = Hashie::Extensions::IndifferentAccess.inject({ name: 'Ada' })
230
+ # hash.default = 'unknown'
231
+ # plain = hash.to_hash
232
+ # plain # => { 'name' => 'Ada' }
233
+ # plain[:name] # => 'unknown'
234
+ # plain.respond_to?(:indifferent_access?) # => false
235
+ def to_hash
236
+ {}.tap do |result|
237
+ each_pair { |key, value| result[key] = value }
238
+
239
+ if default_proc
240
+ result.default_proc = default_proc
241
+ else
242
+ result.default = default
243
+ end
244
+ end
245
+ end
246
+
247
+ # @example Select keys using either representation
248
+ # hash = Hashie::Extensions::IndifferentAccess.inject({ name: 'Ada', age: 42 })
249
+ # hash.slice(:name) # => { 'name' => 'Ada' }
250
+ def slice(*keys)
251
+ string_keys = keys.map { |key| convert_key(key) }
252
+ super(*string_keys)
253
+ end
254
+
255
+ # @example Exclude keys using either representation
256
+ # hash = Hashie::Extensions::IndifferentAccess.inject({ name: 'Ada', age: 42 })
257
+ # hash.except(:age) # => { 'name' => 'Ada' }
258
+ def except(*keys)
259
+ string_keys = keys.map { |key| convert_key(key) }
260
+ super(*string_keys)
261
+ end
262
+
144
263
  protected
145
264
 
146
265
  def hash_lacking_indifference?(other)
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hashie
4
+ module Extensions
5
+ # @example Opt in to strict numeric coercion
6
+ # klass = Class.new(Hash) do
7
+ # include Hashie::Extensions::Coercion
8
+ # extend Hashie::Extensions::KernelCoercion
9
+ # coerce_key :age, Integer
10
+ # end
11
+ # hash = klass.new
12
+ # hash[:age] = '42'
13
+ # hash[:age] # => 42
14
+ # expect { hash[:age] = 'bad' }.to raise_error(Hashie::CoercionError)
15
+ module KernelCoercion
16
+ # Core types whose values can be coerced with a stricter
17
+ # +Kernel+ method (e.g. +Kernel#Integer+) instead of the more
18
+ # lenient +#to_i+-style conversion methods used by default.
19
+ #
20
+ # NOTE: There is no strict Kernel method equivalent for +Symbol+, so
21
+ # +Symbol+ coercion is left to the default, lenient +#to_sym+ behavior.
22
+ STRICT_CORE_TYPES = {
23
+ Integer => :Integer,
24
+ Float => :Float,
25
+ Complex => :Complex,
26
+ Rational => :Rational,
27
+ String => :String
28
+ }.freeze
29
+
30
+ # Overrides Hashie::Extensions::Coercion::ClassMethods#build_core_type_coercion
31
+ # to use a stricter Kernel method (e.g. Kernel#Integer) for coercing core
32
+ # types, instead of the default, more lenient conversion method (e.g. #to_i).
33
+ #
34
+ # "abcd".to_i #=> 0
35
+ # Integer("abcd") #=> ArgumentError: invalid value for Integer(): "abcd"
36
+ #
37
+ # @example Build a strict conversion and preserve Symbol fallback
38
+ # klass = Class.new(Hash) do
39
+ # include Hashie::Extensions::Coercion
40
+ # extend Hashie::Extensions::KernelCoercion
41
+ # end
42
+ # conversion = klass.build_core_type_coercion(Integer)
43
+ # conversion.call('42') # => 42
44
+ # expect { conversion.call('42 items') }.to raise_error(ArgumentError)
45
+ # klass.build_core_type_coercion(Symbol).call('name') # => :name
46
+ def build_core_type_coercion(type)
47
+ return super unless STRICT_CORE_TYPES.key?(type)
48
+
49
+ kernel_method = STRICT_CORE_TYPES[type]
50
+ lambda do |value|
51
+ next value if value.is_a?(type)
52
+
53
+ Kernel.public_send(kernel_method, value)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -1,11 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hashie
2
4
  module Extensions
5
+ # @example Disable conflict warnings on a Mash subclass
6
+ # klass = Class.new(Hashie::Mash)
7
+ # klass.disable_warnings(:size)
8
+ # klass.disable_warnings?(:size) # => true
9
+ # klass.disable_warnings?(:keys) # => false
3
10
  module KeyConflictWarning
11
+ # @example Explain why base Mash warnings cannot be disabled
12
+ # require 'hashie/mash'
13
+ # error = Hashie::Extensions::KeyConflictWarning::CannotDisableMashWarnings.new
14
+ # error.message.include?('please subclass the Mash') # => true
4
15
  class CannotDisableMashWarnings < StandardError
16
+ # @example Construct the warning configuration error
17
+ # require 'hashie/mash'
18
+ # error = Hashie::Extensions::KeyConflictWarning::CannotDisableMashWarnings.new
19
+ # error.message # => 'you cannot disable warnings on the base Mash class; please subclass the Mash and disable it in the subclass'
5
20
  def initialize
6
21
  super(
7
- 'You cannot disable warnings on the base Mash class. ' \
8
- 'Please subclass the Mash and disable it in the subclass.'
22
+ 'you cannot disable warnings on the base Mash class; ' \
23
+ 'please subclass the Mash and disable it in the subclass'
9
24
  )
10
25
  end
11
26
  end
@@ -14,8 +29,13 @@ module Hashie
14
29
  #
15
30
  # @api semipublic
16
31
  # @return [void]
32
+ # @example Disable warnings for selected methods
33
+ # klass = Class.new(Hashie::Mash)
34
+ # klass.disable_warnings(:size, :keys)
35
+ # klass.disabled_warnings # => [:size, :keys]
17
36
  def disable_warnings(*method_keys)
18
37
  raise CannotDisableMashWarnings if self == Hashie::Mash
38
+
19
39
  if method_keys.any?
20
40
  disabled_warnings.concat(method_keys).tap(&:flatten!).uniq!
21
41
  else
@@ -29,23 +49,38 @@ module Hashie
29
49
  #
30
50
  # @api semipublic
31
51
  # @return [Boolean]
52
+ # @example Disable all warnings when no methods are specified
53
+ # klass = Class.new(Hashie::Mash)
54
+ # klass.disable_warnings
55
+ # klass.disable_warnings?(:size) # => true
32
56
  def disable_warnings?(method_key = nil)
33
57
  return disabled_warnings.include?(method_key) if disabled_warnings.any? && method_key
58
+
34
59
  @disable_warnings ||= false
35
60
  end
36
61
 
37
62
  # Returns an array of methods that this class disables warnings for.
38
63
  #
39
64
  # @api semipublic
40
- # @return [Boolean]
65
+ # @return [Array<Symbol>]
66
+ # @example Inspect selected warning exclusions
67
+ # klass = Class.new(Hashie::Mash)
68
+ # klass.disable_warnings(:size)
69
+ # klass.disabled_warnings # => [:size]
41
70
  def disabled_warnings
42
- @_disabled_warnings ||= []
71
+ @disabled_warnings ||= []
43
72
  end
44
73
 
45
74
  # Inheritance hook that sets class configuration when inherited.
46
75
  #
47
76
  # @api semipublic
48
77
  # @return [void]
78
+ # @example Inherit selected warning settings
79
+ # parent = Class.new(Hashie::Mash)
80
+ # parent.disable_warnings(:size)
81
+ # child = Class.new(parent)
82
+ # child.disabled_warnings # => [:size]
83
+ # child.disable_warnings?(:keys) # => false
49
84
  def inherited(subclass)
50
85
  super
51
86
  subclass.disable_warnings(disabled_warnings) if disable_warnings?
@@ -1,5 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hashie
2
4
  module Extensions
5
+ # @example Include both key conversion extensions
6
+ # class ConvertibleHash < Hash
7
+ # include Hashie::Extensions::KeyConversion
8
+ # end
9
+ # hash = ConvertibleHash[:name, 'Ada']
10
+ # hash.stringify_keys # => { 'name' => 'Ada' }
11
+ # hash.stringify_keys!.symbolize_keys # => { name: 'Ada' }
3
12
  module KeyConversion
4
13
  def self.included(base)
5
14
  base.send :include, SymbolizeKeys
@@ -1,7 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hashie
2
4
  module Extensions
3
5
  module Mash
6
+ # Generates reusable methods as Mash properties are accessed.
7
+ #
8
+ # @example
9
+ # klass = Class.new(Hashie::Mash) do
10
+ # include Hashie::Extensions::Mash::DefineAccessors
11
+ # end
12
+ # klass.new(name: 'Ada').name # => 'Ada'
4
13
  module DefineAccessors
14
+ # @example Install generated accessors on a subclass
15
+ # klass = Class.new(Hashie::Mash)
16
+ # klass.include(Hashie::Extensions::Mash::DefineAccessors)
17
+ # klass.new(quantity: 2).quantity # => 2
5
18
  def self.included(klass)
6
19
  klass.class_eval do
7
20
  mod = Ext.new
@@ -9,23 +22,40 @@ module Hashie
9
22
  end
10
23
  end
11
24
 
25
+ # @example Install generated accessors on one Mash
26
+ # mash = Hashie::Mash.new(quantity: 2)
27
+ # mash.extend(Hashie::Extensions::Mash::DefineAccessors)
28
+ # mash.quantity # => 2
12
29
  def self.extended(obj)
13
30
  included(obj.singleton_class)
14
31
  end
15
32
 
33
+ # A module of lazily generated accessors for a Mash class.
34
+ #
35
+ # @example
36
+ # accessors = Hashie::Extensions::Mash::DefineAccessors::Ext.new
37
+ # klass = Class.new(Hashie::Mash) { include accessors }
38
+ # mash = klass.new(name: 'Ada')
39
+ # accessors.define_reader('name', :name)
40
+ # mash.name # => 'Ada'
16
41
  class Ext < Module
42
+ # @example Generate a method on first access
43
+ # accessors = Hashie::Extensions::Mash::DefineAccessors::Ext.new
44
+ # klass = Class.new(Hashie::Mash) { include accessors }
45
+ # klass.new(name: 'Ada').name # => 'Ada'
46
+ # accessors.instance_methods(false).include?(:name) # => true
17
47
  def initialize
18
48
  mod = self
19
49
  define_method(:method_missing) do |method_name, *args, &block|
20
50
  key, suffix = method_name_and_suffix(method_name)
21
51
  case suffix
22
- when '='.freeze
52
+ when '='
23
53
  mod.define_writer(key, method_name)
24
- when '?'.freeze
54
+ when '?'
25
55
  mod.define_predicate(key, method_name)
26
- when '!'.freeze
56
+ when '!'
27
57
  mod.define_initializing_reader(key, method_name)
28
- when '_'.freeze
58
+ when '_'
29
59
  mod.define_underbang_reader(key, method_name)
30
60
  else
31
61
  mod.define_reader(key, method_name)
@@ -34,6 +64,12 @@ module Hashie
34
64
  end
35
65
  end
36
66
 
67
+ # @example
68
+ # accessors = Hashie::Extensions::Mash::DefineAccessors::Ext.new
69
+ # klass = Class.new(Hashie::Mash) { include accessors }
70
+ # mash = klass.new(name: 'Ada')
71
+ # accessors.define_reader('name', :name)
72
+ # mash.name # => 'Ada'
37
73
  def define_reader(key, method_name)
38
74
  define_method(method_name) do |&block|
39
75
  if key? method_name
@@ -44,40 +80,67 @@ module Hashie
44
80
  end
45
81
  end
46
82
 
83
+ # @example
84
+ # accessors = Hashie::Extensions::Mash::DefineAccessors::Ext.new
85
+ # accessors.define_writer('name', :name=)
86
+ # klass = Class.new(Hashie::Mash) { include accessors }
87
+ # mash = klass.new
88
+ # mash.name = 'Ada'
89
+ # mash['name'] # => 'Ada'
47
90
  def define_writer(key, method_name)
48
91
  define_method(method_name) do |value = nil|
49
92
  if key? method_name
50
- self.[](method_name, &proc)
93
+ self.[](method_name)
51
94
  else
52
95
  assign_property(key, value)
53
96
  end
54
97
  end
55
98
  end
56
99
 
100
+ # @example
101
+ # accessors = Hashie::Extensions::Mash::DefineAccessors::Ext.new
102
+ # accessors.define_predicate('name', :name?)
103
+ # klass = Class.new(Hashie::Mash) { include accessors }
104
+ # klass.new(name: 'Ada').name? # => true
105
+ # klass.new.name? # => false
57
106
  def define_predicate(key, method_name)
58
107
  define_method(method_name) do
59
108
  if key? method_name
60
- self.[](method_name, &proc)
109
+ self.[](method_name)
61
110
  else
62
111
  !!self[key]
63
112
  end
64
113
  end
65
114
  end
66
115
 
116
+ # @example
117
+ # accessors = Hashie::Extensions::Mash::DefineAccessors::Ext.new
118
+ # accessors.define_initializing_reader('address', :address!)
119
+ # klass = Class.new(Hashie::Mash) { include accessors }
120
+ # mash = klass.new
121
+ # mash.address!.city = 'London'
122
+ # mash.address.city # => 'London'
67
123
  def define_initializing_reader(key, method_name)
68
124
  define_method(method_name) do
69
125
  if key? method_name
70
- self.[](method_name, &proc)
126
+ self.[](method_name)
71
127
  else
72
128
  initializing_reader(key)
73
129
  end
74
130
  end
75
131
  end
76
132
 
133
+ # @example
134
+ # accessors = Hashie::Extensions::Mash::DefineAccessors::Ext.new
135
+ # accessors.define_underbang_reader('address', :address_)
136
+ # klass = Class.new(Hashie::Mash) { include accessors }
137
+ # mash = klass.new
138
+ # mash.address_.city = 'London'
139
+ # mash.key?('address') # => false
77
140
  def define_underbang_reader(key, method_name)
78
141
  define_method(method_name) do
79
142
  if key? method_name
80
- self.[](key, &proc)
143
+ self.[](key)
81
144
  else
82
145
  underbang_reader(key)
83
146
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hashie
2
4
  module Extensions
3
5
  module Mash
@@ -14,8 +16,13 @@ module Hashie
14
16
  # mash['string_key'] == mash[:string_key] #=> true
15
17
  # mash[:symbol_key] == mash['symbol_key'] #=> true
16
18
  module KeepOriginalKeys
19
+ # @example Preserve symbol keys when including the extension
20
+ # klass = Class.new(Hashie::Mash) do
21
+ # include Hashie::Extensions::Mash::KeepOriginalKeys
22
+ # end
23
+ # klass.new(name: 'Ada').keys # => [:name]
17
24
  def self.included(descendant)
18
- error_message = "#{descendant} is not a kind of Hashie::Mash"
25
+ error_message = "`#{descendant}` is not a kind of Hashie::Mash"
19
26
  raise ArgumentError, error_message unless descendant <= Hashie::Mash
20
27
  end
21
28
 
@@ -26,13 +33,10 @@ module Hashie
26
33
  # @param [Object, String, Symbol] key the key to access.
27
34
  # @return [Object] the value assigned to the key.
28
35
  def convert_key(key)
29
- if regular_key?(key)
30
- key
31
- elsif (converted_key = __convert(key)) && regular_key?(converted_key)
32
- converted_key
33
- else
34
- key
35
- end
36
+ return key if regular_key?(key)
37
+
38
+ converted_key = __convert(key)
39
+ converted_key && regular_key?(converted_key) ? converted_key : key
36
40
  end
37
41
 
38
42
  # Converts symbol/string keys to their alternative formats, but leaves
@@ -42,7 +46,7 @@ module Hashie
42
46
  # @return [Object, String, Symbol] the converted key.
43
47
  def __convert(key)
44
48
  case key
45
- when Symbol then key.to_s
49
+ when Symbol then key.name
46
50
  when String then key.to_sym
47
51
  else key
48
52
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hashie
2
4
  module Extensions
3
5
  module Mash