hashie 3.4.2 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
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