hashie 3.5.7 → 3.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +22 -0
- data/README.md +91 -22
- data/Rakefile +2 -2
- data/hashie.gemspec +1 -1
- data/lib/hashie/clash.rb +12 -1
- data/lib/hashie/dash.rb +41 -21
- data/lib/hashie/extensions/coercion.rb +5 -5
- data/lib/hashie/extensions/dash/property_translation.rb +49 -26
- data/lib/hashie/extensions/deep_fetch.rb +1 -1
- data/lib/hashie/extensions/deep_find.rb +2 -2
- data/lib/hashie/extensions/deep_locate.rb +4 -5
- data/lib/hashie/extensions/deep_merge.rb +8 -9
- data/lib/hashie/extensions/indifferent_access.rb +7 -5
- data/lib/hashie/extensions/mash/keep_original_keys.rb +3 -5
- data/lib/hashie/extensions/mash/safe_assignment.rb +1 -1
- data/lib/hashie/extensions/mash/symbolize_keys.rb +1 -1
- data/lib/hashie/extensions/method_access.rb +47 -17
- data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +3 -1
- data/lib/hashie/extensions/strict_key_access.rb +8 -9
- data/lib/hashie/extensions/stringify_keys.rb +1 -1
- data/lib/hashie/extensions/symbolize_keys.rb +1 -1
- data/lib/hashie/hash.rb +4 -4
- data/lib/hashie/mash.rb +22 -22
- data/lib/hashie/rash.rb +5 -5
- data/lib/hashie/version.rb +1 -1
- data/spec/hashie/array_spec.rb +1 -1
- data/spec/hashie/dash_spec.rb +27 -2
- data/spec/hashie/extensions/coercion_spec.rb +20 -12
- data/spec/hashie/extensions/deep_locate_spec.rb +1 -1
- data/spec/hashie/extensions/deep_merge_spec.rb +1 -1
- data/spec/hashie/extensions/indifferent_access_spec.rb +20 -7
- data/spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb +5 -5
- data/spec/hashie/extensions/method_access_spec.rb +42 -4
- data/spec/hashie/extensions/stringify_keys_spec.rb +4 -4
- data/spec/hashie/extensions/symbolize_keys_spec.rb +3 -3
- data/spec/hashie/mash_spec.rb +16 -8
- data/spec/hashie/parsers/yaml_erb_parser_spec.rb +3 -3
- data/spec/hashie/rash_spec.rb +2 -2
- data/spec/hashie/trash_spec.rb +61 -1
- data/spec/integration/elasticsearch/integration_spec.rb +40 -0
- data/spec/integration/omniauth-oauth2/app.rb +3 -4
- data/spec/integration/omniauth-oauth2/some_site.rb +2 -2
- data/spec/integration/rails/app.rb +3 -4
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8995ca652ff77136955a0d399e6b880e84a9e9c6772c25fdd4cc16599bbb0bce
|
4
|
+
data.tar.gz: 15b18e37285c1beb76f751aea7907b684bd5faddb0d4a476f305f5da90315f8d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e17ecdf0d6ce04ed2ad95f0451d53f01842ee32932d1a8f58a5a3b4056a94772d6c61b8ba8493b59de5d85fd304df21f24b506053508bf816b25809d9f6ea20f
|
7
|
+
data.tar.gz: 54cf50c12052a424a1a4fba3fd3d34e6b65d8ca3f353b96fc96e3c0e9a7dfa76db5856ec1e8b8572128ec34feae59d22ef7df14674a6d05715d372c0bdfce849
|
data/CHANGELOG.md
CHANGED
@@ -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
|
-
[![
|
8
|
-
[![
|
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.
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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: [
|
32
|
+
task default: %i[rubocop spec integration_specs]
|
data/hashie.gemspec
CHANGED
@@ -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
|
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']
|
data/lib/hashie/clash.rb
CHANGED
@@ -75,7 +75,7 @@ module Hashie
|
|
75
75
|
when Hash
|
76
76
|
self[key] = self.class.new(self[key], self)
|
77
77
|
else
|
78
|
-
|
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
|
data/lib/hashie/dash.rb
CHANGED
@@ -15,7 +15,7 @@ module Hashie
|
|
15
15
|
class Dash < Hash
|
16
16
|
include Hashie::Extensions::PrettyInspect
|
17
17
|
|
18
|
-
|
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
|
-
|
46
|
-
|
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
|
-
|
60
|
-
|
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
|
-
|
115
|
-
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
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 !!
|
215
|
-
when Symbol then !!
|
216
|
-
else !!
|
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
|
-
|
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
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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 |
|
101
|
+
@translations ||= {}.tap do |translations|
|
109
102
|
translations_hash.each do |(property_name, property_translations)|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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 |
|
114
|
+
@inverse_translations ||= {}.tap do |translations|
|
121
115
|
translations_hash.each do |(property_name, property_translations)|
|
122
|
-
property_translations.
|
123
|
-
|
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
|