hashie 3.5.7 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +91 -22
  4. data/Rakefile +2 -2
  5. data/hashie.gemspec +1 -1
  6. data/lib/hashie/clash.rb +12 -1
  7. data/lib/hashie/dash.rb +41 -21
  8. data/lib/hashie/extensions/coercion.rb +5 -5
  9. data/lib/hashie/extensions/dash/property_translation.rb +49 -26
  10. data/lib/hashie/extensions/deep_fetch.rb +1 -1
  11. data/lib/hashie/extensions/deep_find.rb +2 -2
  12. data/lib/hashie/extensions/deep_locate.rb +4 -5
  13. data/lib/hashie/extensions/deep_merge.rb +8 -9
  14. data/lib/hashie/extensions/indifferent_access.rb +7 -5
  15. data/lib/hashie/extensions/mash/keep_original_keys.rb +3 -5
  16. data/lib/hashie/extensions/mash/safe_assignment.rb +1 -1
  17. data/lib/hashie/extensions/mash/symbolize_keys.rb +1 -1
  18. data/lib/hashie/extensions/method_access.rb +47 -17
  19. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +3 -1
  20. data/lib/hashie/extensions/strict_key_access.rb +8 -9
  21. data/lib/hashie/extensions/stringify_keys.rb +1 -1
  22. data/lib/hashie/extensions/symbolize_keys.rb +1 -1
  23. data/lib/hashie/hash.rb +4 -4
  24. data/lib/hashie/mash.rb +22 -22
  25. data/lib/hashie/rash.rb +5 -5
  26. data/lib/hashie/version.rb +1 -1
  27. data/spec/hashie/array_spec.rb +1 -1
  28. data/spec/hashie/dash_spec.rb +27 -2
  29. data/spec/hashie/extensions/coercion_spec.rb +20 -12
  30. data/spec/hashie/extensions/deep_locate_spec.rb +1 -1
  31. data/spec/hashie/extensions/deep_merge_spec.rb +1 -1
  32. data/spec/hashie/extensions/indifferent_access_spec.rb +20 -7
  33. data/spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb +5 -5
  34. data/spec/hashie/extensions/method_access_spec.rb +42 -4
  35. data/spec/hashie/extensions/stringify_keys_spec.rb +4 -4
  36. data/spec/hashie/extensions/symbolize_keys_spec.rb +3 -3
  37. data/spec/hashie/mash_spec.rb +16 -8
  38. data/spec/hashie/parsers/yaml_erb_parser_spec.rb +3 -3
  39. data/spec/hashie/rash_spec.rb +2 -2
  40. data/spec/hashie/trash_spec.rb +61 -1
  41. data/spec/integration/elasticsearch/integration_spec.rb +40 -0
  42. data/spec/integration/omniauth-oauth2/app.rb +3 -4
  43. data/spec/integration/omniauth-oauth2/some_site.rb +2 -2
  44. data/spec/integration/rails/app.rb +3 -4
  45. metadata +5 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 5b7463c46397ff888a6fa1794dda68695b8e5f78
4
- data.tar.gz: 407d19363def156021e44da618365cf8657dc94e
2
+ SHA256:
3
+ metadata.gz: 8995ca652ff77136955a0d399e6b880e84a9e9c6772c25fdd4cc16599bbb0bce
4
+ data.tar.gz: 15b18e37285c1beb76f751aea7907b684bd5faddb0d4a476f305f5da90315f8d
5
5
  SHA512:
6
- metadata.gz: 1c8f87a18cf129c5f4d31e38a3196db8503f6b752ded005734f2f42bb9a79a967aa564c0a63f939d6544de454976fc2d8fa05e43b4bc171b7a05169e6a275a68
7
- data.tar.gz: c5934315223ca60ee7cb878fe1ace02967fb6a53c949cfa78f1a4be29a7c7b20255831e949ae404ce0b0449fbd9a83323f01693ac1dfe809f437f4053f3db2da
6
+ metadata.gz: e17ecdf0d6ce04ed2ad95f0451d53f01842ee32932d1a8f58a5a3b4056a94772d6c61b8ba8493b59de5d85fd304df21f24b506053508bf816b25809d9f6ea20f
7
+ data.tar.gz: 54cf50c12052a424a1a4fba3fd3d34e6b65d8ca3f353b96fc96e3c0e9a7dfa76db5856ec1e8b8572128ec34feae59d22ef7df14674a6d05715d372c0bdfce849
@@ -6,6 +6,28 @@ scheme are considered to be bugs.
6
6
 
7
7
  [semver]: http://semver.org/spec/v2.0.0.html
8
8
 
9
+ ## [3.6.0] - 2018-08-13
10
+
11
+ [3.6.0]: https://github.com/intridea/hashie/compare/v3.5.7...v3.6.0
12
+
13
+ ### Added
14
+
15
+ * [#455](https://github.com/intridea/hashie/pull/455): Allow overriding methods when passing in a hash - [@lnestor](https://github.com/lnestor).
16
+
17
+ ### Fixed
18
+
19
+ * [#435](https://github.com/intridea/hashie/pull/435): Mash `default_proc`s are now propagated down to nested sub-Hashes - [@michaelherold](https://github.com/michaelherold).
20
+ * [#436](https://github.com/intridea/hashie/pull/436): Ensure that `Hashie::Extensions::IndifferentAccess` injects itself after a non-destructive merge - [@michaelherold](https://github.com/michaelherold).
21
+ * [#437](https://github.com/intridea/hashie/pull/437): Allow codependent properties to be set on Dash - [@michaelherold](https://github.com/michaelherold).
22
+ * [#438](https://github.com/intridea/hashie/pull/438): Fix: `NameError (uninitialized constant Hashie::Extensions::Parsers::YamlErbParser::Pathname)` in `Hashie::Mash.load` - [@onk](https://github.com/onk).
23
+ * [#457](https://github.com/intridea/hashie/pull/457): Fix `Trash` to allow it to copy properties from other properties - [@michaelherold](https://github.com/michaelherold).
24
+
25
+ ### Miscellaneous
26
+
27
+ * [#433](https://github.com/intridea/hashie/pull/433): Update Rubocop to the most recent version - [@michaelherold](https://github.com/michaelherold).
28
+ * [#434](https://github.com/intridea/hashie/pull/434): Add documentation around Mash sub-Hashes - [@michaelherold](https://github.com/michaelherold).
29
+ * [#439](https://github.com/intridea/hashie/pull/439): Add an integration spec for Elasticsearch - [@michaelherold](https://github.com/michaelherold).
30
+
9
31
  ## [3.5.7] - 2017-12-19
10
32
 
11
33
  [3.5.7]: https://github.com/intridea/hashie/compare/v3.5.6...v3.5.7
data/README.md CHANGED
@@ -1,12 +1,10 @@
1
1
  # Hashie
2
2
 
3
3
  [![Join the chat at https://gitter.im/intridea/hashie](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/intridea/hashie?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4
-
5
4
  [![Gem Version](http://img.shields.io/gem/v/hashie.svg)](http://badge.fury.io/rb/hashie)
6
5
  [![Build Status](http://img.shields.io/travis/intridea/hashie.svg)](https://travis-ci.org/intridea/hashie)
7
- [![Dependency Status](https://gemnasium.com/intridea/hashie.svg)](https://gemnasium.com/intridea/hashie)
8
- [![Code Climate](https://codeclimate.com/github/intridea/hashie.svg)](https://codeclimate.com/github/intridea/hashie)
9
- [![Coverage Status](https://codeclimate.com/github/intridea/hashie/badges/coverage.svg)](https://codeclimate.com/github/intridea/hashie)
6
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/7a0b42c8a22c945571fd/test_coverage)](https://codeclimate.com/github/intridea/hashie/test_coverage)
7
+ [![Maintainability](https://api.codeclimate.com/v1/badges/7a0b42c8a22c945571fd/maintainability)](https://codeclimate.com/github/intridea/hashie/maintainability)
10
8
 
11
9
  Hashie is a growing collection of tools that extend Hashes and make them more useful.
12
10
 
@@ -20,7 +18,7 @@ $ gem install hashie
20
18
 
21
19
  ## Upgrading
22
20
 
23
- You're reading the documentation for the stable release of Hashie, 3.5.7. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version.
21
+ You're reading the documentation for the stable release of Hashie, 3.6.0. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version.
24
22
 
25
23
  ## Hash Extensions
26
24
 
@@ -133,7 +131,7 @@ You can also use coerce from the following supertypes with `coerce_value`:
133
131
  - Integer
134
132
  - Numeric
135
133
 
136
- Hashie does not have built-in support for coercion boolean values, since Ruby does not have a built-in boolean type or standard method for to a boolean. You can coerce to booleans using a custom proc.
134
+ Hashie does not have built-in support for coercing boolean values, since Ruby does not have a built-in boolean type or standard method for coercing to a boolean. You can coerce to booleans using a custom proc.
137
135
 
138
136
  ### Coercion Proc
139
137
 
@@ -245,6 +243,26 @@ overriding.zip #=> 'a-dee-doo-dah'
245
243
  overriding.__zip #=> [[['zip', 'a-dee-doo-dah']]]
246
244
  ```
247
245
 
246
+ ### MethodOverridingInitializer
247
+
248
+ The MethodOverridingInitializer extension will override hash methods if you pass in a normal hash to the constructor. It aliases any overridden method with two leading underscores. To include only this initializing functionality, you can include the single module `Hashie::Extensions::MethodOverridingInitializer`.
249
+
250
+ ```ruby
251
+ class MyHash < Hash
252
+ end
253
+
254
+ class MyOverridingHash < Hash
255
+ include Hashie::Extensions::MethodOverridingInitializer
256
+ end
257
+
258
+ non_overriding = MyHash.new(zip: 'a-dee-doo-dah')
259
+ non_overriding.zip #=> []
260
+
261
+ overriding = MyOverridingHash.new(zip: 'a-dee-doo-dah')
262
+ overriding.zip #=> 'a-dee-doo-dah'
263
+ overriding.__zip #=> [[['zip', 'a-dee-doo-dah']]]
264
+ ```
265
+
248
266
  ### IndifferentAccess
249
267
 
250
268
  This extension can be mixed in to your Hash subclass to allow you to use Strings or Symbols interchangeably as keys; similar to the `params` hash in Rails.
@@ -470,9 +488,11 @@ mash.inspect # => <Hashie::Mash>
470
488
 
471
489
  **Note:** The `?` method will return false if a key has been set to false or nil. In order to check if a key has been set at all, use the `mash.key?('some_key')` method instead.
472
490
 
491
+ ### How does Mash handle conflicts with pre-existing methods?
492
+
473
493
  Please note that a Mash will not override methods through the use of the property-like syntax. This can lead to confusion if you expect to be able to access a Mash value through the property-like syntax for a key that conflicts with a method name. However, it protects users of your library from the unexpected behavior of those methods being overridden behind the scenes.
474
494
 
475
- ### Example:
495
+ #### Example:
476
496
 
477
497
  ```ruby
478
498
  mash = Hashie::Mash.new
@@ -481,9 +501,40 @@ mash.zip = "Method Override?"
481
501
  mash.zip # => [[["name", "My Mash"]], [["zip", "Method Override?"]]]
482
502
  ```
483
503
 
504
+ Since Mash gives you the ability to set arbitrary keys that then act as methods, Hashie logs when there is a conflict between a key and a pre-existing method. You can set the logger that this logs message to via the global Hashie logger:
505
+
506
+ ```ruby
507
+ Hashie.logger = Rails.logger
508
+ ```
509
+
510
+ You can also disable the logging in subclasses of Mash:
511
+
512
+ ```ruby
513
+ class Response < Hashie::Mash
514
+ disable_warnings
515
+ end
516
+ ```
517
+
518
+ ### How does the wrapping of Mash sub-Hashes work?
519
+
520
+ Mash duplicates any sub-Hashes that you add to it and wraps them in a Mash. This allows for infinite chaining of nested Hashes within a Mash without modifying the object(s) that are passed into the Mash. When you subclass Mash, the subclass wraps any sub-Hashes in its own class. This preserves any extensions that you mixed into the Mash subclass and allows them to work within the sub-Hashes, in addition to the main containing Mash.
521
+
522
+ #### Example:
523
+
524
+ ```ruby
525
+ mash = Hashie::Mash.new(name: "Hashie", dependencies: { rake: "< 11", rspec: "~> 3.0" })
526
+ mash.dependencies.class #=> Hashie::Mash
527
+
528
+ class MyGem < Hashie::Mash; end
529
+ my_gem = MyGem.new(name: "Hashie", dependencies: { rake: "< 11", rspec: "~> 3.0" })
530
+ my_gem.dependencies.class #=> MyGem
531
+ ```
532
+
533
+ ### What else can Mash do?
534
+
484
535
  Mash allows you also to transform any files into a Mash objects.
485
536
 
486
- ### Example:
537
+ #### Example:
487
538
 
488
539
  ```yml
489
540
  #/etc/config/settings/twitter.yml
@@ -531,20 +582,6 @@ mash = Mash.load('data/user.csv', parser: MyCustomCsvParser)
531
582
  mash[1] #=> { name: 'John', lastname: 'Doe' }
532
583
  ```
533
584
 
534
- Since Mash gives you the ability to set arbitrary keys that then act as methods, Hashie logs when there is a conflict between a key and a pre-existing method. You can set the logger that this logs message to via the global Hashie logger:
535
-
536
- ```ruby
537
- Hashie.logger = Rails.logger
538
- ```
539
-
540
- You can also disable the logging in subclasses of Mash:
541
-
542
- ```ruby
543
- class Response < Hashie::Mash
544
- disable_warnings
545
- end
546
- ```
547
-
548
585
  ### Mash Extension: KeepOriginalKeys
549
586
 
550
587
  This extension can be mixed into a Mash to keep the form of any keys passed directly into the Mash. By default, Mash converts keys to strings to give indifferent access. This extension still allows indifferent access, but keeps the form of the keys to eliminate confusion when you're not expecting the keys to change.
@@ -676,6 +713,38 @@ p = Tricky.new('trick' => 'two')
676
713
  p.trick # => NoMethodError
677
714
  ```
678
715
 
716
+ ### Potential gotchas
717
+
718
+ Because Dashes are subclasses of the built-in Ruby Hash class, the double-splat operator takes the Dash as-is without any conversion. This can lead to strange behavior when you use the double-splat operator on a Dash as the first part of a keyword list or built Hash. For example:
719
+
720
+ ```ruby
721
+ class Foo < Hashie::Dash
722
+ property :bar
723
+ end
724
+
725
+ foo = Foo.new(bar: 'baz') #=> {:bar=>"baz"}
726
+ qux = { **foo, quux: 'corge' } #=> {:bar=> "baz", :quux=>"corge"}
727
+ qux.is_a?(Foo) #=> true
728
+ qux[:quux]
729
+ #=> raise NoMethodError, "The property 'quux' is not defined for Foo."
730
+ qux.key?(:quux) #=> true
731
+ ```
732
+
733
+ You can work around this problem in two ways:
734
+
735
+ 1. Call `#to_h` on the resulting object to convert it into a Hash.
736
+ 2. Use the double-splat operator on the Dash as the last argument in the Hash literal. This will cause the resulting object to be a Hash instead of a Dash, thereby circumventing the problem.
737
+
738
+ ```ruby
739
+ qux = { **foo, quux: 'corge' }.to_h #=> {:bar=> "baz", :quux=>"corge"}
740
+ qux.is_a?(Hash) #=> true
741
+ qux[:quux] #=> "corge"
742
+
743
+ qux = { quux: 'corge', **foo } #=> {:quux=>"corge", :bar=> "baz"}
744
+ qux.is_a?(Hash) #=> true
745
+ qux[:quux] #=> "corge"
746
+ ```
747
+
679
748
  ### Dash Extension: PropertyTranslation
680
749
 
681
750
  The `Hashie::Extensions::Dash::PropertyTranslation` mixin extends a Dash with
data/Rakefile CHANGED
@@ -24,9 +24,9 @@ task :integration_specs do
24
24
  run_all_integration_specs(handler: handler, logger: ->(msg) { puts msg })
25
25
 
26
26
  if status_codes.any?
27
- $stderr.puts "#{status_codes.size} integration test(s) failed"
27
+ warn "#{status_codes.size} integration test(s) failed"
28
28
  exit status_codes.last
29
29
  end
30
30
  end
31
31
 
32
- task default: [:rubocop, :spec, :integration_specs]
32
+ task default: %i[rubocop spec integration_specs]
@@ -11,7 +11,7 @@ Gem::Specification.new do |gem|
11
11
  gem.license = 'MIT'
12
12
 
13
13
  gem.require_paths = ['lib']
14
- gem.files = %w(.yardopts CHANGELOG.md CONTRIBUTING.md LICENSE README.md UPGRADING.md Rakefile hashie.gemspec)
14
+ gem.files = %w[.yardopts CHANGELOG.md CONTRIBUTING.md LICENSE README.md UPGRADING.md Rakefile hashie.gemspec]
15
15
  gem.files += Dir['lib/**/*.rb']
16
16
  gem.files += Dir['spec/**/*.rb']
17
17
  gem.test_files = Dir['spec/**/*.rb']
@@ -75,7 +75,7 @@ module Hashie
75
75
  when Hash
76
76
  self[key] = self.class.new(self[key], self)
77
77
  else
78
- fail ChainError, 'Tried to chain into a non-hash key.'
78
+ raise ChainError, 'Tried to chain into a non-hash key.'
79
79
  end
80
80
  elsif args.any?
81
81
  merge_store(name, *args)
@@ -83,5 +83,16 @@ module Hashie
83
83
  super
84
84
  end
85
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
95
+ end
96
+ end
86
97
  end
87
98
  end
@@ -15,7 +15,7 @@ module Hashie
15
15
  class Dash < Hash
16
16
  include Hashie::Extensions::PrettyInspect
17
17
 
18
- alias_method :to_s, :inspect
18
+ alias to_s inspect
19
19
 
20
20
  # Defines a property on the Dash. Options are
21
21
  # as follows:
@@ -42,30 +42,27 @@ module Hashie
42
42
  defaults.delete property_name
43
43
  end
44
44
 
45
- unless instance_methods.map(&:to_s).include?("#{property_name}=")
46
- define_method(property_name) { |&block| self.[](property_name, &block) }
47
- property_assignment = "#{property_name}=".to_sym
48
- define_method(property_assignment) { |value| self.[]=(property_name, value) }
49
- end
45
+ define_getter_for(property_name)
46
+ define_setter_for(property_name)
50
47
 
51
- if defined? @subclasses
52
- @subclasses.each { |klass| klass.property(property_name, options) }
53
- end
48
+ @subclasses.each { |klass| klass.property(property_name, options) } if defined? @subclasses
54
49
 
55
50
  condition = options.delete(:required)
56
51
  if condition
57
52
  message = options.delete(:message) || "is required for #{name}."
58
53
  required_properties[property_name] = { condition: condition, message: message }
59
- else
60
- fail ArgumentError, 'The :message option should be used with :required option.' if options.key?(:message)
54
+ elsif options.key?(:message)
55
+ raise ArgumentError, 'The :message option should be used with :required option.'
61
56
  end
62
57
  end
63
58
 
64
59
  class << self
65
60
  attr_reader :properties, :defaults
61
+ attr_reader :getters
66
62
  attr_reader :required_properties
67
63
  end
68
64
  instance_variable_set('@properties', Set.new)
65
+ instance_variable_set('@getters', Set.new)
69
66
  instance_variable_set('@defaults', {})
70
67
  instance_variable_set('@required_properties', {})
71
68
 
@@ -73,6 +70,7 @@ module Hashie
73
70
  super
74
71
  (@subclasses ||= Set.new) << klass
75
72
  klass.instance_variable_set('@properties', properties.dup)
73
+ klass.instance_variable_set('@getters', getters.dup)
76
74
  klass.instance_variable_set('@defaults', defaults.dup)
77
75
  klass.instance_variable_set('@required_properties', required_properties.dup)
78
76
  end
@@ -89,6 +87,18 @@ module Hashie
89
87
  required_properties.key? name
90
88
  end
91
89
 
90
+ private_class_method def self.define_getter_for(property_name)
91
+ return if getters.include?(property_name)
92
+ define_method(property_name) { |&block| self.[](property_name, &block) }
93
+ getters << property_name
94
+ end
95
+
96
+ private_class_method def self.define_setter_for(property_name)
97
+ setter = :"#{property_name}="
98
+ return if instance_methods.include?(setter)
99
+ define_method(setter) { |value| self.[]=(property_name, value) }
100
+ end
101
+
92
102
  # You may initialize a Dash with an attributes hash
93
103
  # just like you would many other kinds of data objects.
94
104
  def initialize(attributes = {}, &block)
@@ -111,8 +121,8 @@ module Hashie
111
121
  assert_required_attributes_set!
112
122
  end
113
123
 
114
- alias_method :_regular_reader, :[]
115
- alias_method :_regular_writer, :[]=
124
+ alias _regular_reader []
125
+ alias _regular_writer []=
116
126
  private :_regular_reader, :_regular_writer
117
127
 
118
128
  # Retrieve a value from the Dash (will return the
@@ -160,14 +170,15 @@ module Hashie
160
170
  end
161
171
 
162
172
  def update_attributes!(attributes)
163
- initialize_attributes(attributes)
173
+ update_attributes(attributes)
164
174
 
165
175
  self.class.defaults.each_pair do |prop, value|
176
+ next unless self[prop].nil?
166
177
  self[prop] = begin
167
178
  value.dup
168
179
  rescue TypeError
169
180
  value
170
- end if self[prop].nil?
181
+ end
171
182
  end
172
183
  assert_required_attributes_set!
173
184
  end
@@ -175,9 +186,18 @@ module Hashie
175
186
  private
176
187
 
177
188
  def initialize_attributes(attributes)
189
+ return unless attributes
190
+
191
+ cleaned_attributes = attributes.reject { |_attr, value| value.nil? }
192
+ update_attributes(cleaned_attributes)
193
+ end
194
+
195
+ def update_attributes(attributes)
196
+ return unless attributes
197
+
178
198
  attributes.each_pair do |att, value|
179
199
  self[att] = value
180
- end if attributes
200
+ end
181
201
  end
182
202
 
183
203
  def assert_property_exists!(property)
@@ -199,11 +219,11 @@ module Hashie
199
219
  end
200
220
 
201
221
  def fail_property_required_error!(property)
202
- fail ArgumentError, "The property '#{property}' #{self.class.required_properties[property][:message]}"
222
+ raise ArgumentError, "The property '#{property}' #{self.class.required_properties[property][:message]}"
203
223
  end
204
224
 
205
225
  def fail_no_property_error!(property)
206
- fail NoMethodError, "The property '#{property}' is not defined for #{self.class.name}."
226
+ raise NoMethodError, "The property '#{property}' is not defined for #{self.class.name}."
207
227
  end
208
228
 
209
229
  def required?(property)
@@ -211,9 +231,9 @@ module Hashie
211
231
 
212
232
  condition = self.class.required_properties[property][:condition]
213
233
  case condition
214
- when Proc then !!(instance_exec(&condition))
215
- when Symbol then !!(send(condition))
216
- else !!(condition)
234
+ when Proc then !!instance_exec(&condition)
235
+ when Symbol then !!send(condition)
236
+ else !!condition
217
237
  end
218
238
  end
219
239
  end
@@ -10,14 +10,14 @@ module Hashie
10
10
  Rational => :to_r,
11
11
  String => :to_s,
12
12
  Symbol => :to_sym
13
- }
13
+ }.freeze
14
14
 
15
15
  ABSTRACT_CORE_TYPES = if RubyVersion.new(RUBY_VERSION) >= RubyVersion.new('2.4.0')
16
16
  { Numeric => [Integer, Float, Complex, Rational] }
17
17
  else
18
18
  {
19
- Integer => [Fixnum, Bignum],
20
- Numeric => [Fixnum, Bignum, Float, Complex, Rational]
19
+ Integer => [Fixnum, Bignum], # rubocop:disable Lint/UnifiedInteger
20
+ Numeric => [Fixnum, Bignum, Float, Complex, Rational] # rubocop:disable Lint/UnifiedInteger
21
21
  }
22
22
  end
23
23
 
@@ -78,7 +78,7 @@ module Hashie
78
78
  attrs.each { |key| key_coercions[key] = into }
79
79
  end
80
80
 
81
- alias_method :coerce_keys, :coerce_key
81
+ alias coerce_keys coerce_key
82
82
 
83
83
  # Returns a hash of any existing key coercions.
84
84
  def key_coercions
@@ -180,7 +180,7 @@ module Hashie
180
180
  type.new(value)
181
181
  end
182
182
  else
183
- fail TypeError, "#{type} is not a coercable type"
183
+ raise TypeError, "#{type} is not a coercable type"
184
184
  end
185
185
  end
186
186
 
@@ -40,7 +40,7 @@ module Hashie
40
40
  module PropertyTranslation
41
41
  def self.included(base)
42
42
  base.instance_variable_set(:@transforms, {})
43
- base.instance_variable_set(:@translations_hash, {})
43
+ base.instance_variable_set(:@translations_hash, ::Hash.new { |hash, key| hash[key] = {} })
44
44
  base.extend(ClassMethods)
45
45
  base.send(:include, InstanceMethods)
46
46
  end
@@ -72,23 +72,16 @@ module Hashie
72
72
  def property(property_name, options = {})
73
73
  super
74
74
 
75
- if options[:from]
76
- if property_name == options[:from]
77
- fail ArgumentError, "Property name (#{property_name}) and :from option must not be the same"
78
- end
79
-
80
- translations_hash[options[:from]] ||= {}
81
- translations_hash[options[:from]][property_name] = options[:with] || options[:transform_with]
75
+ from = options[:from]
76
+ converter = options[:with]
77
+ transformer = options[:transform_with]
82
78
 
83
- define_method "#{options[:from]}=" do |val|
84
- self.class.translations_hash[options[:from]].each do |name, with|
85
- self[name] = with.respond_to?(:call) ? with.call(val) : val
86
- end
87
- end
88
- else
89
- if options[:transform_with].respond_to? :call
90
- transforms[property_name] = options[:transform_with]
91
- end
79
+ if from
80
+ fail_self_transformation_error!(property_name) if property_name == from
81
+ define_translation(from, property_name, converter || transformer)
82
+ define_writer_for_source_property(from)
83
+ elsif valid_transformer?(transformer)
84
+ transforms[property_name] = transformer
92
85
  end
93
86
  end
94
87
 
@@ -105,26 +98,49 @@ module Hashie
105
98
  end
106
99
 
107
100
  def translations
108
- @translations ||= {}.tap do |h|
101
+ @translations ||= {}.tap do |translations|
109
102
  translations_hash.each do |(property_name, property_translations)|
110
- if property_translations.size > 1
111
- h[property_name] = property_translations.keys
112
- else
113
- h[property_name] = property_translations.keys.first
114
- end
103
+ translations[property_name] =
104
+ if property_translations.size > 1
105
+ property_translations.keys
106
+ else
107
+ property_translations.keys.first
108
+ end
115
109
  end
116
110
  end
117
111
  end
118
112
 
119
113
  def inverse_translations
120
- @inverse_translations ||= {}.tap do |h|
114
+ @inverse_translations ||= {}.tap do |translations|
121
115
  translations_hash.each do |(property_name, property_translations)|
122
- property_translations.keys.each do |k|
123
- h[k] = property_name
116
+ property_translations.each_key do |key|
117
+ translations[key] = property_name
124
118
  end
125
119
  end
126
120
  end
127
121
  end
122
+
123
+ private
124
+
125
+ def define_translation(from, property_name, translator)
126
+ translations_hash[from][property_name] = translator
127
+ end
128
+
129
+ def define_writer_for_source_property(property)
130
+ define_method "#{property}=" do |val|
131
+ __translations[property].each do |name, with|
132
+ self[name] = with.respond_to?(:call) ? with.call(val) : val
133
+ end
134
+ end
135
+ end
136
+
137
+ def fail_self_transformation_error!(property_name)
138
+ raise ArgumentError, "Property name (#{property_name}) and :from option must not be the same"
139
+ end
140
+
141
+ def valid_transformer?(transformer)
142
+ transformer.respond_to? :call
143
+ end
128
144
  end
129
145
 
130
146
  module InstanceMethods
@@ -134,6 +150,7 @@ module Hashie
134
150
  def []=(property, value)
135
151
  if self.class.translation_exists? property
136
152
  send("#{property}=", value)
153
+ super(property, value) if self.class.properties.include?(property)
137
154
  elsif self.class.transformation_exists? property
138
155
  super property, self.class.transformed_property(property, value)
139
156
  elsif property_exists? property
@@ -158,6 +175,12 @@ module Hashie
158
175
  fail_no_property_error!(property) unless self.class.property?(property)
159
176
  true
160
177
  end
178
+
179
+ private
180
+
181
+ def __translations
182
+ self.class.translations_hash
183
+ end
161
184
  end
162
185
  end
163
186
  end