hashie 3.6.0 → 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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +271 -207
  3. data/CONTRIBUTING.md +13 -6
  4. data/LICENSE +1 -1
  5. data/README.md +245 -54
  6. data/UPGRADING.md +121 -7
  7. data/hashie.gemspec +13 -7
  8. data/lib/hashie/dash.rb +18 -17
  9. data/lib/hashie/extensions/active_support/core_ext/hash.rb +14 -0
  10. data/lib/hashie/extensions/coercion.rb +23 -16
  11. data/lib/hashie/extensions/dash/indifferent_access.rb +29 -1
  12. data/lib/hashie/extensions/dash/predefined_values.rb +88 -0
  13. data/lib/hashie/extensions/dash/property_translation.rb +12 -4
  14. data/lib/hashie/extensions/deep_fetch.rb +4 -2
  15. data/lib/hashie/extensions/deep_find.rb +12 -3
  16. data/lib/hashie/extensions/deep_locate.rb +22 -7
  17. data/lib/hashie/extensions/deep_merge.rb +18 -1
  18. data/lib/hashie/extensions/ignore_undeclared.rb +4 -5
  19. data/lib/hashie/extensions/indifferent_access.rb +37 -6
  20. data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
  21. data/lib/hashie/extensions/mash/define_accessors.rb +90 -0
  22. data/lib/hashie/extensions/mash/keep_original_keys.rb +2 -1
  23. data/lib/hashie/extensions/mash/permissive_respond_to.rb +61 -0
  24. data/lib/hashie/extensions/mash/safe_assignment.rb +3 -1
  25. data/lib/hashie/extensions/mash/symbolize_keys.rb +5 -5
  26. data/lib/hashie/extensions/method_access.rb +5 -2
  27. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +26 -4
  28. data/lib/hashie/extensions/ruby_version_check.rb +5 -1
  29. data/lib/hashie/extensions/strict_key_access.rb +8 -4
  30. data/lib/hashie/extensions/symbolize_keys.rb +12 -1
  31. data/lib/hashie/hash.rb +16 -9
  32. data/lib/hashie/mash.rb +130 -64
  33. data/lib/hashie/railtie.rb +7 -0
  34. data/lib/hashie/rash.rb +1 -1
  35. data/lib/hashie/utils.rb +28 -0
  36. data/lib/hashie/version.rb +1 -1
  37. data/lib/hashie.rb +22 -19
  38. metadata +23 -133
  39. data/spec/hashie/array_spec.rb +0 -29
  40. data/spec/hashie/clash_spec.rb +0 -70
  41. data/spec/hashie/dash_spec.rb +0 -598
  42. data/spec/hashie/extensions/autoload_spec.rb +0 -24
  43. data/spec/hashie/extensions/coercion_spec.rb +0 -639
  44. data/spec/hashie/extensions/dash/coercion_spec.rb +0 -13
  45. data/spec/hashie/extensions/dash/indifferent_access_spec.rb +0 -84
  46. data/spec/hashie/extensions/deep_fetch_spec.rb +0 -97
  47. data/spec/hashie/extensions/deep_find_spec.rb +0 -138
  48. data/spec/hashie/extensions/deep_locate_spec.rb +0 -137
  49. data/spec/hashie/extensions/deep_merge_spec.rb +0 -70
  50. data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -47
  51. data/spec/hashie/extensions/indifferent_access_spec.rb +0 -295
  52. data/spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb +0 -208
  53. data/spec/hashie/extensions/key_conversion_spec.rb +0 -12
  54. data/spec/hashie/extensions/mash/keep_original_keys_spec.rb +0 -46
  55. data/spec/hashie/extensions/mash/safe_assignment_spec.rb +0 -50
  56. data/spec/hashie/extensions/mash/symbolize_keys_spec.rb +0 -39
  57. data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
  58. data/spec/hashie/extensions/method_access_spec.rb +0 -226
  59. data/spec/hashie/extensions/strict_key_access_spec.rb +0 -110
  60. data/spec/hashie/extensions/stringify_keys_spec.rb +0 -124
  61. data/spec/hashie/extensions/symbolize_keys_spec.rb +0 -129
  62. data/spec/hashie/hash_spec.rb +0 -84
  63. data/spec/hashie/mash_spec.rb +0 -771
  64. data/spec/hashie/parsers/yaml_erb_parser_spec.rb +0 -46
  65. data/spec/hashie/rash_spec.rb +0 -83
  66. data/spec/hashie/trash_spec.rb +0 -328
  67. data/spec/hashie/utils_spec.rb +0 -25
  68. data/spec/hashie/version_spec.rb +0 -7
  69. data/spec/hashie_spec.rb +0 -13
  70. data/spec/integration/elasticsearch/integration_spec.rb +0 -40
  71. data/spec/integration/omniauth/app.rb +0 -11
  72. data/spec/integration/omniauth/integration_spec.rb +0 -38
  73. data/spec/integration/omniauth-oauth2/app.rb +0 -52
  74. data/spec/integration/omniauth-oauth2/integration_spec.rb +0 -26
  75. data/spec/integration/omniauth-oauth2/some_site.rb +0 -38
  76. data/spec/integration/rails/app.rb +0 -47
  77. data/spec/integration/rails/integration_spec.rb +0 -26
  78. data/spec/integration/rails-without-dependency/integration_spec.rb +0 -15
  79. data/spec/spec_helper.rb +0 -23
  80. data/spec/support/integration_specs.rb +0 -36
  81. data/spec/support/logger.rb +0 -24
  82. data/spec/support/module_context.rb +0 -11
  83. data/spec/support/ruby_version_check.rb +0 -6
@@ -3,7 +3,7 @@ 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
- copy = dup
6
+ copy = _deep_dup(self)
7
7
  copy.extend(Hashie::Extensions::DeepMerge) unless copy.respond_to?(:deep_merge!)
8
8
  copy.deep_merge!(other_hash, &block)
9
9
  end
@@ -18,11 +18,28 @@ module Hashie
18
18
 
19
19
  private
20
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
+
21
36
  def _recursive_merge(hash, other_hash, &block)
22
37
  other_hash.each do |k, v|
23
38
  hash[k] =
24
39
  if hash.key?(k) && hash[k].is_a?(::Hash) && v.is_a?(::Hash)
25
40
  _recursive_merge(hash[k], v, &block)
41
+ elsif v.is_a?(::Hash)
42
+ _recursive_merge({}, v, &block)
26
43
  elsif hash.key?(k) && block_given?
27
44
  yield(k, hash[k], v)
28
45
  else
@@ -31,12 +31,11 @@ module Hashie
31
31
  module IgnoreUndeclared
32
32
  def initialize_attributes(attributes)
33
33
  return unless attributes
34
+
34
35
  klass = self.class
35
- translations = klass.respond_to?(:translations) && klass.translations
36
- attributes.each_pair do |att, value|
37
- next unless klass.property?(att) || (translations && translations.include?(att))
38
- self[att] = value
39
- end
36
+ translations = klass.respond_to?(:translations) && klass.translations || []
37
+
38
+ super(attributes.select { |attr, _| klass.property?(attr) || translations.include?(attr) })
40
39
  end
41
40
 
42
41
  def property_exists?(property)
@@ -23,10 +23,15 @@ 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)
@@ -68,7 +73,7 @@ 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
@@ -76,7 +81,7 @@ module Hashie
76
81
  # is injecting itself into member hashes.
77
82
  def convert!
78
83
  keys.each do |k| # rubocop:disable Performance/HashEachMethods
79
- regular_writer convert_key(k), indifferent_value(regular_delete(k))
84
+ indifferent_writer k, regular_delete(k)
80
85
  end
81
86
  self
82
87
  end
@@ -135,7 +140,7 @@ module Hashie
135
140
 
136
141
  def merge(*args)
137
142
  result = super
138
- IndifferentAccess.inject!(result) if hash_lacking_indifference?(result)
143
+ return IndifferentAccess.inject!(result) if hash_lacking_indifference?(result)
139
144
  result.convert!
140
145
  end
141
146
 
@@ -143,6 +148,32 @@ module Hashie
143
148
  super.convert!
144
149
  end
145
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
+
146
177
  protected
147
178
 
148
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
@@ -15,7 +15,8 @@ module Hashie
15
15
  # mash[:symbol_key] == mash['symbol_key'] #=> true
16
16
  module KeepOriginalKeys
17
17
  def self.included(descendant)
18
- raise ArgumentError, "#{descendant} is not a kind of Hashie::Mash" unless descendant <= Hashie::Mash
18
+ error_message = "#{descendant} is not a kind of Hashie::Mash"
19
+ raise ArgumentError, error_message unless descendant <= Hashie::Mash
19
20
  end
20
21
 
21
22
  private
@@ -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
- raise 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
@@ -24,13 +24,13 @@ module Hashie
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
@@ -73,7 +73,9 @@ module Hashie
73
73
  end
74
74
 
75
75
  def method_missing(name, *args)
76
- return self[convert_key(Regexp.last_match[1])] = args.first if args.size == 1 && name.to_s =~ /(.*)=$/
76
+ if args.size == 1 && name.to_s =~ /(.*)=$/
77
+ return self[convert_key(Regexp.last_match[1])] = args.first
78
+ end
77
79
 
78
80
  super
79
81
  end
@@ -231,7 +233,8 @@ module Hashie
231
233
  # underscores.
232
234
  module MethodAccessWithOverride
233
235
  def self.included(base)
234
- [MethodReader, MethodOverridingWriter, MethodQuery, MethodOverridingInitializer].each do |mod|
236
+ [MethodReader, MethodOverridingWriter,
237
+ MethodQuery, MethodOverridingInitializer].each do |mod|
235
238
  base.send :include, mod
236
239
  end
237
240
  end
@@ -6,19 +6,41 @@ module Hashie
6
6
  module Extensions
7
7
  module Parsers
8
8
  class YamlErbParser
9
- def initialize(file_path)
9
+ def initialize(file_path, options = {})
10
10
  @content = File.read(file_path)
11
11
  @file_path = file_path.is_a?(Pathname) ? file_path.to_s : file_path
12
+ @options = options
12
13
  end
13
14
 
14
15
  def perform
15
16
  template = ERB.new(@content)
16
17
  template.filename = @file_path
17
- YAML.safe_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)
23
+ end
24
+
25
+ def self.perform(file_path, options = {})
26
+ new(file_path, options).perform
18
27
  end
19
28
 
20
- def self.perform(file_path)
21
- new(file_path).perform
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
22
44
  end
23
45
  end
24
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,7 +46,7 @@ module Hashie
46
46
  hash.extend(Hashie::Extensions::SymbolizeKeys) unless hash.respond_to?(:symbolize_keys!)
47
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
@@ -18,20 +18,21 @@ module Hashie
18
18
  def to_hash(options = {})
19
19
  out = {}
20
20
  each_key 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
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] << (array_object.is_a?(Hash) ? 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] = self[k].is_a?(Hash) || 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,6 +45,12 @@ 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
55
  if object.method(:to_hash).arity.zero?
49
56
  object.to_hash