hashie 3.4.2 → 5.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 (73) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +518 -122
  3. data/CONTRIBUTING.md +24 -7
  4. data/LICENSE +1 -1
  5. data/README.md +455 -48
  6. data/Rakefile +18 -1
  7. data/UPGRADING.md +157 -7
  8. data/hashie.gemspec +14 -7
  9. data/lib/hashie/array.rb +21 -0
  10. data/lib/hashie/clash.rb +24 -12
  11. data/lib/hashie/dash.rb +56 -31
  12. data/lib/hashie/extensions/active_support/core_ext/hash.rb +14 -0
  13. data/lib/hashie/extensions/array/pretty_inspect.rb +19 -0
  14. data/lib/hashie/extensions/coercion.rb +91 -52
  15. data/lib/hashie/extensions/dash/coercion.rb +25 -0
  16. data/lib/hashie/extensions/dash/indifferent_access.rb +30 -1
  17. data/lib/hashie/extensions/dash/predefined_values.rb +88 -0
  18. data/lib/hashie/extensions/dash/property_translation.rb +59 -30
  19. data/lib/hashie/extensions/deep_fetch.rb +5 -3
  20. data/lib/hashie/extensions/deep_find.rb +14 -5
  21. data/lib/hashie/extensions/deep_locate.rb +40 -21
  22. data/lib/hashie/extensions/deep_merge.rb +28 -10
  23. data/lib/hashie/extensions/ignore_undeclared.rb +6 -4
  24. data/lib/hashie/extensions/indifferent_access.rb +49 -8
  25. data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
  26. data/lib/hashie/extensions/mash/define_accessors.rb +90 -0
  27. data/lib/hashie/extensions/mash/keep_original_keys.rb +53 -0
  28. data/lib/hashie/extensions/mash/permissive_respond_to.rb +61 -0
  29. data/lib/hashie/extensions/mash/safe_assignment.rb +3 -1
  30. data/lib/hashie/extensions/mash/symbolize_keys.rb +38 -0
  31. data/lib/hashie/extensions/method_access.rb +77 -19
  32. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +29 -5
  33. data/lib/hashie/extensions/ruby_version.rb +60 -0
  34. data/lib/hashie/extensions/ruby_version_check.rb +21 -0
  35. data/lib/hashie/extensions/strict_key_access.rb +77 -0
  36. data/lib/hashie/extensions/stringify_keys.rb +8 -5
  37. data/lib/hashie/extensions/symbolize_keys.rb +21 -7
  38. data/lib/hashie/hash.rb +18 -11
  39. data/lib/hashie/logger.rb +18 -0
  40. data/lib/hashie/mash.rb +196 -55
  41. data/lib/hashie/railtie.rb +21 -0
  42. data/lib/hashie/rash.rb +7 -7
  43. data/lib/hashie/utils.rb +44 -0
  44. data/lib/hashie/version.rb +1 -1
  45. data/lib/hashie.rb +34 -16
  46. metadata +30 -79
  47. data/spec/hashie/clash_spec.rb +0 -48
  48. data/spec/hashie/dash_spec.rb +0 -513
  49. data/spec/hashie/extensions/autoload_spec.rb +0 -24
  50. data/spec/hashie/extensions/coercion_spec.rb +0 -625
  51. data/spec/hashie/extensions/dash/indifferent_access_spec.rb +0 -84
  52. data/spec/hashie/extensions/deep_fetch_spec.rb +0 -97
  53. data/spec/hashie/extensions/deep_find_spec.rb +0 -45
  54. data/spec/hashie/extensions/deep_locate_spec.rb +0 -124
  55. data/spec/hashie/extensions/deep_merge_spec.rb +0 -45
  56. data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -46
  57. data/spec/hashie/extensions/indifferent_access_spec.rb +0 -219
  58. data/spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb +0 -208
  59. data/spec/hashie/extensions/key_conversion_spec.rb +0 -12
  60. data/spec/hashie/extensions/mash/safe_assignment_spec.rb +0 -23
  61. data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
  62. data/spec/hashie/extensions/method_access_spec.rb +0 -184
  63. data/spec/hashie/extensions/stringify_keys_spec.rb +0 -101
  64. data/spec/hashie/extensions/symbolize_keys_spec.rb +0 -106
  65. data/spec/hashie/hash_spec.rb +0 -84
  66. data/spec/hashie/mash_spec.rb +0 -683
  67. data/spec/hashie/parsers/yaml_erb_parser_spec.rb +0 -29
  68. data/spec/hashie/rash_spec.rb +0 -77
  69. data/spec/hashie/trash_spec.rb +0 -268
  70. data/spec/hashie/version_spec.rb +0 -7
  71. data/spec/spec_helper.rb +0 -15
  72. data/spec/support/module_context.rb +0 -11
  73. data/spec/support/ruby_version.rb +0 -10
@@ -3,7 +3,9 @@ module Hashie
3
3
  module DeepMerge
4
4
  # Returns a new hash with +self+ and +other_hash+ merged recursively.
5
5
  def deep_merge(other_hash, &block)
6
- dup.deep_merge!(other_hash, &block)
6
+ copy = _deep_dup(self)
7
+ copy.extend(Hashie::Extensions::DeepMerge) unless copy.respond_to?(:deep_merge!)
8
+ copy.deep_merge!(other_hash, &block)
7
9
  end
8
10
 
9
11
  # Returns a new hash with +self+ and +other_hash+ merged recursively.
@@ -16,17 +18,33 @@ module Hashie
16
18
 
17
19
  private
18
20
 
21
+ def _deep_dup(hash)
22
+ copy = hash.dup
23
+
24
+ copy.each do |key, value|
25
+ copy[key] =
26
+ if value.is_a?(::Hash)
27
+ _deep_dup(value)
28
+ else
29
+ Hashie::Utils.safe_dup(value)
30
+ end
31
+ end
32
+
33
+ copy
34
+ end
35
+
19
36
  def _recursive_merge(hash, other_hash, &block)
20
37
  other_hash.each do |k, v|
21
- hash[k] = if hash.key?(k) && hash[k].is_a?(::Hash) && v.is_a?(::Hash)
22
- _recursive_merge(hash[k], v, &block)
23
- else
24
- if hash.key?(k) && block_given?
25
- block.call(k, hash[k], v)
26
- else
27
- v
28
- end
29
- end
38
+ hash[k] =
39
+ if hash.key?(k) && hash[k].is_a?(::Hash) && v.is_a?(::Hash)
40
+ _recursive_merge(hash[k], v, &block)
41
+ elsif v.is_a?(::Hash)
42
+ _recursive_merge({}, v, &block)
43
+ elsif hash.key?(k) && block_given?
44
+ yield(k, hash[k], v)
45
+ else
46
+ v.respond_to?(:deep_dup) ? v.deep_dup : v
47
+ end
30
48
  end
31
49
  hash
32
50
  end
@@ -30,10 +30,12 @@ module Hashie
30
30
  # p.email # => NoMethodError
31
31
  module IgnoreUndeclared
32
32
  def initialize_attributes(attributes)
33
- attributes.each_pair do |att, value|
34
- next unless self.class.property?(att) || (self.class.respond_to?(:translations) && self.class.translations.include?(att.to_sym))
35
- self[att] = value
36
- end if attributes
33
+ return unless attributes
34
+
35
+ klass = self.class
36
+ translations = klass.respond_to?(:translations) && klass.translations || []
37
+
38
+ super(attributes.select { |attr, _| klass.property?(attr) || translations.include?(attr) })
37
39
  end
38
40
 
39
41
  def property_exists?(property)
@@ -23,21 +23,26 @@ module Hashie
23
23
  # h['baz'] # => 'blip'
24
24
  #
25
25
  module IndifferentAccess
26
+ include Hashie::Extensions::RubyVersionCheck
27
+
28
+ # @api private
29
+ def self.convert_key(key)
30
+ key.to_s
31
+ end
32
+
26
33
  def self.included(base)
27
- Hashie::Extensions::Dash::IndifferentAccess::ClassMethods.tap do |extension|
28
- base.extend(extension) if base <= Hashie::Dash && !base.singleton_class.included_modules.include?(extension)
29
- end
34
+ Hashie::Extensions::Dash::IndifferentAccess.maybe_extend(base)
30
35
 
31
36
  base.class_eval do
32
37
  alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
33
38
  alias_method :[]=, :indifferent_writer
34
39
  alias_method :store, :indifferent_writer
35
- %w(default update replace fetch delete key? values_at).each do |m|
40
+ %w[default update replace fetch delete key? values_at].each do |m|
36
41
  alias_method "regular_#{m}", m unless method_defined?("regular_#{m}")
37
42
  alias_method m, "indifferent_#{m}"
38
43
  end
39
44
 
40
- %w(include? member? has_key?).each do |key_alias|
45
+ %w[include? member? has_key?].each do |key_alias|
41
46
  alias_method key_alias, :indifferent_key?
42
47
  end
43
48
 
@@ -68,15 +73,15 @@ module Hashie
68
73
  end
69
74
 
70
75
  def convert_key(key)
71
- key.to_s
76
+ IndifferentAccess.convert_key(key)
72
77
  end
73
78
 
74
79
  # Iterates through the keys and values, reconverting them to
75
80
  # their proper indifferent state. Used when IndifferentAccess
76
81
  # is injecting itself into member hashes.
77
82
  def convert!
78
- keys.each do |k|
79
- regular_writer convert_key(k), indifferent_value(regular_delete(k))
83
+ keys.each do |k| # rubocop:disable Performance/HashEachMethods
84
+ indifferent_writer k, regular_delete(k)
80
85
  end
81
86
  self
82
87
  end
@@ -133,6 +138,42 @@ module Hashie
133
138
  self
134
139
  end
135
140
 
141
+ def merge(*args)
142
+ result = super
143
+ return IndifferentAccess.inject!(result) if hash_lacking_indifference?(result)
144
+ result.convert!
145
+ end
146
+
147
+ def merge!(*)
148
+ super.convert!
149
+ end
150
+
151
+ def to_hash
152
+ {}.tap do |result|
153
+ each_pair { |key, value| result[key] = value }
154
+
155
+ if default_proc
156
+ result.default_proc = default_proc
157
+ else
158
+ result.default = default
159
+ end
160
+ end
161
+ end
162
+
163
+ with_minimum_ruby('2.5.0') do
164
+ def slice(*keys)
165
+ string_keys = keys.map { |key| convert_key(key) }
166
+ super(*string_keys)
167
+ end
168
+ end
169
+
170
+ with_minimum_ruby('3.0.0') do
171
+ def except(*keys)
172
+ string_keys = keys.map { |key| convert_key(key) }
173
+ super(*string_keys)
174
+ end
175
+ end
176
+
136
177
  protected
137
178
 
138
179
  def hash_lacking_indifference?(other)
@@ -0,0 +1,55 @@
1
+ module Hashie
2
+ module Extensions
3
+ module KeyConflictWarning
4
+ class CannotDisableMashWarnings < StandardError
5
+ def initialize
6
+ super(
7
+ 'You cannot disable warnings on the base Mash class. ' \
8
+ 'Please subclass the Mash and disable it in the subclass.'
9
+ )
10
+ end
11
+ end
12
+
13
+ # Disable the logging of warnings based on keys conflicting keys/methods
14
+ #
15
+ # @api semipublic
16
+ # @return [void]
17
+ def disable_warnings(*method_keys)
18
+ raise CannotDisableMashWarnings if self == Hashie::Mash
19
+ if method_keys.any?
20
+ disabled_warnings.concat(method_keys).tap(&:flatten!).uniq!
21
+ else
22
+ disabled_warnings.clear
23
+ end
24
+
25
+ @disable_warnings = true
26
+ end
27
+
28
+ # Checks whether this class disables warnings for conflicting keys/methods
29
+ #
30
+ # @api semipublic
31
+ # @return [Boolean]
32
+ def disable_warnings?(method_key = nil)
33
+ return disabled_warnings.include?(method_key) if disabled_warnings.any? && method_key
34
+ @disable_warnings ||= false
35
+ end
36
+
37
+ # Returns an array of methods that this class disables warnings for.
38
+ #
39
+ # @api semipublic
40
+ # @return [Boolean]
41
+ def disabled_warnings
42
+ @_disabled_warnings ||= []
43
+ end
44
+
45
+ # Inheritance hook that sets class configuration when inherited.
46
+ #
47
+ # @api semipublic
48
+ # @return [void]
49
+ def inherited(subclass)
50
+ super
51
+ subclass.disable_warnings(disabled_warnings) if disable_warnings?
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,90 @@
1
+ module Hashie
2
+ module Extensions
3
+ module Mash
4
+ module DefineAccessors
5
+ def self.included(klass)
6
+ klass.class_eval do
7
+ mod = Ext.new
8
+ include mod
9
+ end
10
+ end
11
+
12
+ def self.extended(obj)
13
+ included(obj.singleton_class)
14
+ end
15
+
16
+ class Ext < Module
17
+ def initialize
18
+ mod = self
19
+ define_method(:method_missing) do |method_name, *args, &block|
20
+ key, suffix = method_name_and_suffix(method_name)
21
+ case suffix
22
+ when '='.freeze
23
+ mod.define_writer(key, method_name)
24
+ when '?'.freeze
25
+ mod.define_predicate(key, method_name)
26
+ when '!'.freeze
27
+ mod.define_initializing_reader(key, method_name)
28
+ when '_'.freeze
29
+ mod.define_underbang_reader(key, method_name)
30
+ else
31
+ mod.define_reader(key, method_name)
32
+ end
33
+ send(method_name, *args, &block)
34
+ end
35
+ end
36
+
37
+ def define_reader(key, method_name)
38
+ define_method(method_name) do |&block|
39
+ if key? method_name
40
+ self.[](method_name, &block)
41
+ else
42
+ self.[](key, &block)
43
+ end
44
+ end
45
+ end
46
+
47
+ def define_writer(key, method_name)
48
+ define_method(method_name) do |value = nil|
49
+ if key? method_name
50
+ self.[](method_name, &proc)
51
+ else
52
+ assign_property(key, value)
53
+ end
54
+ end
55
+ end
56
+
57
+ def define_predicate(key, method_name)
58
+ define_method(method_name) do
59
+ if key? method_name
60
+ self.[](method_name, &proc)
61
+ else
62
+ !!self[key]
63
+ end
64
+ end
65
+ end
66
+
67
+ def define_initializing_reader(key, method_name)
68
+ define_method(method_name) do
69
+ if key? method_name
70
+ self.[](method_name, &proc)
71
+ else
72
+ initializing_reader(key)
73
+ end
74
+ end
75
+ end
76
+
77
+ def define_underbang_reader(key, method_name)
78
+ define_method(method_name) do
79
+ if key? method_name
80
+ self.[](key, &proc)
81
+ else
82
+ underbang_reader(key)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,53 @@
1
+ module Hashie
2
+ module Extensions
3
+ module Mash
4
+ # Overrides the indifferent access of a Mash to keep keys in the
5
+ # original format given to the Mash.
6
+ #
7
+ # @example
8
+ # class KeepingMash < Hashie::Mash
9
+ # include Hashie::Extensions::Mash::KeepOriginalKeys
10
+ # end
11
+ #
12
+ # mash = KeepingMash.new(:symbol_key => :symbol, 'string_key' => 'string')
13
+ # mash.to_hash #=> { :symbol_key => :symbol, 'string_key' => 'string' }
14
+ # mash['string_key'] == mash[:string_key] #=> true
15
+ # mash[:symbol_key] == mash['symbol_key'] #=> true
16
+ module KeepOriginalKeys
17
+ def self.included(descendant)
18
+ error_message = "#{descendant} is not a kind of Hashie::Mash"
19
+ raise ArgumentError, error_message unless descendant <= Hashie::Mash
20
+ end
21
+
22
+ private
23
+
24
+ # Converts the key when necessary to access the correct Mash key.
25
+ #
26
+ # @param [Object, String, Symbol] key the key to access.
27
+ # @return [Object] the value assigned to the key.
28
+ 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
+ end
37
+
38
+ # Converts symbol/string keys to their alternative formats, but leaves
39
+ # other keys alone.
40
+ #
41
+ # @param [Object, String, Symbol] key the key to convert.
42
+ # @return [Object, String, Symbol] the converted key.
43
+ def __convert(key)
44
+ case key
45
+ when Symbol then key.to_s
46
+ when String then key.to_sym
47
+ else key
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,61 @@
1
+ module Hashie
2
+ module Extensions
3
+ module Mash
4
+ # Allow a Mash to properly respond to everything
5
+ #
6
+ # By default, Mashes only say they respond to methods for keys that exist
7
+ # in their key set or any of the affix methods (e.g. setter, underbang,
8
+ # etc.). This causes issues when you try to use them within a
9
+ # SimpleDelegator or bind to a method for a key that is unset.
10
+ #
11
+ # This extension allows a Mash to properly respond to `respond_to?` and
12
+ # `method` for keys that have not yet been set. This enables full
13
+ # compatibility with SimpleDelegator and thunk-oriented programming.
14
+ #
15
+ # There is a trade-off with this extension: it will run slower than a
16
+ # regular Mash; insertions and initializations with keys run approximately
17
+ # 20% slower and cost approximately 19KB of memory per class that you
18
+ # make permissive.
19
+ #
20
+ # @api public
21
+ # @example Make a new, permissively responding Mash subclass
22
+ # class PermissiveMash < Hashie::Mash
23
+ # include Hashie::Extensions::Mash::PermissiveRespondTo
24
+ # end
25
+ #
26
+ # mash = PermissiveMash.new(a: 1)
27
+ # mash.respond_to? :b #=> true
28
+ module PermissiveRespondTo
29
+ # The Ruby hook for behavior when including the module
30
+ #
31
+ # @api private
32
+ # @private
33
+ # @return void
34
+ def self.included(base)
35
+ base.instance_variable_set :@_method_cache, base.instance_methods
36
+ base.define_singleton_method(:method_cache) { @_method_cache }
37
+ end
38
+
39
+ # The Ruby hook for determining what messages a class might respond to
40
+ #
41
+ # @api private
42
+ # @private
43
+ def respond_to_missing?(_method_name, _include_private = false)
44
+ true
45
+ end
46
+
47
+ private
48
+
49
+ # Override the Mash logging behavior to account for permissiveness
50
+ #
51
+ # @api private
52
+ # @private
53
+ def log_collision?(method_key)
54
+ self.class.method_cache.include?(method_key) &&
55
+ !self.class.disable_warnings?(method_key) &&
56
+ !(regular_key?(method_key) || regular_key?(method_key.to_s))
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -3,7 +3,9 @@ module Hashie
3
3
  module Mash
4
4
  module SafeAssignment
5
5
  def custom_writer(key, *args) #:nodoc:
6
- fail ArgumentError, "The property #{key} clashes with an existing method." if methods.include?(key.to_sym)
6
+ if !key?(key) && respond_to?(key, true)
7
+ raise ArgumentError, "The property #{key} clashes with an existing method."
8
+ end
7
9
  super
8
10
  end
9
11
 
@@ -0,0 +1,38 @@
1
+ module Hashie
2
+ module Extensions
3
+ module Mash
4
+ # Overrides Mash's default behavior of converting keys to strings
5
+ #
6
+ # @example
7
+ # class LazyResponse < Hashie::Mash
8
+ # include Hashie::Extensions::Mash::SymbolizeKeys
9
+ # end
10
+ #
11
+ # response = LazyResponse.new("id" => 123, "name" => "Rey").to_h
12
+ # #=> {id: 123, name: "Rey"}
13
+ #
14
+ # @api public
15
+ module SymbolizeKeys
16
+ # Hook for being included in a class
17
+ #
18
+ # @api private
19
+ # @return [void]
20
+ # @raise [ArgumentError] when the base class isn't a Mash
21
+ def self.included(base)
22
+ raise ArgumentError, "#{base} must descent from Hashie::Mash" unless base <= Hashie::Mash
23
+ end
24
+
25
+ private
26
+
27
+ # Converts a key to a symbol, if possible
28
+ #
29
+ # @api private
30
+ # @param [<K>] key the key to attempt convert to a symbol
31
+ # @return [Symbol, K]
32
+ def convert_key(key)
33
+ key.respond_to?(:to_sym) ? key.to_sym : key
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -27,7 +27,7 @@ module Hashie
27
27
  #
28
28
  # user.not_declared # => NoMethodError
29
29
  module MethodReader
30
- def respond_to?(name, include_private = false)
30
+ def respond_to_missing?(name, include_private = false)
31
31
  return true if key?(name.to_s) || key?(name.to_sym)
32
32
  super
33
33
  end
@@ -67,7 +67,7 @@ module Hashie
67
67
  # h['awesome'] # => 'sauce'
68
68
  #
69
69
  module MethodWriter
70
- def respond_to?(name, include_private = false)
70
+ def respond_to_missing?(name, include_private = false)
71
71
  return true if name.to_s =~ /=$/
72
72
  super
73
73
  end
@@ -106,17 +106,42 @@ module Hashie
106
106
  # h.def? # => false
107
107
  # h.hji? # => NoMethodError
108
108
  module MethodQuery
109
- def respond_to?(name, include_private = false)
110
- return true if name.to_s =~ /(.*)\?$/ && (key?(Regexp.last_match[1]) || key?(Regexp.last_match[1].to_sym))
111
- super
109
+ def respond_to_missing?(name, include_private = false)
110
+ if query_method?(name) && indifferent_key?(key_from_query_method(name))
111
+ true
112
+ else
113
+ super
114
+ end
112
115
  end
113
116
 
114
117
  def method_missing(name, *args)
115
- if args.empty? && name.to_s =~ /(.*)\?$/ && (key?(Regexp.last_match[1]) || key?(Regexp.last_match[1].to_sym))
116
- return self[Regexp.last_match[1]] || self[Regexp.last_match[1].to_sym]
118
+ return super unless args.empty?
119
+
120
+ if query_method?(name)
121
+ key = key_from_query_method(name)
122
+ if indifferent_key?(key)
123
+ !!(self[key] || self[key.to_sym])
124
+ else
125
+ super
126
+ end
127
+ else
128
+ super
117
129
  end
130
+ end
118
131
 
119
- super
132
+ private
133
+
134
+ def indifferent_key?(name)
135
+ name = name.to_s
136
+ key?(name) || key?(name.to_sym)
137
+ end
138
+
139
+ def key_from_query_method(query_method)
140
+ query_method.to_s[0..-2]
141
+ end
142
+
143
+ def query_method?(name)
144
+ name.to_s.end_with?('?')
120
145
  end
121
146
  end
122
147
 
@@ -131,6 +156,22 @@ module Hashie
131
156
  end
132
157
  end
133
158
 
159
+ # A module shared between MethodOverridingWriter and MethodOverridingInitializer
160
+ # to contained shared logic. This module aids in redefining existing hash methods.
161
+ module RedefineMethod
162
+ protected
163
+
164
+ def method?(name)
165
+ methods.map(&:to_s).include?(name)
166
+ end
167
+
168
+ def redefine_method(method_name)
169
+ eigenclass = class << self; self; end
170
+ eigenclass.__send__(:alias_method, "__#{method_name}", method_name)
171
+ eigenclass.__send__(:define_method, method_name, -> { self[method_name] })
172
+ end
173
+ end
174
+
134
175
  # MethodOverridingWriter gives you #key_name= shortcuts for
135
176
  # writing to your hash. It allows methods to be overridden by
136
177
  # #key_name= shortcuts and aliases those methods with two
@@ -156,6 +197,8 @@ module Hashie
156
197
  # h.__zip # => [[['awesome', 'sauce'], ['zip', 'a-dee-doo-dah']]]
157
198
  #
158
199
  module MethodOverridingWriter
200
+ include RedefineMethod
201
+
159
202
  def convert_key(key)
160
203
  key.to_s
161
204
  end
@@ -180,16 +223,6 @@ module Hashie
180
223
  def already_overridden?(name)
181
224
  method?("__#{name}")
182
225
  end
183
-
184
- def method?(name)
185
- methods.map(&:to_s).include?(name)
186
- end
187
-
188
- def redefine_method(method_name)
189
- eigenclass = class << self; self; end
190
- eigenclass.__send__(:alias_method, "__#{method_name}", method_name)
191
- eigenclass.__send__(:define_method, method_name, -> { self[method_name] })
192
- end
193
226
  end
194
227
 
195
228
  # A macro module that will automatically include MethodReader,
@@ -200,10 +233,35 @@ module Hashie
200
233
  # underscores.
201
234
  module MethodAccessWithOverride
202
235
  def self.included(base)
203
- [MethodReader, MethodOverridingWriter, MethodQuery].each do |mod|
236
+ [MethodReader, MethodOverridingWriter,
237
+ MethodQuery, MethodOverridingInitializer].each do |mod|
204
238
  base.send :include, mod
205
239
  end
206
240
  end
207
241
  end
242
+
243
+ # MethodOverridingInitializer allows you to override default hash
244
+ # methods when passing in values from an existing hash. The overriden
245
+ # methods are aliased with two leading underscores.
246
+ #
247
+ # @example
248
+ # class MyHash < Hash
249
+ # include Hashie::Extensions::MethodOverridingInitializer
250
+ # end
251
+ #
252
+ # h = MyHash.new(zip: 'a-dee-doo-dah')
253
+ # h.zip # => 'a-dee-doo-dah'
254
+ # h.__zip # => [[['zip', 'a-dee-doo-dah']]]
255
+ module MethodOverridingInitializer
256
+ include RedefineMethod
257
+
258
+ def initialize(hash = {})
259
+ hash.each do |key, value|
260
+ skey = key.to_s
261
+ redefine_method(skey) if method?(skey)
262
+ self[skey] = value
263
+ end
264
+ end
265
+ end
208
266
  end
209
267
  end
@@ -1,22 +1,46 @@
1
1
  require 'yaml'
2
2
  require 'erb'
3
+ require 'pathname'
4
+
3
5
  module Hashie
4
6
  module Extensions
5
7
  module Parsers
6
8
  class YamlErbParser
7
- def initialize(file_path)
9
+ def initialize(file_path, options = {})
8
10
  @content = File.read(file_path)
9
- @file_path = file_path
11
+ @file_path = file_path.is_a?(Pathname) ? file_path.to_s : file_path
12
+ @options = options
10
13
  end
11
14
 
12
15
  def perform
13
16
  template = ERB.new(@content)
14
17
  template.filename = @file_path
15
- YAML.load template.result
18
+ permitted_classes = @options.fetch(:permitted_classes) { [] }
19
+ permitted_symbols = @options.fetch(:permitted_symbols) { [] }
20
+ aliases = @options.fetch(:aliases) { true }
21
+
22
+ yaml_safe_load(template, permitted_classes, permitted_symbols, aliases)
16
23
  end
17
24
 
18
- def self.perform(file_path)
19
- new(file_path).perform
25
+ def self.perform(file_path, options = {})
26
+ new(file_path, options).perform
27
+ end
28
+
29
+ private
30
+
31
+ if Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0') # Ruby 2.6+
32
+ def yaml_safe_load(template, permitted_classes, permitted_symbols, aliases)
33
+ YAML.safe_load(
34
+ template.result,
35
+ permitted_classes: permitted_classes,
36
+ permitted_symbols: permitted_symbols,
37
+ aliases: aliases
38
+ )
39
+ end
40
+ else
41
+ def yaml_safe_load(template, permitted_classes, permitted_symbols, aliases)
42
+ YAML.safe_load(template.result, permitted_classes, permitted_symbols, aliases)
43
+ end
20
44
  end
21
45
  end
22
46
  end