hashie 2.1.2 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +524 -59
  3. data/CONTRIBUTING.md +24 -7
  4. data/README.md +781 -90
  5. data/Rakefile +19 -2
  6. data/UPGRADING.md +245 -0
  7. data/hashie.gemspec +21 -13
  8. data/lib/hashie.rb +60 -21
  9. data/lib/hashie/array.rb +21 -0
  10. data/lib/hashie/clash.rb +24 -12
  11. data/lib/hashie/dash.rb +96 -33
  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 +124 -18
  15. data/lib/hashie/extensions/dash/coercion.rb +25 -0
  16. data/lib/hashie/extensions/dash/indifferent_access.rb +56 -0
  17. data/lib/hashie/extensions/dash/property_translation.rb +191 -0
  18. data/lib/hashie/extensions/deep_fetch.rb +7 -5
  19. data/lib/hashie/extensions/deep_find.rb +69 -0
  20. data/lib/hashie/extensions/deep_locate.rb +113 -0
  21. data/lib/hashie/extensions/deep_merge.rb +35 -12
  22. data/lib/hashie/extensions/ignore_undeclared.rb +11 -5
  23. data/lib/hashie/extensions/indifferent_access.rb +28 -16
  24. data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
  25. data/lib/hashie/extensions/key_conversion.rb +0 -82
  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 +18 -0
  30. data/lib/hashie/extensions/mash/symbolize_keys.rb +38 -0
  31. data/lib/hashie/extensions/method_access.rb +154 -11
  32. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +48 -0
  33. data/lib/hashie/extensions/pretty_inspect.rb +19 -0
  34. data/lib/hashie/extensions/ruby_version.rb +60 -0
  35. data/lib/hashie/extensions/ruby_version_check.rb +21 -0
  36. data/lib/hashie/extensions/strict_key_access.rb +77 -0
  37. data/lib/hashie/extensions/stringify_keys.rb +71 -0
  38. data/lib/hashie/extensions/symbolize_keys.rb +71 -0
  39. data/lib/hashie/hash.rb +27 -8
  40. data/lib/hashie/logger.rb +18 -0
  41. data/lib/hashie/mash.rb +235 -57
  42. data/lib/hashie/railtie.rb +21 -0
  43. data/lib/hashie/rash.rb +40 -16
  44. data/lib/hashie/trash.rb +2 -88
  45. data/lib/hashie/utils.rb +44 -0
  46. data/lib/hashie/version.rb +1 -1
  47. metadata +42 -81
  48. data/.gitignore +0 -9
  49. data/.rspec +0 -2
  50. data/.rubocop.yml +0 -36
  51. data/.travis.yml +0 -15
  52. data/Gemfile +0 -11
  53. data/Guardfile +0 -5
  54. data/lib/hashie/hash_extensions.rb +0 -47
  55. data/spec/hashie/clash_spec.rb +0 -48
  56. data/spec/hashie/dash_spec.rb +0 -338
  57. data/spec/hashie/extensions/coercion_spec.rb +0 -156
  58. data/spec/hashie/extensions/deep_fetch_spec.rb +0 -70
  59. data/spec/hashie/extensions/deep_merge_spec.rb +0 -22
  60. data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -23
  61. data/spec/hashie/extensions/indifferent_access_spec.rb +0 -152
  62. data/spec/hashie/extensions/key_conversion_spec.rb +0 -103
  63. data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
  64. data/spec/hashie/extensions/method_access_spec.rb +0 -121
  65. data/spec/hashie/hash_spec.rb +0 -66
  66. data/spec/hashie/mash_spec.rb +0 -467
  67. data/spec/hashie/rash_spec.rb +0 -44
  68. data/spec/hashie/trash_spec.rb +0 -193
  69. data/spec/hashie/version_spec.rb +0 -7
  70. data/spec/spec.opts +0 -3
  71. data/spec/spec_helper.rb +0 -8
data/Rakefile CHANGED
@@ -7,9 +7,26 @@ Bundler::GemHelper.install_tasks
7
7
  require 'rspec/core/rake_task'
8
8
  RSpec::Core::RakeTask.new do |spec|
9
9
  spec.pattern = 'spec/**/*_spec.rb'
10
+ spec.exclude_pattern = 'spec/integration/**/*_spec.rb'
10
11
  end
11
12
 
12
13
  require 'rubocop/rake_task'
13
- Rubocop::RakeTask.new(:rubocop)
14
+ RuboCop::RakeTask.new(:rubocop)
14
15
 
15
- task default: [:rubocop, :spec]
16
+ require_relative 'spec/support/integration_specs'
17
+ task :integration_specs do
18
+ next if ENV['CI']
19
+ status_codes = []
20
+ handler = lambda do |status_code|
21
+ status_codes << status_code unless status_code.zero?
22
+ end
23
+
24
+ run_all_integration_specs(handler: handler, logger: ->(msg) { puts msg })
25
+
26
+ if status_codes.any?
27
+ warn "#{status_codes.size} integration test(s) failed"
28
+ exit status_codes.last
29
+ end
30
+ end
31
+
32
+ task default: %i[rubocop spec integration_specs]
@@ -0,0 +1,245 @@
1
+ Upgrading Hashie
2
+ ================
3
+
4
+ ### Upgrading to 4.0.0
5
+
6
+ #### Non-destructive Hash methods called on Mash
7
+
8
+ The following non-destructive Hash methods called on Mash will now return an instance of the class it was called on.
9
+
10
+ | method | ruby |
11
+ | ----------------- | ---- |
12
+ | #compact | |
13
+ | #invert | |
14
+ | #reject | |
15
+ | #select | |
16
+ | #slice | 2.5 |
17
+ | #transform_keys | 2.5 |
18
+ | #transform_values | 2.4 |
19
+
20
+ ```ruby
21
+ class Parents < Hashie::Mash; end
22
+
23
+ parents = Parents.new(father: 'Dad', mother: 'Mom')
24
+ cool_parents = parents.transform_values { |v| v + v[-1] + 'io'}
25
+
26
+ p cool_parents
27
+
28
+ # before:
29
+ {"father"=>"Daddio", "mother"=>"Mommio"}
30
+ => {"father"=>"Daddio", "mother"=>"Mommio"}
31
+
32
+ # after:
33
+ #<Parents father="Daddio" mother="Mommio">
34
+ => {"father"=>"Dad", "mother"=>"Mom"}
35
+ ```
36
+
37
+ This may make places where you had to re-make the Mash redundant, and may cause unintended side effects if your application was expecting a plain old ruby Hash.
38
+
39
+ #### Ruby 2.6: Mash#merge and Mash#merge!
40
+
41
+ In Ruby > 2.6.0, Hashie now supports passing multiple hash and Mash objects to Mash#merge and Mash#merge!.
42
+
43
+ #### Hashie::Mash::CannotDisableMashWarnings error class is removed
44
+
45
+ There shouldn't really be a case that anyone was relying on catching this specific error, but if so, they should change it to rescue Hashie::Extensions::KeyConflictWarning::CannotDisableMashWarnings
46
+
47
+ ### Upgrading to 3.7.0
48
+
49
+ #### Mash#load takes options
50
+
51
+ The `Hashie::Mash#load` method now accepts options, changing the interface of `Parser#initialize`. If you have a custom parser, you must update its `initialize` method.
52
+
53
+ For example, `Hashie::Extensions::Parsers::YamlErbParser` now accepts `permitted_classes`, `permitted_symbols` and `aliases` options.
54
+
55
+ Before:
56
+
57
+ ```ruby
58
+ class Hashie::Extensions::Parsers::YamlErbParser
59
+ def initialize(file_path)
60
+ @file_path = file_path
61
+ end
62
+ end
63
+ ```
64
+
65
+ After:
66
+
67
+ ```ruby
68
+ class Hashie::Extensions::Parsers::YamlErbParser
69
+ def initialize(file_path, options = {})
70
+ @file_path = file_path
71
+ @options = options
72
+ end
73
+ end
74
+ ```
75
+
76
+ Options can now be passed into `Mash#load`.
77
+
78
+ ```ruby
79
+ Mash.load(filename, permitted_classes: [])
80
+ ```
81
+
82
+ ### Upgrading to 3.5.2
83
+
84
+ #### Disable logging in Mash subclasses
85
+
86
+ If you subclass `Hashie::Mash`, you can now disable the logging we do about
87
+ overriding existing methods with keys. This looks like:
88
+
89
+ ```ruby
90
+ class MyMash < Hashie::Mash
91
+ disable_warnings
92
+ end
93
+ ```
94
+
95
+ ### Upgrading to 3.4.7
96
+
97
+ #### Procs as default values for Dash
98
+
99
+ ```ruby
100
+ class MyHash < Hashie::Dash
101
+ property :time, default: -> { Time.now }
102
+ end
103
+ ```
104
+
105
+ In versions < 3.4.7 `Time.now` will be evaluated when `time` property is accessed directly first time.
106
+ In version >= 3.4.7 `Time.now` is evaluated in time of object initialization.
107
+ ### Upgrading to 3.4.4
108
+
109
+ #### Mash subclasses and reverse_merge
110
+
111
+ ```ruby
112
+ class MyMash < Hashie::Mash
113
+ end
114
+ ```
115
+
116
+ In versions >= 3.4.4 `MyMash#reverse_merge` returns an instance of `MyMash` but in previous versions it was a `Hashie::Mash` instance.
117
+
118
+ ### Upgrading to 3.2.2
119
+
120
+ #### Testing if key defined
121
+
122
+ In versions <= 3.2.1 Hash object being questioned doesn't return a boolean value as it's mentioned in README.md
123
+
124
+ ```ruby
125
+ class MyHash < Hash
126
+ include Hashie::Extensions::MethodAccess
127
+ end
128
+
129
+ h = MyHash.new
130
+ h.abc = 'def'
131
+ h.abc # => 'def'
132
+ h.abc? # => 'def'
133
+ ```
134
+
135
+ In versions >= 3.2.2 it returns a boolean value
136
+
137
+ ```ruby
138
+ h.abc? # => true
139
+ h.abb? # => false
140
+ ```
141
+
142
+ ### Upgrading to 3.2.1
143
+
144
+ #### Possible coercion changes
145
+
146
+ The improvements made to coercions in version 3.2.1 [issue #200](https://github.com/hashie/hashie/pull/200) do not break the documented API, but are significant enough that changes may effect undocumented side-effects. Applications that depended on those side-effects will need to be updated.
147
+
148
+ **Change**: Type coercion no longer creates new objects if the input matches the target type. Previously coerced properties always resulted in the creation of a new object, even when it wasn't necessary. This had the effect of a `dup` or `clone` on coerced properties but not uncoerced ones.
149
+
150
+ If necessary, `dup` or `clone` your own objects. Do not assume Hashie will do it for you.
151
+
152
+ **Change**: Failed coercion attempts now raise Hashie::CoercionError.
153
+
154
+ Hashie now raises a Hashie::CoercionError that details on the property that could not be coerced, the source and target type of the coercion, and the internal error. Previously only the internal error was raised.
155
+
156
+ Applications that were attempting to rescuing the internal errors should be updated to rescue Hashie::CoercionError instead.
157
+
158
+ ### Upgrading to 3.0
159
+
160
+ #### Compatibility with Rails 4 Strong Parameters
161
+
162
+ Version 2.1 introduced support to prevent default Rails 4 mass-assignment protection behavior. This was [issue #89](https://github.com/hashie/hashie/issues/89), resolved in [#104](https://github.com/hashie/hashie/pull/104). In version 2.2 this behavior has been removed in [#147](https://github.com/hashie/hashie/pull/147) in favor of a mixin and finally extracted into a separate gem in Hashie 3.0.
163
+
164
+ To enable 2.1 compatible behavior with Rails 4, use the [hashie_rails](http://rubygems.org/gems/hashie_rails) gem.
165
+
166
+ ```
167
+ gem 'hashie_rails'
168
+ ```
169
+
170
+ See [#154](https://github.com/hashie/hashie/pull/154) and [Mash and Rails 4 Strong Parameters](README.md#mash-and-rails-4-strong-parameters) for more details.
171
+
172
+ #### Key Conversions in Hashie::Dash and Hashie::Trash
173
+
174
+ Version 2.1 and older of Hashie::Dash and Hashie::Trash converted keys to strings by default. This is no longer the case in 2.2.
175
+
176
+ Consider the following code.
177
+
178
+ ```ruby
179
+ class Person < Hashie::Dash
180
+ property :name
181
+ end
182
+
183
+ p = Person.new(name: 'dB.')
184
+ ```
185
+
186
+ Version 2.1 behaves as follows.
187
+
188
+ ```ruby
189
+ p.name # => 'dB.'
190
+ p[:name] # => 'dB.'
191
+ p['name'] # => 'dB.'
192
+
193
+ # not what I put in
194
+ p.inspect # => { 'name' => 'dB.' }
195
+ p.to_hash # => { 'name' => 'dB.' }
196
+ ```
197
+
198
+ It was not possible to achieve the behavior of preserving keys, as described in [issue #151](https://github.com/hashie/hashie/issues/151).
199
+
200
+ Version 2.2 does not perform this conversion by default.
201
+
202
+ ```ruby
203
+ p.name # => 'dB.'
204
+ p[:name] # => 'dB.'
205
+ # p['name'] # => NoMethodError
206
+
207
+ p.inspect # => { :name => 'dB.' }
208
+ p.to_hash # => { :name => 'dB.' }
209
+ ```
210
+
211
+ To enable behavior compatible with older versions, use `Hashie::Extensions::Dash::IndifferentAccess`.
212
+
213
+ ```ruby
214
+ class Person < Hashie::Dash
215
+ include Hashie::Extensions::Dash::IndifferentAccess
216
+ property :name
217
+ end
218
+ ```
219
+
220
+ #### Key Conversions in Hashie::Hash#to_hash
221
+
222
+ Version 2.1 or older of Hash#to_hash converted keys to strings automatically.
223
+
224
+ ```ruby
225
+ instance = Hashie::Hash[first: 'First', 'last' => 'Last']
226
+ instance.to_hash # => { "first" => 'First', "last" => 'Last' }
227
+ ```
228
+
229
+ It was possible to symbolize keys by passing `:symbolize_keys`, however it was not possible to retrieve the hash with initial key values.
230
+
231
+ ```ruby
232
+ instance.to_hash(symbolize_keys: true) # => { :first => 'First', :last => 'Last' }
233
+ instance.to_hash(stringify_keys: true) # => { "first" => 'First', "last" => 'Last' }
234
+ ```
235
+
236
+ Version 2.2 no longer converts keys by default.
237
+
238
+ ```ruby
239
+ instance = Hashie::Hash[first: 'First', 'last' => 'Last']
240
+ instance.to_hash # => { :first => 'First', "last" => 'Last' }
241
+ ```
242
+
243
+ The behavior with `symbolize_keys` and `stringify_keys` is unchanged.
244
+
245
+ See [#152](https://github.com/hashie/hashie/pull/152) for more information.
@@ -1,20 +1,28 @@
1
1
  require File.expand_path('../lib/hashie/version', __FILE__)
2
2
 
3
3
  Gem::Specification.new do |gem|
4
- gem.authors = ["Michael Bleigh", "Jerry Cheung"]
5
- gem.email = ["michael@intridea.com", "jollyjerry@gmail.com"]
6
- gem.description = %q{Hashie is a collection of classes and mixins that make hashes more powerful.}
7
- gem.summary = %q{Your friendly neighborhood hash library.}
8
- gem.homepage = 'https://github.com/intridea/hashie'
4
+ gem.name = 'hashie'
5
+ gem.version = Hashie::VERSION
6
+ gem.authors = ['Michael Bleigh', 'Jerry Cheung']
7
+ gem.email = ['michael@intridea.com', 'jollyjerry@gmail.com']
8
+ gem.description = 'Hashie is a collection of classes and mixins that make hashes more powerful.'
9
+ gem.summary = 'Your friendly neighborhood hash library.'
10
+ gem.homepage = 'https://github.com/hashie/hashie'
11
+ gem.license = 'MIT'
9
12
 
10
- gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
11
- gem.files = `git ls-files`.split("\n")
12
- gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
13
- gem.name = "hashie"
14
13
  gem.require_paths = ['lib']
15
- gem.version = Hashie::VERSION
16
- gem.license = "MIT"
14
+ gem.files = %w[.yardopts CHANGELOG.md CONTRIBUTING.md LICENSE README.md UPGRADING.md]
15
+ gem.files += %w[Rakefile hashie.gemspec]
16
+ gem.files += Dir['lib/**/*.rb']
17
+
18
+ if gem.respond_to?(:metadata)
19
+ gem.metadata = {
20
+ 'bug_tracker_uri' => 'https://github.com/hashie/hashie/issues',
21
+ 'changelog_uri' => 'https://github.com/hashie/hashie/blob/master/CHANGELOG.md',
22
+ 'documentation_uri' => 'https://www.rubydoc.info/gems/hashie',
23
+ 'source_code_uri' => 'https://github.com/hashie/hashie'
24
+ }
25
+ end
17
26
 
18
- gem.add_development_dependency 'rake'
19
- gem.add_development_dependency 'rspec'
27
+ gem.add_development_dependency 'bundler'
20
28
  end
@@ -1,26 +1,65 @@
1
+ require 'hashie/logger'
2
+ require 'hashie/version'
3
+
1
4
  module Hashie
2
- autoload :Clash, 'hashie/clash'
3
- autoload :Dash, 'hashie/dash'
4
- autoload :Hash, 'hashie/hash'
5
- autoload :HashExtensions, 'hashie/hash_extensions'
6
- autoload :Mash, 'hashie/mash'
7
- autoload :PrettyInspect, 'hashie/hash_extensions'
8
- autoload :Trash, 'hashie/trash'
9
- autoload :Rash, 'hashie/rash'
5
+ autoload :Clash, 'hashie/clash'
6
+ autoload :Dash, 'hashie/dash'
7
+ autoload :Hash, 'hashie/hash'
8
+ autoload :Mash, 'hashie/mash'
9
+ autoload :Trash, 'hashie/trash'
10
+ autoload :Rash, 'hashie/rash'
11
+ autoload :Array, 'hashie/array'
12
+ autoload :Utils, 'hashie/utils'
10
13
 
11
14
  module Extensions
12
- autoload :Coercion, 'hashie/extensions/coercion'
13
- autoload :DeepMerge, 'hashie/extensions/deep_merge'
14
- autoload :KeyConversion, 'hashie/extensions/key_conversion'
15
- autoload :IgnoreUndeclared, 'hashie/extensions/ignore_undeclared'
16
- autoload :IndifferentAccess, 'hashie/extensions/indifferent_access'
17
- autoload :MergeInitializer, 'hashie/extensions/merge_initializer'
18
- autoload :MethodAccess, 'hashie/extensions/method_access'
19
- autoload :MethodQuery, 'hashie/extensions/method_access'
20
- autoload :MethodReader, 'hashie/extensions/method_access'
21
- autoload :MethodWriter, 'hashie/extensions/method_access'
22
- autoload :StringifyKeys, 'hashie/extensions/key_conversion'
23
- autoload :SymbolizeKeys, 'hashie/extensions/key_conversion'
24
- autoload :DeepFetch, 'hashie/extensions/deep_fetch'
15
+ autoload :Coercion, 'hashie/extensions/coercion'
16
+ autoload :DeepMerge, 'hashie/extensions/deep_merge'
17
+ autoload :IgnoreUndeclared, 'hashie/extensions/ignore_undeclared'
18
+ autoload :IndifferentAccess, 'hashie/extensions/indifferent_access'
19
+ autoload :MergeInitializer, 'hashie/extensions/merge_initializer'
20
+ autoload :MethodAccess, 'hashie/extensions/method_access'
21
+ autoload :MethodQuery, 'hashie/extensions/method_access'
22
+ autoload :MethodReader, 'hashie/extensions/method_access'
23
+ autoload :MethodWriter, 'hashie/extensions/method_access'
24
+ autoload :StringifyKeys, 'hashie/extensions/stringify_keys'
25
+ autoload :SymbolizeKeys, 'hashie/extensions/symbolize_keys'
26
+ autoload :DeepFetch, 'hashie/extensions/deep_fetch'
27
+ autoload :DeepFind, 'hashie/extensions/deep_find'
28
+ autoload :DeepLocate, 'hashie/extensions/deep_locate'
29
+ autoload :PrettyInspect, 'hashie/extensions/pretty_inspect'
30
+ autoload :KeyConversion, 'hashie/extensions/key_conversion'
31
+ autoload :MethodAccessWithOverride, 'hashie/extensions/method_access'
32
+ autoload :StrictKeyAccess, 'hashie/extensions/strict_key_access'
33
+ autoload :RubyVersion, 'hashie/extensions/ruby_version'
34
+ autoload :RubyVersionCheck, 'hashie/extensions/ruby_version_check'
35
+
36
+ module Parsers
37
+ autoload :YamlErbParser, 'hashie/extensions/parsers/yaml_erb_parser'
38
+ end
39
+
40
+ module Dash
41
+ autoload :IndifferentAccess, 'hashie/extensions/dash/indifferent_access'
42
+ autoload :PropertyTranslation, 'hashie/extensions/dash/property_translation'
43
+ autoload :Coercion, 'hashie/extensions/dash/coercion'
44
+ end
45
+
46
+ module Mash
47
+ autoload :KeepOriginalKeys, 'hashie/extensions/mash/keep_original_keys'
48
+ autoload :PermissiveRespondTo, 'hashie/extensions/mash/permissive_respond_to'
49
+ autoload :SafeAssignment, 'hashie/extensions/mash/safe_assignment'
50
+ autoload :SymbolizeKeys, 'hashie/extensions/mash/symbolize_keys'
51
+ autoload :DefineAccessors, 'hashie/extensions/mash/define_accessors'
52
+ end
53
+
54
+ module Array
55
+ autoload :PrettyInspect, 'hashie/extensions/array/pretty_inspect'
56
+ end
57
+ end
58
+
59
+ class << self
60
+ include Hashie::Extensions::StringifyKeys::ClassMethods
61
+ include Hashie::Extensions::SymbolizeKeys::ClassMethods
25
62
  end
63
+
64
+ require 'hashie/railtie' if defined?(::Rails)
26
65
  end
@@ -0,0 +1,21 @@
1
+ require 'hashie/extensions/array/pretty_inspect'
2
+ require 'hashie/extensions/ruby_version_check'
3
+
4
+ module Hashie
5
+ class Array < ::Array
6
+ include Hashie::Extensions::Array::PrettyInspect
7
+ include Hashie::Extensions::RubyVersionCheck
8
+ with_minimum_ruby('2.3.0') do
9
+ def dig(*indexes)
10
+ converted_indexes = indexes.map do |idx|
11
+ begin
12
+ Integer(idx)
13
+ rescue ArgumentError
14
+ idx
15
+ end
16
+ end
17
+ super(*converted_indexes)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -54,7 +54,7 @@ module Hashie
54
54
  case args.length
55
55
  when 1
56
56
  val = args.first
57
- val = self[key].merge(val) if self[key].is_a?(::Hash) && val.is_a?(::Hash)
57
+ val = self.class.new(self[key]).merge(val) if self[key].is_a?(::Hash) && val.is_a?(::Hash)
58
58
  else
59
59
  val = args
60
60
  end
@@ -64,22 +64,34 @@ module Hashie
64
64
  end
65
65
 
66
66
  def method_missing(name, *args) #:nodoc:
67
- name = name.to_s
68
- if name.match(/!$/) && args.empty?
67
+ if args.empty? && name.to_s.end_with?('!')
69
68
  key = name[0...-1].to_sym
70
69
 
71
- if self[key].nil?
72
- self[key] = Clash.new({}, self)
73
- elsif self[key].is_a?(::Hash) && !self[key].is_a?(Clash)
74
- self[key] = Clash.new(self[key], self)
70
+ case self[key]
71
+ when NilClass
72
+ self[key] = self.class.new({}, self)
73
+ when Clash
74
+ self[key]
75
+ when Hash
76
+ self[key] = self.class.new(self[key], self)
75
77
  else
76
- fail ChainError, 'Tried to chain into a non-hash key.'
78
+ raise ChainError, 'Tried to chain into a non-hash key.'
77
79
  end
78
-
79
- self[key]
80
80
  elsif args.any?
81
- key = name.to_sym
82
- merge_store(key, *args)
81
+ merge_store(name, *args)
82
+ else
83
+ super
84
+ end
85
+ end
86
+
87
+ def respond_to_missing?(method_name, _include_private = false)
88
+ method_name = method_name.to_s
89
+
90
+ if method_name.end_with?('!')
91
+ key = method_name[0...-1].to_sym
92
+ [NilClass, Clash, Hash].include?(self[key].class)
93
+ else
94
+ true
83
95
  end
84
96
  end
85
97
  end