hashie 3.5.7 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +281 -195
  3. data/CONTRIBUTING.md +13 -6
  4. data/LICENSE +1 -1
  5. data/README.md +320 -60
  6. data/Rakefile +2 -2
  7. data/UPGRADING.md +121 -7
  8. data/hashie.gemspec +13 -7
  9. data/lib/hashie/clash.rb +12 -1
  10. data/lib/hashie/dash.rb +56 -35
  11. data/lib/hashie/extensions/active_support/core_ext/hash.rb +14 -0
  12. data/lib/hashie/extensions/coercion.rb +26 -19
  13. data/lib/hashie/extensions/dash/indifferent_access.rb +29 -1
  14. data/lib/hashie/extensions/dash/predefined_values.rb +88 -0
  15. data/lib/hashie/extensions/dash/property_translation.rb +59 -28
  16. data/lib/hashie/extensions/deep_fetch.rb +5 -3
  17. data/lib/hashie/extensions/deep_find.rb +14 -5
  18. data/lib/hashie/extensions/deep_locate.rb +22 -8
  19. data/lib/hashie/extensions/deep_merge.rb +26 -10
  20. data/lib/hashie/extensions/ignore_undeclared.rb +4 -5
  21. data/lib/hashie/extensions/indifferent_access.rb +43 -10
  22. data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
  23. data/lib/hashie/extensions/mash/define_accessors.rb +90 -0
  24. data/lib/hashie/extensions/mash/keep_original_keys.rb +4 -5
  25. data/lib/hashie/extensions/mash/permissive_respond_to.rb +61 -0
  26. data/lib/hashie/extensions/mash/safe_assignment.rb +3 -1
  27. data/lib/hashie/extensions/mash/symbolize_keys.rb +6 -6
  28. data/lib/hashie/extensions/method_access.rb +47 -14
  29. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +28 -4
  30. data/lib/hashie/extensions/ruby_version_check.rb +5 -1
  31. data/lib/hashie/extensions/strict_key_access.rb +16 -13
  32. data/lib/hashie/extensions/stringify_keys.rb +1 -1
  33. data/lib/hashie/extensions/symbolize_keys.rb +13 -2
  34. data/lib/hashie/hash.rb +18 -11
  35. data/lib/hashie/mash.rb +147 -81
  36. data/lib/hashie/railtie.rb +7 -0
  37. data/lib/hashie/rash.rb +6 -6
  38. data/lib/hashie/utils.rb +28 -0
  39. data/lib/hashie/version.rb +1 -1
  40. data/lib/hashie.rb +22 -19
  41. metadata +23 -131
  42. data/spec/hashie/array_spec.rb +0 -29
  43. data/spec/hashie/clash_spec.rb +0 -70
  44. data/spec/hashie/dash_spec.rb +0 -573
  45. data/spec/hashie/extensions/autoload_spec.rb +0 -24
  46. data/spec/hashie/extensions/coercion_spec.rb +0 -631
  47. data/spec/hashie/extensions/dash/coercion_spec.rb +0 -13
  48. data/spec/hashie/extensions/dash/indifferent_access_spec.rb +0 -84
  49. data/spec/hashie/extensions/deep_fetch_spec.rb +0 -97
  50. data/spec/hashie/extensions/deep_find_spec.rb +0 -138
  51. data/spec/hashie/extensions/deep_locate_spec.rb +0 -137
  52. data/spec/hashie/extensions/deep_merge_spec.rb +0 -70
  53. data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -47
  54. data/spec/hashie/extensions/indifferent_access_spec.rb +0 -282
  55. data/spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb +0 -208
  56. data/spec/hashie/extensions/key_conversion_spec.rb +0 -12
  57. data/spec/hashie/extensions/mash/keep_original_keys_spec.rb +0 -46
  58. data/spec/hashie/extensions/mash/safe_assignment_spec.rb +0 -50
  59. data/spec/hashie/extensions/mash/symbolize_keys_spec.rb +0 -39
  60. data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
  61. data/spec/hashie/extensions/method_access_spec.rb +0 -188
  62. data/spec/hashie/extensions/strict_key_access_spec.rb +0 -110
  63. data/spec/hashie/extensions/stringify_keys_spec.rb +0 -124
  64. data/spec/hashie/extensions/symbolize_keys_spec.rb +0 -129
  65. data/spec/hashie/hash_spec.rb +0 -84
  66. data/spec/hashie/mash_spec.rb +0 -763
  67. data/spec/hashie/parsers/yaml_erb_parser_spec.rb +0 -46
  68. data/spec/hashie/rash_spec.rb +0 -83
  69. data/spec/hashie/trash_spec.rb +0 -268
  70. data/spec/hashie/utils_spec.rb +0 -25
  71. data/spec/hashie/version_spec.rb +0 -7
  72. data/spec/hashie_spec.rb +0 -13
  73. data/spec/integration/omniauth/app.rb +0 -11
  74. data/spec/integration/omniauth/integration_spec.rb +0 -38
  75. data/spec/integration/omniauth-oauth2/app.rb +0 -53
  76. data/spec/integration/omniauth-oauth2/integration_spec.rb +0 -26
  77. data/spec/integration/omniauth-oauth2/some_site.rb +0 -38
  78. data/spec/integration/rails/app.rb +0 -48
  79. data/spec/integration/rails/integration_spec.rb +0 -26
  80. data/spec/integration/rails-without-dependency/integration_spec.rb +0 -15
  81. data/spec/spec_helper.rb +0 -23
  82. data/spec/support/integration_specs.rb +0 -36
  83. data/spec/support/logger.rb +0 -24
  84. data/spec/support/module_context.rb +0 -11
  85. data/spec/support/ruby_version_check.rb +0 -6
@@ -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 !key?(key) && respond_to?(key, true)
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
 
@@ -5,7 +5,7 @@ module Hashie
5
5
  #
6
6
  # @example
7
7
  # class LazyResponse < Hashie::Mash
8
- # include Hashie::Extensions::Mash::SymbolizedKeys
8
+ # include Hashie::Extensions::Mash::SymbolizeKeys
9
9
  # end
10
10
  #
11
11
  # response = LazyResponse.new("id" => 123, "name" => "Rey").to_h
@@ -19,18 +19,18 @@ module Hashie
19
19
  # @return [void]
20
20
  # @raise [ArgumentError] when the base class isn't a Mash
21
21
  def self.included(base)
22
- fail ArgumentError, "#{base} must descent from Hashie::Mash" unless base <= Hashie::Mash
22
+ raise ArgumentError, "#{base} must descent from Hashie::Mash" unless base <= Hashie::Mash
23
23
  end
24
24
 
25
25
  private
26
26
 
27
- # Converts a key to a symbol
27
+ # Converts a key to a symbol, if possible
28
28
  #
29
29
  # @api private
30
- # @param [String, Symbol] key the key to convert to a symbol
31
- # @return [void]
30
+ # @param [<K>] key the key to attempt convert to a symbol
31
+ # @return [Symbol, K]
32
32
  def convert_key(key)
33
- key.to_sym
33
+ key.respond_to?(:to_sym) ? key.to_sym : key
34
34
  end
35
35
  end
36
36
  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,7 +106,7 @@ module Hashie
106
106
  # h.def? # => false
107
107
  # h.hji? # => NoMethodError
108
108
  module MethodQuery
109
- def respond_to?(name, include_private = false)
109
+ def respond_to_missing?(name, include_private = false)
110
110
  if query_method?(name) && indifferent_key?(key_from_query_method(name))
111
111
  true
112
112
  else
@@ -156,6 +156,22 @@ module Hashie
156
156
  end
157
157
  end
158
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
+
159
175
  # MethodOverridingWriter gives you #key_name= shortcuts for
160
176
  # writing to your hash. It allows methods to be overridden by
161
177
  # #key_name= shortcuts and aliases those methods with two
@@ -181,6 +197,8 @@ module Hashie
181
197
  # h.__zip # => [[['awesome', 'sauce'], ['zip', 'a-dee-doo-dah']]]
182
198
  #
183
199
  module MethodOverridingWriter
200
+ include RedefineMethod
201
+
184
202
  def convert_key(key)
185
203
  key.to_s
186
204
  end
@@ -205,16 +223,6 @@ module Hashie
205
223
  def already_overridden?(name)
206
224
  method?("__#{name}")
207
225
  end
208
-
209
- def method?(name)
210
- methods.map(&:to_s).include?(name)
211
- end
212
-
213
- def redefine_method(method_name)
214
- eigenclass = class << self; self; end
215
- eigenclass.__send__(:alias_method, "__#{method_name}", method_name)
216
- eigenclass.__send__(:define_method, method_name, -> { self[method_name] })
217
- end
218
226
  end
219
227
 
220
228
  # A macro module that will automatically include MethodReader,
@@ -225,10 +233,35 @@ module Hashie
225
233
  # underscores.
226
234
  module MethodAccessWithOverride
227
235
  def self.included(base)
228
- [MethodReader, MethodOverridingWriter, MethodQuery].each do |mod|
236
+ [MethodReader, MethodOverridingWriter,
237
+ MethodQuery, MethodOverridingInitializer].each do |mod|
229
238
  base.send :include, mod
230
239
  end
231
240
  end
232
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
233
266
  end
234
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
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
@@ -9,7 +9,11 @@ module Hashie
9
9
 
10
10
  module ClassMethods
11
11
  def with_minimum_ruby(version)
12
- yield if RubyVersion.new(RUBY_VERSION) >= RubyVersion.new(version)
12
+ yield if with_minimum_ruby?(version)
13
+ end
14
+
15
+ def with_minimum_ruby?(version)
16
+ RubyVersion.new(RUBY_VERSION) >= RubyVersion.new(version)
13
17
  end
14
18
  end
15
19
  end
@@ -1,6 +1,7 @@
1
1
  module Hashie
2
2
  module Extensions
3
- # SRP: This extension will fail an error whenever a key is accessed that does not exist in the hash.
3
+ # SRP: This extension will fail an error whenever a key is accessed
4
+ # that does not exist in the hash.
4
5
  #
5
6
  # EXAMPLE:
6
7
  #
@@ -15,12 +16,15 @@ module Hashie
15
16
  # >> hash[:cow]
16
17
  # KeyError: key not found: :cow
17
18
  #
18
- # NOTE: For googlers coming from Python to Ruby, this extension makes a Hash behave more like a "Dictionary".
19
+ # NOTE: For googlers coming from Python to Ruby, this extension makes a Hash
20
+ # behave more like a "Dictionary".
19
21
  #
20
22
  module StrictKeyAccess
21
23
  class DefaultError < StandardError
22
- def initialize(msg = 'Setting or using a default with Hashie::Extensions::StrictKeyAccess does not make sense', *args)
23
- super
24
+ def initialize
25
+ super('Setting or using a default with Hashie::Extensions::StrictKeyAccess'\
26
+ ' does not make sense'
27
+ )
24
28
  end
25
29
  end
26
30
 
@@ -46,27 +50,26 @@ module Hashie
46
50
  end
47
51
 
48
52
  def default(_ = nil)
49
- fail DefaultError
53
+ raise DefaultError
50
54
  end
51
55
 
52
56
  def default=(_)
53
- fail DefaultError
57
+ raise DefaultError
54
58
  end
55
59
 
56
60
  def default_proc
57
- fail DefaultError
61
+ raise DefaultError
58
62
  end
59
63
 
60
64
  def default_proc=(_)
61
- fail DefaultError
65
+ raise DefaultError
62
66
  end
63
67
 
64
68
  def key(value)
65
- result = super
66
- if result.nil? && (!key?(result) || self[result] != value)
67
- fail KeyError, "key not found with value of #{value.inspect}"
68
- else
69
- result
69
+ super.tap do |result|
70
+ if result.nil? && (!key?(result) || self[result] != value)
71
+ raise KeyError, "key not found with value of #{value.inspect}"
72
+ end
70
73
  end
71
74
  end
72
75
  end
@@ -44,7 +44,7 @@ module Hashie
44
44
  # test # => {'abc' => 'def'}
45
45
  def stringify_keys!(hash)
46
46
  hash.extend(Hashie::Extensions::StringifyKeys) unless hash.respond_to?(:stringify_keys!)
47
- hash.keys.each do |k|
47
+ hash.keys.each do |k| # rubocop:disable Performance/HashEachMethods
48
48
  stringify_keys_recursively!(hash[k])
49
49
  hash[k.to_s] = hash.delete(k)
50
50
  end
@@ -44,9 +44,9 @@ module Hashie
44
44
  # test # => {:abc => 'def'}
45
45
  def symbolize_keys!(hash)
46
46
  hash.extend(Hashie::Extensions::SymbolizeKeys) unless hash.respond_to?(:symbolize_keys!)
47
- hash.keys.each do |k|
47
+ hash.keys.each do |k| # rubocop:disable Performance/HashEachMethods
48
48
  symbolize_keys_recursively!(hash[k])
49
- hash[k.to_sym] = hash.delete(k)
49
+ hash[convert_key(k)] = hash.delete(k)
50
50
  end
51
51
  hash
52
52
  end
@@ -61,6 +61,17 @@ module Hashie
61
61
  symbolize_keys!(new_hash)
62
62
  end
63
63
  end
64
+
65
+ private
66
+
67
+ # Converts a key to a symbol, if possible
68
+ #
69
+ # @api private
70
+ # @param [<K>] key the key to attempt convert to a symbol
71
+ # @return [Symbol, K]
72
+ def convert_key(key)
73
+ key.respond_to?(:to_sym) ? key.to_sym : key
74
+ end
64
75
  end
65
76
 
66
77
  class << self
data/lib/hashie/hash.rb CHANGED
@@ -17,21 +17,22 @@ module Hashie
17
17
  # Converts a mash back to a hash (with stringified or symbolized keys)
18
18
  def to_hash(options = {})
19
19
  out = {}
20
- keys.each do |k|
21
- assignment_key = if options[:stringify_keys]
22
- k.to_s
23
- elsif options[:symbolize_keys]
24
- k.to_s.to_sym
25
- else
26
- k
27
- end
20
+ each_key do |k|
21
+ assignment_key =
22
+ if options[:stringify_keys]
23
+ k.to_s
24
+ elsif options[:symbolize_keys] && k.respond_to?(:to_sym)
25
+ k.to_sym
26
+ else
27
+ k
28
+ end
28
29
  if self[k].is_a?(Array)
29
30
  out[assignment_key] ||= []
30
31
  self[k].each do |array_object|
31
- out[assignment_key] << (Hash === array_object ? flexibly_convert_to_hash(array_object, options) : array_object)
32
+ out[assignment_key] << maybe_convert_to_hash(array_object, options)
32
33
  end
33
34
  else
34
- out[assignment_key] = (Hash === self[k] || self[k].respond_to?(:to_hash)) ? flexibly_convert_to_hash(self[k], options) : self[k]
35
+ out[assignment_key] = maybe_convert_to_hash(self[k], options)
35
36
  end
36
37
  end
37
38
  out
@@ -44,8 +45,14 @@ module Hashie
44
45
 
45
46
  private
46
47
 
48
+ def maybe_convert_to_hash(object, options)
49
+ return object unless object.is_a?(Hash) || object.respond_to?(:to_hash)
50
+
51
+ flexibly_convert_to_hash(object, options)
52
+ end
53
+
47
54
  def flexibly_convert_to_hash(object, options = {})
48
- if object.method(:to_hash).arity == 0
55
+ if object.method(:to_hash).arity.zero?
49
56
  object.to_hash
50
57
  else
51
58
  object.to_hash(options)