hashie 3.4.3 → 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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +516 -129
- data/CONTRIBUTING.md +24 -7
- data/LICENSE +1 -1
- data/README.md +408 -50
- data/Rakefile +18 -1
- data/UPGRADING.md +157 -7
- data/hashie.gemspec +14 -8
- data/lib/hashie/array.rb +21 -0
- data/lib/hashie/clash.rb +24 -12
- data/lib/hashie/dash.rb +56 -31
- data/lib/hashie/extensions/active_support/core_ext/hash.rb +14 -0
- data/lib/hashie/extensions/array/pretty_inspect.rb +19 -0
- data/lib/hashie/extensions/coercion.rb +30 -17
- data/lib/hashie/extensions/dash/indifferent_access.rb +30 -1
- data/lib/hashie/extensions/dash/predefined_values.rb +88 -0
- data/lib/hashie/extensions/dash/property_translation.rb +59 -28
- data/lib/hashie/extensions/deep_fetch.rb +5 -3
- data/lib/hashie/extensions/deep_find.rb +14 -5
- data/lib/hashie/extensions/deep_locate.rb +40 -21
- data/lib/hashie/extensions/deep_merge.rb +26 -10
- data/lib/hashie/extensions/ignore_undeclared.rb +6 -4
- data/lib/hashie/extensions/indifferent_access.rb +49 -8
- data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
- data/lib/hashie/extensions/mash/define_accessors.rb +90 -0
- data/lib/hashie/extensions/mash/keep_original_keys.rb +53 -0
- data/lib/hashie/extensions/mash/permissive_respond_to.rb +61 -0
- data/lib/hashie/extensions/mash/safe_assignment.rb +3 -1
- data/lib/hashie/extensions/mash/symbolize_keys.rb +38 -0
- data/lib/hashie/extensions/method_access.rb +77 -19
- data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +29 -5
- data/lib/hashie/extensions/ruby_version.rb +60 -0
- data/lib/hashie/extensions/ruby_version_check.rb +21 -0
- data/lib/hashie/extensions/strict_key_access.rb +16 -13
- data/lib/hashie/extensions/stringify_keys.rb +1 -1
- data/lib/hashie/extensions/symbolize_keys.rb +13 -2
- data/lib/hashie/hash.rb +18 -11
- data/lib/hashie/logger.rb +18 -0
- data/lib/hashie/mash.rb +177 -43
- data/lib/hashie/railtie.rb +21 -0
- data/lib/hashie/rash.rb +7 -7
- data/lib/hashie/utils.rb +44 -0
- data/lib/hashie/version.rb +1 -1
- data/lib/hashie.rb +33 -17
- metadata +28 -95
- data/spec/hashie/clash_spec.rb +0 -48
- data/spec/hashie/dash_spec.rb +0 -513
- data/spec/hashie/extensions/autoload_spec.rb +0 -24
- data/spec/hashie/extensions/coercion_spec.rb +0 -625
- data/spec/hashie/extensions/dash/coercion_spec.rb +0 -13
- data/spec/hashie/extensions/dash/indifferent_access_spec.rb +0 -84
- data/spec/hashie/extensions/deep_fetch_spec.rb +0 -97
- data/spec/hashie/extensions/deep_find_spec.rb +0 -45
- data/spec/hashie/extensions/deep_locate_spec.rb +0 -124
- data/spec/hashie/extensions/deep_merge_spec.rb +0 -65
- data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -46
- data/spec/hashie/extensions/indifferent_access_spec.rb +0 -219
- data/spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb +0 -208
- data/spec/hashie/extensions/key_conversion_spec.rb +0 -12
- data/spec/hashie/extensions/mash/safe_assignment_spec.rb +0 -50
- data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
- data/spec/hashie/extensions/method_access_spec.rb +0 -184
- data/spec/hashie/extensions/strict_key_access_spec.rb +0 -110
- data/spec/hashie/extensions/stringify_keys_spec.rb +0 -124
- data/spec/hashie/extensions/symbolize_keys_spec.rb +0 -129
- data/spec/hashie/hash_spec.rb +0 -84
- data/spec/hashie/mash_spec.rb +0 -680
- data/spec/hashie/parsers/yaml_erb_parser_spec.rb +0 -29
- data/spec/hashie/rash_spec.rb +0 -77
- data/spec/hashie/trash_spec.rb +0 -268
- data/spec/hashie/version_spec.rb +0 -7
- data/spec/spec_helper.rb +0 -16
- data/spec/support/module_context.rb +0 -11
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
14
|
RuboCop::RakeTask.new(:rubocop)
|
14
15
|
|
15
|
-
|
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]
|
data/UPGRADING.md
CHANGED
@@ -1,6 +1,158 @@
|
|
1
1
|
Upgrading Hashie
|
2
2
|
================
|
3
3
|
|
4
|
+
### Upgrading to 5.0.0
|
5
|
+
|
6
|
+
#### Mash initialization key conversion
|
7
|
+
|
8
|
+
Mash initialization now only converts to string keys which can be represented as symbols.
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
Hashie::Mash.new(
|
12
|
+
{foo: "bar"} => "baz",
|
13
|
+
"1" => "one string",
|
14
|
+
:"1" => "one sym",
|
15
|
+
1 => "one num"
|
16
|
+
)
|
17
|
+
|
18
|
+
# Before
|
19
|
+
{"{:foo=>\"bar\"}"=>"baz", "1"=>"one num"}
|
20
|
+
|
21
|
+
# After
|
22
|
+
{{:foo=>"bar"}=>"baz", "1"=>"one sym", 1=>"one num"}
|
23
|
+
```
|
24
|
+
|
25
|
+
#### Mash#dig with numeric keys
|
26
|
+
|
27
|
+
`Hashie::Mash#dig` no longer considers numeric keys for indifferent access.
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
my_mash = Hashie::Mash.new("1" => "a") # => {"1"=>"a"}
|
31
|
+
|
32
|
+
my_mash.dig("1") # => "a"
|
33
|
+
my_mash.dig(:"1") # => "a"
|
34
|
+
|
35
|
+
# Before
|
36
|
+
my_mash.dig(1) # => "a"
|
37
|
+
|
38
|
+
# After
|
39
|
+
my_mash.dig(1) # => nil
|
40
|
+
```
|
41
|
+
|
42
|
+
### Upgrading to 4.0.0
|
43
|
+
|
44
|
+
#### Non-destructive Hash methods called on Mash
|
45
|
+
|
46
|
+
The following non-destructive Hash methods called on Mash will now return an instance of the class it was called on.
|
47
|
+
|
48
|
+
| method | ruby |
|
49
|
+
| ----------------- | ---- |
|
50
|
+
| #compact | |
|
51
|
+
| #invert | |
|
52
|
+
| #reject | |
|
53
|
+
| #select | |
|
54
|
+
| #slice | 2.5 |
|
55
|
+
| #transform_keys | 2.5 |
|
56
|
+
| #transform_values | 2.4 |
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
class Parents < Hashie::Mash; end
|
60
|
+
|
61
|
+
parents = Parents.new(father: 'Dad', mother: 'Mom')
|
62
|
+
cool_parents = parents.transform_values { |v| v + v[-1] + 'io'}
|
63
|
+
|
64
|
+
p cool_parents
|
65
|
+
|
66
|
+
# before:
|
67
|
+
{"father"=>"Daddio", "mother"=>"Mommio"}
|
68
|
+
=> {"father"=>"Daddio", "mother"=>"Mommio"}
|
69
|
+
|
70
|
+
# after:
|
71
|
+
#<Parents father="Daddio" mother="Mommio">
|
72
|
+
=> {"father"=>"Dad", "mother"=>"Mom"}
|
73
|
+
```
|
74
|
+
|
75
|
+
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.
|
76
|
+
|
77
|
+
#### Ruby 2.6: Mash#merge and Mash#merge!
|
78
|
+
|
79
|
+
In Ruby > 2.6.0, Hashie now supports passing multiple hash and Mash objects to Mash#merge and Mash#merge!.
|
80
|
+
|
81
|
+
#### Hashie::Mash::CannotDisableMashWarnings error class is removed
|
82
|
+
|
83
|
+
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
|
84
|
+
|
85
|
+
### Upgrading to 3.7.0
|
86
|
+
|
87
|
+
#### Mash#load takes options
|
88
|
+
|
89
|
+
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.
|
90
|
+
|
91
|
+
For example, `Hashie::Extensions::Parsers::YamlErbParser` now accepts `permitted_classes`, `permitted_symbols` and `aliases` options.
|
92
|
+
|
93
|
+
Before:
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
class Hashie::Extensions::Parsers::YamlErbParser
|
97
|
+
def initialize(file_path)
|
98
|
+
@file_path = file_path
|
99
|
+
end
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
After:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
class Hashie::Extensions::Parsers::YamlErbParser
|
107
|
+
def initialize(file_path, options = {})
|
108
|
+
@file_path = file_path
|
109
|
+
@options = options
|
110
|
+
end
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
Options can now be passed into `Mash#load`.
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
Mash.load(filename, permitted_classes: [])
|
118
|
+
```
|
119
|
+
|
120
|
+
### Upgrading to 3.5.2
|
121
|
+
|
122
|
+
#### Disable logging in Mash subclasses
|
123
|
+
|
124
|
+
If you subclass `Hashie::Mash`, you can now disable the logging we do about
|
125
|
+
overriding existing methods with keys. This looks like:
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
class MyMash < Hashie::Mash
|
129
|
+
disable_warnings
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
### Upgrading to 3.4.7
|
134
|
+
|
135
|
+
#### Procs as default values for Dash
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
class MyHash < Hashie::Dash
|
139
|
+
property :time, default: -> { Time.now }
|
140
|
+
end
|
141
|
+
```
|
142
|
+
|
143
|
+
In versions < 3.4.7 `Time.now` will be evaluated when `time` property is accessed directly first time.
|
144
|
+
In version >= 3.4.7 `Time.now` is evaluated in time of object initialization.
|
145
|
+
### Upgrading to 3.4.4
|
146
|
+
|
147
|
+
#### Mash subclasses and reverse_merge
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
class MyMash < Hashie::Mash
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
In versions >= 3.4.4 `MyMash#reverse_merge` returns an instance of `MyMash` but in previous versions it was a `Hashie::Mash` instance.
|
155
|
+
|
4
156
|
### Upgrading to 3.2.2
|
5
157
|
|
6
158
|
#### Testing if key defined
|
@@ -29,7 +181,7 @@ h.abb? # => false
|
|
29
181
|
|
30
182
|
#### Possible coercion changes
|
31
183
|
|
32
|
-
The improvements made to coercions in version 3.2.1 [issue #200](https://github.com/
|
184
|
+
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.
|
33
185
|
|
34
186
|
**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.
|
35
187
|
|
@@ -45,7 +197,7 @@ Applications that were attempting to rescuing the internal errors should be upda
|
|
45
197
|
|
46
198
|
#### Compatibility with Rails 4 Strong Parameters
|
47
199
|
|
48
|
-
Version 2.1 introduced support to prevent default Rails 4 mass-assignment protection behavior. This was [issue #89](https://github.com/
|
200
|
+
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.
|
49
201
|
|
50
202
|
To enable 2.1 compatible behavior with Rails 4, use the [hashie_rails](http://rubygems.org/gems/hashie_rails) gem.
|
51
203
|
|
@@ -53,7 +205,7 @@ To enable 2.1 compatible behavior with Rails 4, use the [hashie_rails](http://ru
|
|
53
205
|
gem 'hashie_rails'
|
54
206
|
```
|
55
207
|
|
56
|
-
See [#154](https://github.com/
|
208
|
+
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.
|
57
209
|
|
58
210
|
#### Key Conversions in Hashie::Dash and Hashie::Trash
|
59
211
|
|
@@ -81,7 +233,7 @@ p.inspect # => { 'name' => 'dB.' }
|
|
81
233
|
p.to_hash # => { 'name' => 'dB.' }
|
82
234
|
```
|
83
235
|
|
84
|
-
It was not possible to achieve the behavior of preserving keys, as described in [issue #151](https://github.com/
|
236
|
+
It was not possible to achieve the behavior of preserving keys, as described in [issue #151](https://github.com/hashie/hashie/issues/151).
|
85
237
|
|
86
238
|
Version 2.2 does not perform this conversion by default.
|
87
239
|
|
@@ -128,6 +280,4 @@ instance.to_hash # => { :first => 'First', "last" => 'Last' }
|
|
128
280
|
|
129
281
|
The behavior with `symbolize_keys` and `stringify_keys` is unchanged.
|
130
282
|
|
131
|
-
See [#152](https://github.com/
|
132
|
-
|
133
|
-
|
283
|
+
See [#152](https://github.com/hashie/hashie/pull/152) for more information.
|
data/hashie.gemspec
CHANGED
@@ -7,16 +7,22 @@ Gem::Specification.new do |gem|
|
|
7
7
|
gem.email = ['michael@intridea.com', 'jollyjerry@gmail.com']
|
8
8
|
gem.description = 'Hashie is a collection of classes and mixins that make hashes more powerful.'
|
9
9
|
gem.summary = 'Your friendly neighborhood hash library.'
|
10
|
-
gem.homepage = 'https://github.com/
|
10
|
+
gem.homepage = 'https://github.com/hashie/hashie'
|
11
11
|
gem.license = 'MIT'
|
12
12
|
|
13
13
|
gem.require_paths = ['lib']
|
14
|
-
gem.files
|
15
|
-
gem.files
|
16
|
-
gem.files
|
17
|
-
gem.test_files = Dir['spec/**/*.rb']
|
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']
|
18
17
|
|
19
|
-
gem.
|
20
|
-
|
21
|
-
|
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
|
26
|
+
|
27
|
+
gem.add_development_dependency 'bundler'
|
22
28
|
end
|
data/lib/hashie/array.rb
ADDED
@@ -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
|
data/lib/hashie/clash.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
78
|
+
raise ChainError, 'Tried to chain into a non-hash key.'
|
77
79
|
end
|
78
|
-
|
79
|
-
self[key]
|
80
80
|
elsif args.any?
|
81
|
-
|
82
|
-
|
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
|
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,26 +87,29 @@ 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)
|
95
105
|
super(&block)
|
96
106
|
|
97
|
-
self.class.defaults.each_pair do |prop, value|
|
98
|
-
self[prop] = begin
|
99
|
-
val = value.dup
|
100
|
-
val.is_a?(Proc) && val.arity > 0 ? val.call(self) : val
|
101
|
-
rescue TypeError
|
102
|
-
value
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
107
|
initialize_attributes(attributes)
|
107
108
|
assert_required_attributes_set!
|
108
109
|
end
|
109
110
|
|
110
|
-
|
111
|
-
|
111
|
+
alias _regular_reader []
|
112
|
+
alias _regular_writer []=
|
112
113
|
private :_regular_reader, :_regular_writer
|
113
114
|
|
114
115
|
# Retrieve a value from the Dash (will return the
|
@@ -155,25 +156,48 @@ module Hashie
|
|
155
156
|
self
|
156
157
|
end
|
157
158
|
|
159
|
+
def to_h
|
160
|
+
defaults = ::Hash[self.class.properties.map { |prop| [prop, self.class.defaults[prop]] }]
|
161
|
+
|
162
|
+
defaults.merge(self)
|
163
|
+
end
|
164
|
+
alias to_hash to_h
|
165
|
+
|
158
166
|
def update_attributes!(attributes)
|
159
|
-
|
167
|
+
update_attributes(attributes)
|
160
168
|
|
161
169
|
self.class.defaults.each_pair do |prop, value|
|
170
|
+
next unless fetch(prop, nil).nil?
|
162
171
|
self[prop] = begin
|
163
|
-
value.dup
|
172
|
+
val = value.dup
|
173
|
+
if val.is_a?(Proc)
|
174
|
+
val.arity == 1 ? val.call(self) : val.call
|
175
|
+
else
|
176
|
+
val
|
177
|
+
end
|
164
178
|
rescue TypeError
|
165
179
|
value
|
166
|
-
end
|
180
|
+
end
|
167
181
|
end
|
182
|
+
|
168
183
|
assert_required_attributes_set!
|
169
184
|
end
|
170
185
|
|
171
186
|
private
|
172
187
|
|
173
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
|
+
|
174
198
|
attributes.each_pair do |att, value|
|
175
199
|
self[att] = value
|
176
|
-
end
|
200
|
+
end
|
177
201
|
end
|
178
202
|
|
179
203
|
def assert_property_exists!(property)
|
@@ -195,11 +219,12 @@ module Hashie
|
|
195
219
|
end
|
196
220
|
|
197
221
|
def fail_property_required_error!(property)
|
198
|
-
|
222
|
+
raise ArgumentError,
|
223
|
+
"The property '#{property}' #{self.class.required_properties[property][:message]}"
|
199
224
|
end
|
200
225
|
|
201
226
|
def fail_no_property_error!(property)
|
202
|
-
|
227
|
+
raise NoMethodError, "The property '#{property}' is not defined for #{self.class.name}."
|
203
228
|
end
|
204
229
|
|
205
230
|
def required?(property)
|
@@ -207,9 +232,9 @@ module Hashie
|
|
207
232
|
|
208
233
|
condition = self.class.required_properties[property][:condition]
|
209
234
|
case condition
|
210
|
-
when Proc then !!
|
211
|
-
when Symbol then !!
|
212
|
-
else !!
|
235
|
+
when Proc then !!instance_exec(&condition)
|
236
|
+
when Symbol then !!send(condition)
|
237
|
+
else !!condition
|
213
238
|
end
|
214
239
|
end
|
215
240
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Hashie
|
2
|
+
module Extensions
|
3
|
+
module Array
|
4
|
+
module PrettyInspect
|
5
|
+
def self.included(base)
|
6
|
+
base.send :alias_method, :array_inspect, :inspect
|
7
|
+
base.send :alias_method, :inspect, :hashie_inspect
|
8
|
+
end
|
9
|
+
|
10
|
+
def hashie_inspect
|
11
|
+
ret = "#<#{self.class} ["
|
12
|
+
ret << to_a.map(&:inspect).join(', ')
|
13
|
+
ret << ']>'
|
14
|
+
ret
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,5 +1,9 @@
|
|
1
1
|
module Hashie
|
2
|
-
class CoercionError < StandardError
|
2
|
+
class CoercionError < StandardError
|
3
|
+
def initialize(key, value, into, message)
|
4
|
+
super("Cannot coerce property #{key.inspect} from #{value.class} to #{into}: #{message}")
|
5
|
+
end
|
6
|
+
end
|
3
7
|
|
4
8
|
module Extensions
|
5
9
|
module Coercion
|
@@ -10,18 +14,24 @@ module Hashie
|
|
10
14
|
Rational => :to_r,
|
11
15
|
String => :to_s,
|
12
16
|
Symbol => :to_sym
|
13
|
-
}
|
17
|
+
}.freeze
|
14
18
|
|
15
|
-
ABSTRACT_CORE_TYPES =
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
+
ABSTRACT_CORE_TYPES =
|
20
|
+
if RubyVersion.new(RUBY_VERSION) >= RubyVersion.new('2.4.0')
|
21
|
+
{ Numeric => [Integer, Float, Complex, Rational] }
|
22
|
+
else
|
23
|
+
{
|
24
|
+
Integer => [Fixnum, Bignum],
|
25
|
+
Numeric => [Fixnum, Bignum, Float, Complex, Rational]
|
26
|
+
}
|
27
|
+
end
|
19
28
|
|
20
29
|
def self.included(base)
|
21
30
|
base.send :include, InstanceMethods
|
22
|
-
base.extend ClassMethods
|
23
|
-
|
24
|
-
|
31
|
+
base.extend ClassMethods
|
32
|
+
unless base.method_defined?(:set_value_without_coercion)
|
33
|
+
base.send :alias_method, :set_value_without_coercion, :[]=
|
34
|
+
end
|
25
35
|
base.send :alias_method, :[]=, :set_value_with_coercion
|
26
36
|
end
|
27
37
|
|
@@ -33,7 +43,7 @@ module Hashie
|
|
33
43
|
begin
|
34
44
|
value = self.class.fetch_coercion(into).call(value)
|
35
45
|
rescue NoMethodError, TypeError => e
|
36
|
-
raise CoercionError,
|
46
|
+
raise CoercionError.new(key, value, into, e.message)
|
37
47
|
end
|
38
48
|
end
|
39
49
|
|
@@ -74,7 +84,7 @@ module Hashie
|
|
74
84
|
attrs.each { |key| key_coercions[key] = into }
|
75
85
|
end
|
76
86
|
|
77
|
-
|
87
|
+
alias coerce_keys coerce_key
|
78
88
|
|
79
89
|
# Returns a hash of any existing key coercions.
|
80
90
|
def key_coercions
|
@@ -93,7 +103,8 @@ module Hashie
|
|
93
103
|
#
|
94
104
|
# @param [Class] from the type you would like coerced.
|
95
105
|
# @param [Class] into the class into which you would like the value coerced.
|
96
|
-
# @option options [Boolean] :strict (true) whether use exact source class
|
106
|
+
# @option options [Boolean] :strict (true) whether use exact source class
|
107
|
+
# only or include ancestors
|
97
108
|
#
|
98
109
|
# @example Coerce all hashes into this special type of hash
|
99
110
|
# class SpecialHash < Hash
|
@@ -111,7 +122,7 @@ module Hashie
|
|
111
122
|
options = { strict: true }.merge(options)
|
112
123
|
|
113
124
|
if ABSTRACT_CORE_TYPES.key? from
|
114
|
-
ABSTRACT_CORE_TYPES[from].each do |
|
125
|
+
ABSTRACT_CORE_TYPES[from].each do |type|
|
115
126
|
coerce_value type, into, options
|
116
127
|
end
|
117
128
|
end
|
@@ -130,6 +141,7 @@ module Hashie
|
|
130
141
|
def strict_value_coercions
|
131
142
|
@strict_value_coercions ||= {}
|
132
143
|
end
|
144
|
+
|
133
145
|
# Return all value coercions that have the :strict rule as false.
|
134
146
|
def lenient_value_coercions
|
135
147
|
@lenient_value_coercions ||= {}
|
@@ -154,11 +166,12 @@ module Hashie
|
|
154
166
|
|
155
167
|
def build_coercion(type)
|
156
168
|
if type.is_a? Enumerable
|
157
|
-
if type.class
|
169
|
+
if type.class == ::Hash
|
158
170
|
type, key_type, value_type = type.class, *type.first
|
159
171
|
build_hash_coercion(type, key_type, value_type)
|
160
|
-
else
|
161
|
-
|
172
|
+
else
|
173
|
+
value_type = type.first
|
174
|
+
type = type.class
|
162
175
|
build_container_coercion(type, value_type)
|
163
176
|
end
|
164
177
|
elsif CORE_TYPES.key? type
|
@@ -174,7 +187,7 @@ module Hashie
|
|
174
187
|
type.new(value)
|
175
188
|
end
|
176
189
|
else
|
177
|
-
|
190
|
+
raise TypeError, "#{type} is not a coercable type"
|
178
191
|
end
|
179
192
|
end
|
180
193
|
|