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
@@ -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
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hashie
4
+ module Extensions
5
+ module Mash
6
+ # Extends a Mash with the ability to require specific keys to be
7
+ # set (non-nil) when constructed, while still allowing arbitrary,
8
+ # undeclared keys to be assigned - unlike Hashie::Dash, which
9
+ # restricts assignment to only pre-declared properties.
10
+ #
11
+ # @example
12
+ #
13
+ # class PersonMash < Hashie::Mash
14
+ # include Hashie::Extensions::Mash::RequiredKeys
15
+ #
16
+ # required_keys :name, :email
17
+ # end
18
+ #
19
+ # expect { PersonMash.new(email: 'bob@example.com') }
20
+ # .to raise_error(ArgumentError, 'the following keys are required: `name`')
21
+ #
22
+ # person = PersonMash.new(name: 'Bob', email: 'bob@example.com')
23
+ # person.age = 42 # arbitrary, undeclared keys are still allowed
24
+ # person.age # => 42
25
+ module RequiredKeys
26
+ # @example Install required-key declarations
27
+ # klass = Class.new(Hashie::Mash) do
28
+ # include Hashie::Extensions::Mash::RequiredKeys
29
+ # end
30
+ # klass.required_keys # => []
31
+ def self.included(base)
32
+ base.instance_variable_set(:@required_keys, [])
33
+ base.extend(ClassMethods)
34
+ base.include(InstanceMethods)
35
+ end
36
+
37
+ # Class-level required-key declarations inherited by subclasses.
38
+ #
39
+ # @example
40
+ # klass = Class.new(Hashie::Mash) do
41
+ # include Hashie::Extensions::Mash::RequiredKeys
42
+ # required_keys :name
43
+ # end
44
+ # klass.required_keys # => ['name']
45
+ module ClassMethods
46
+ # @example Copy required keys when subclassing
47
+ # parent = Class.new(Hashie::Mash) do
48
+ # include Hashie::Extensions::Mash::RequiredKeys
49
+ # required_keys :name
50
+ # end
51
+ # child = Class.new(parent)
52
+ # child.required_keys :email
53
+ # child.required_keys # => ['name', 'email']
54
+ # parent.required_keys # => ['name']
55
+ def inherited(klass)
56
+ super
57
+ klass.instance_variable_set(:@required_keys, required_keys.dup)
58
+ end
59
+
60
+ # With no arguments, returns the keys currently declared as
61
+ # required. With one or more arguments, declares those keys
62
+ # as required. Keys are stored and compared as strings, so
63
+ # both symbol and string keys are accepted.
64
+ #
65
+ # @example
66
+ # klass = Class.new(Hashie::Mash) do
67
+ # include Hashie::Extensions::Mash::RequiredKeys
68
+ # required_keys :name, 'email', :name
69
+ # end
70
+ # klass.required_keys # => ['name', 'email']
71
+ def required_keys(*keys)
72
+ return @required_keys if keys.empty?
73
+
74
+ @required_keys |= keys.map(&:to_s)
75
+ end
76
+ end
77
+
78
+ # Checks required values when constructing a Mash.
79
+ #
80
+ # @example
81
+ # klass = Class.new(Hashie::Mash) do
82
+ # include Hashie::Extensions::Mash::RequiredKeys
83
+ # required_keys :name
84
+ # end
85
+ # klass.new(name: 'Ada').name # => 'Ada'
86
+ module InstanceMethods
87
+ # @example Reject nil or missing required values
88
+ # klass = Class.new(Hashie::Mash) do
89
+ # include Hashie::Extensions::Mash::RequiredKeys
90
+ # required_keys :name
91
+ # end
92
+ # expect { klass.new(name: nil) }.to raise_error(ArgumentError, /name/)
93
+ # klass.new(name: 'Ada', age: 37).age # => 37
94
+ def initialize(*args, &block)
95
+ super
96
+ assert_required_keys_set!
97
+ end
98
+
99
+ private
100
+
101
+ def assert_required_keys_set!
102
+ missing_keys = self.class.required_keys.select { |key| self[key].nil? }
103
+ return if missing_keys.empty?
104
+
105
+ raise ArgumentError, "the following keys are required: `#{missing_keys.join(', ')}`"
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -1,14 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hashie
2
4
  module Extensions
3
5
  module Mash
6
+ # Rejects assignments that would shadow existing Mash methods.
7
+ #
8
+ # @example
9
+ # klass = Class.new(Hashie::Mash) do
10
+ # include Hashie::Extensions::Mash::SafeAssignment
11
+ # end
12
+ # mash = klass.new
13
+ # expect { mash[:size] = 2 }.to raise_error(ArgumentError, /clashes/)
14
+ # mash.empty? # => true
4
15
  module SafeAssignment
5
- def custom_writer(key, *args) #:nodoc:
6
- if !key?(key) && respond_to?(key, true)
7
- raise ArgumentError, "The property #{key} clashes with an existing method."
8
- end
16
+ def custom_writer(key, *args) # :nodoc:
17
+ raise ArgumentError, "the property `#{key}` clashes with an existing method" if !key?(key) && respond_to?(key, true)
18
+
9
19
  super
10
20
  end
11
21
 
22
+ # @example
23
+ # klass = Class.new(Hashie::Mash) do
24
+ # include Hashie::Extensions::Mash::SafeAssignment
25
+ # end
26
+ # mash = klass.new
27
+ # mash[:name] = 'Ada'
28
+ # mash.name # => 'Ada'
29
+ # expect { mash[:size] = 2 }.to raise_error(ArgumentError, /clashes/)
12
30
  def []=(*args)
13
31
  custom_writer(*args)
14
32
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hashie
4
+ module Extensions
5
+ module Mash
6
+ # Overrides Mash's default behavior of returning nil for keys that
7
+ # aren't set, raising a KeyError instead.
8
+ #
9
+ # Unlike Hashie::Extensions::StrictKeyAccess, this extension is aware
10
+ # of Mash's special suffixed methods (e.g. +?+, +!+, +_+ and +=+) so
11
+ # that they continue to work as expected instead of always raising.
12
+ #
13
+ # Because nested Hashes are converted into instances of +self.class+,
14
+ # this extension is automatically applied to any nested Mash as well.
15
+ #
16
+ # @example
17
+ # class StrictMash < Hashie::Mash
18
+ # include Hashie::Extensions::Mash::StrictKeyAccess
19
+ # end
20
+ #
21
+ # mash = StrictMash.new(name: 'Bob')
22
+ # mash.name #=> 'Bob'
23
+ # expect { mash.age }.to raise_error(KeyError, 'key not found: `age`')
24
+ #
25
+ # mash.name? #=> true
26
+ # mash.age? #=> false
27
+ #
28
+ # mash.address!.city = 'Springfield'
29
+ # mash.address.city #=> 'Springfield'
30
+ #
31
+ # @api public
32
+ module StrictKeyAccess
33
+ # Hook for being included in a class
34
+ #
35
+ # @api private
36
+ # @return [void]
37
+ # @raise [ArgumentError] when the base class isn't a Mash
38
+ def self.included(descendant)
39
+ error_message = "#{descendant} is not a kind of Hashie::Mash"
40
+ raise ArgumentError, error_message unless descendant <= Hashie::Mash
41
+ end
42
+
43
+ # @api private
44
+ def method_missing(method_name, *args, &blk)
45
+ return super if key?(method_name)
46
+
47
+ name, suffix = method_name_and_suffix(method_name)
48
+ raise KeyError, "key not found: `#{name}`" if !key?(name) && !suffix
49
+
50
+ super
51
+ end
52
+
53
+ # @api private
54
+ def respond_to_missing?(method_name, *args)
55
+ return true if key?(method_name)
56
+
57
+ _, suffix = method_name_and_suffix(method_name)
58
+ return false unless suffix
59
+
60
+ super
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hashie
2
4
  module Extensions
3
5
  module Mash
@@ -9,7 +11,7 @@ module Hashie
9
11
  # end
10
12
  #
11
13
  # response = LazyResponse.new("id" => 123, "name" => "Rey").to_h
12
- # #=> {id: 123, name: "Rey"}
14
+ # response # => {id: 123, name: "Rey"}
13
15
  #
14
16
  # @api public
15
17
  module SymbolizeKeys
@@ -19,7 +21,9 @@ module Hashie
19
21
  # @return [void]
20
22
  # @raise [ArgumentError] when the base class isn't a Mash
21
23
  def self.included(base)
22
- raise ArgumentError, "#{base} must descent from Hashie::Mash" unless base <= Hashie::Mash
24
+ return if base <= Hashie::Mash
25
+
26
+ raise ArgumentError, "`#{base}` must descend from Hashie::Mash"
23
27
  end
24
28
 
25
29
  private
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hashie
4
+ module Extensions
5
+ module Mash
6
+ # Applies a class-configured normalization to string and symbol Mash keys.
7
+ #
8
+ # @example
9
+ # klass = Class.new(Hashie::Mash) do
10
+ # include Hashie::Extensions::Mash::TransformKeys
11
+ # transform_keys_with(&:downcase)
12
+ # end
13
+ # klass.new('NAME' => 'Ada').name # => 'Ada'
14
+ module TransformKeys
15
+ # @example Install class-level transformation configuration
16
+ # klass = Class.new(Hashie::Mash) do
17
+ # include Hashie::Extensions::Mash::TransformKeys
18
+ # end
19
+ # klass.key_transformer # => nil
20
+ def self.included(base)
21
+ raise ArgumentError, "`#{base}` must descend from Hashie::Mash" unless base <= Hashie::Mash
22
+
23
+ base.extend(ClassMethods)
24
+ end
25
+
26
+ # Configures key normalization independently for each subclass.
27
+ #
28
+ # @example
29
+ # klass = Class.new(Hashie::Mash) do
30
+ # include Hashie::Extensions::Mash::TransformKeys
31
+ # transform_keys_with(&:downcase)
32
+ # end
33
+ # klass.new('NAME' => 'Ada')['name'] # => 'Ada'
34
+ module ClassMethods
35
+ # @example Inherit a transformer without sharing later configuration
36
+ # parent = Class.new(Hashie::Mash) do
37
+ # include Hashie::Extensions::Mash::TransformKeys
38
+ # transform_keys_with(&:downcase)
39
+ # end
40
+ # child = Class.new(parent)
41
+ # child.new('NAME' => 'Ada').name # => 'Ada'
42
+ # child.transform_keys_with(&:upcase)
43
+ # parent.new('NAME' => 'Ada').keys # => ['name']
44
+ def inherited(klass)
45
+ super
46
+ klass.instance_variable_set(:@key_transformer, key_transformer)
47
+ end
48
+
49
+ # @example Configure with a callable or a block
50
+ # klass = Class.new(Hashie::Mash) do
51
+ # include Hashie::Extensions::Mash::TransformKeys
52
+ # transform_keys_with(->(key) { key.downcase })
53
+ # end
54
+ # klass.new('NAME' => 'Ada').keys # => ['name']
55
+ # klass.transform_keys_with { |key| key.upcase }
56
+ # klass.new(name: 'Ada').keys # => ['NAME']
57
+ def transform_keys_with(callable = nil, &block)
58
+ raise ArgumentError, 'provide one callable or block' if (callable && block) || !(callable || block).respond_to?(:call)
59
+
60
+ @key_transformer = callable || block
61
+ end
62
+
63
+ # @example Read the configured callable
64
+ # klass = Class.new(Hashie::Mash) do
65
+ # include Hashie::Extensions::Mash::TransformKeys
66
+ # transform_keys_with(&:downcase)
67
+ # end
68
+ # klass.key_transformer.call('NAME') # => 'name'
69
+ attr_reader :key_transformer
70
+ end
71
+
72
+ private
73
+
74
+ def convert_key(key)
75
+ transformer = self.class.key_transformer
76
+ return super unless transformer && (key.is_a?(String) || key.is_a?(Symbol))
77
+
78
+ transformed = transformer.call(key.to_s)
79
+ raise TypeError, 'key transformer must return a String' unless transformed.is_a?(String)
80
+
81
+ super(transformed)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hashie/extensions/mash/transform_keys'
4
+
5
+ module Hashie
6
+ module Extensions
7
+ module Mash
8
+ # Normalizes camel-case and hyphenated Mash keys to lowercase underscores.
9
+ #
10
+ # @example
11
+ # klass = Class.new(Hashie::Mash) do
12
+ # include Hashie::Extensions::Mash::UnderscoreKeys
13
+ # end
14
+ # klass.new('firstName' => 'Ada', 'last-name' => 'Lovelace').to_h # => {'first_name' => 'Ada', 'last_name' => 'Lovelace'}
15
+ module UnderscoreKeys
16
+ # @example Configure underscore normalization on inclusion
17
+ # klass = Class.new(Hashie::Mash) do
18
+ # include Hashie::Extensions::Mash::UnderscoreKeys
19
+ # end
20
+ # klass.key_transformer.call('HTTPStatus') # => 'http_status'
21
+ def self.included(base)
22
+ base.include(TransformKeys) unless base < TransformKeys
23
+ base.transform_keys_with do |key|
24
+ key.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
25
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
26
+ .tr('-', '_').downcase
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hashie
2
4
  module Extensions
3
5
  # The MergeInitializer is a super-simple mixin that allows
@@ -15,6 +17,13 @@ module Hashie
15
17
  # h[:abc] # => 'def'
16
18
  #
17
19
  module MergeInitializer
20
+ # @example Initialize entries and a default value
21
+ # class InitializedHash < Hash
22
+ # include Hashie::Extensions::MergeInitializer
23
+ # end
24
+ # hash = InitializedHash.new({ name: 'Ada' }, 'unknown')
25
+ # hash[:name] # => 'Ada'
26
+ # hash[:missing] # => 'unknown'
18
27
  def initialize(hash = {}, default = nil, &block)
19
28
  default ? super(default) : super(&block)
20
29
  hash.each do |key, value|