hashie 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +213 -187
  3. data/CONTRIBUTING.md +13 -6
  4. data/README.md +33 -9
  5. data/UPGRADING.md +5 -5
  6. data/hashie.gemspec +11 -6
  7. data/lib/hashie.rb +1 -0
  8. data/lib/hashie/extensions/dash/property_translation.rb +1 -1
  9. data/lib/hashie/extensions/deep_merge.rb +18 -1
  10. data/lib/hashie/extensions/mash/permissive_respond_to.rb +61 -0
  11. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +19 -2
  12. data/lib/hashie/extensions/ruby_version_check.rb +5 -1
  13. data/lib/hashie/mash.rb +31 -26
  14. data/lib/hashie/utils.rb +28 -0
  15. data/lib/hashie/version.rb +1 -1
  16. metadata +16 -131
  17. data/spec/hashie/array_spec.rb +0 -29
  18. data/spec/hashie/clash_spec.rb +0 -70
  19. data/spec/hashie/dash_spec.rb +0 -608
  20. data/spec/hashie/extensions/autoload_spec.rb +0 -24
  21. data/spec/hashie/extensions/coercion_spec.rb +0 -648
  22. data/spec/hashie/extensions/dash/coercion_spec.rb +0 -13
  23. data/spec/hashie/extensions/dash/indifferent_access_spec.rb +0 -84
  24. data/spec/hashie/extensions/deep_fetch_spec.rb +0 -97
  25. data/spec/hashie/extensions/deep_find_spec.rb +0 -144
  26. data/spec/hashie/extensions/deep_locate_spec.rb +0 -138
  27. data/spec/hashie/extensions/deep_merge_spec.rb +0 -74
  28. data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -48
  29. data/spec/hashie/extensions/indifferent_access_spec.rb +0 -295
  30. data/spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb +0 -208
  31. data/spec/hashie/extensions/key_conversion_spec.rb +0 -12
  32. data/spec/hashie/extensions/mash/define_accessors_spec.rb +0 -90
  33. data/spec/hashie/extensions/mash/keep_original_keys_spec.rb +0 -46
  34. data/spec/hashie/extensions/mash/safe_assignment_spec.rb +0 -50
  35. data/spec/hashie/extensions/mash/symbolize_keys_spec.rb +0 -39
  36. data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
  37. data/spec/hashie/extensions/method_access_spec.rb +0 -233
  38. data/spec/hashie/extensions/strict_key_access_spec.rb +0 -109
  39. data/spec/hashie/extensions/stringify_keys_spec.rb +0 -124
  40. data/spec/hashie/extensions/symbolize_keys_spec.rb +0 -131
  41. data/spec/hashie/hash_spec.rb +0 -123
  42. data/spec/hashie/mash_spec.rb +0 -1077
  43. data/spec/hashie/parsers/yaml_erb_parser_spec.rb +0 -46
  44. data/spec/hashie/rash_spec.rb +0 -83
  45. data/spec/hashie/trash_spec.rb +0 -334
  46. data/spec/hashie/utils_spec.rb +0 -25
  47. data/spec/hashie/version_spec.rb +0 -7
  48. data/spec/hashie_spec.rb +0 -13
  49. data/spec/integration/elasticsearch/integration_spec.rb +0 -41
  50. data/spec/integration/omniauth-oauth2/app.rb +0 -52
  51. data/spec/integration/omniauth-oauth2/integration_spec.rb +0 -26
  52. data/spec/integration/omniauth-oauth2/some_site.rb +0 -38
  53. data/spec/integration/omniauth/app.rb +0 -11
  54. data/spec/integration/omniauth/integration_spec.rb +0 -38
  55. data/spec/integration/rails-without-dependency/integration_spec.rb +0 -15
  56. data/spec/integration/rails/app.rb +0 -40
  57. data/spec/integration/rails/integration_spec.rb +0 -47
  58. data/spec/spec_helper.rb +0 -23
  59. data/spec/support/integration_specs.rb +0 -36
  60. data/spec/support/logger.rb +0 -24
  61. data/spec/support/module_context.rb +0 -11
  62. data/spec/support/ruby_version_check.rb +0 -6
@@ -12,5 +12,33 @@ module Hashie
12
12
  "defined in #{bound_method.owner}"
13
13
  end
14
14
  end
15
+
16
+ # Duplicates a value or returns the value when it is not duplicable
17
+ #
18
+ # @api public
19
+ #
20
+ # @param value [Object] the value to safely duplicate
21
+ # @return [Object] the duplicated value
22
+ def self.safe_dup(value)
23
+ case value
24
+ when Complex, FalseClass, NilClass, Rational, Method, Symbol, TrueClass, *integer_classes
25
+ value
26
+ else
27
+ value.dup
28
+ end
29
+ end
30
+
31
+ # Lists the classes Ruby uses for integers
32
+ #
33
+ # @api private
34
+ # @return [Array<Class>]
35
+ def self.integer_classes
36
+ @integer_classes ||=
37
+ if 0.class == Integer
38
+ [Integer]
39
+ else
40
+ [Fixnum, Bignum] # rubocop:disable Lint/UnifiedInteger
41
+ end
42
+ end
15
43
  end
16
44
  end
@@ -1,3 +1,3 @@
1
1
  module Hashie
2
- VERSION = '4.0.0'.freeze
2
+ VERSION = '4.1.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hashie
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Bleigh
@@ -9,50 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-10-30 00:00:00.000000000 Z
12
+ date: 2020-02-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: rake
15
+ name: bundler
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - "<"
18
+ - - ">="
19
19
  - !ruby/object:Gem::Version
20
- version: '11'
20
+ version: '0'
21
21
  type: :development
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
- - - "<"
25
+ - - ">="
26
26
  - !ruby/object:Gem::Version
27
- version: '11'
28
- - !ruby/object:Gem::Dependency
29
- name: rspec
30
- requirement: !ruby/object:Gem::Requirement
31
- requirements:
32
- - - "~>"
33
- - !ruby/object:Gem::Version
34
- version: '3.0'
35
- type: :development
36
- prerelease: false
37
- version_requirements: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - "~>"
40
- - !ruby/object:Gem::Version
41
- version: '3.0'
42
- - !ruby/object:Gem::Dependency
43
- name: rspec-pending_for
44
- requirement: !ruby/object:Gem::Requirement
45
- requirements:
46
- - - "~>"
47
- - !ruby/object:Gem::Version
48
- version: '0.1'
49
- type: :development
50
- prerelease: false
51
- version_requirements: !ruby/object:Gem::Requirement
52
- requirements:
53
- - - "~>"
54
- - !ruby/object:Gem::Version
55
- version: '0.1'
27
+ version: '0'
56
28
  description: Hashie is a collection of classes and mixins that make hashes more powerful.
57
29
  email:
58
30
  - michael@intridea.com
@@ -89,6 +61,7 @@ files:
89
61
  - lib/hashie/extensions/key_conversion.rb
90
62
  - lib/hashie/extensions/mash/define_accessors.rb
91
63
  - lib/hashie/extensions/mash/keep_original_keys.rb
64
+ - lib/hashie/extensions/mash/permissive_respond_to.rb
92
65
  - lib/hashie/extensions/mash/safe_assignment.rb
93
66
  - lib/hashie/extensions/mash/symbolize_keys.rb
94
67
  - lib/hashie/extensions/merge_initializer.rb
@@ -108,56 +81,14 @@ files:
108
81
  - lib/hashie/trash.rb
109
82
  - lib/hashie/utils.rb
110
83
  - lib/hashie/version.rb
111
- - spec/hashie/array_spec.rb
112
- - spec/hashie/clash_spec.rb
113
- - spec/hashie/dash_spec.rb
114
- - spec/hashie/extensions/autoload_spec.rb
115
- - spec/hashie/extensions/coercion_spec.rb
116
- - spec/hashie/extensions/dash/coercion_spec.rb
117
- - spec/hashie/extensions/dash/indifferent_access_spec.rb
118
- - spec/hashie/extensions/deep_fetch_spec.rb
119
- - spec/hashie/extensions/deep_find_spec.rb
120
- - spec/hashie/extensions/deep_locate_spec.rb
121
- - spec/hashie/extensions/deep_merge_spec.rb
122
- - spec/hashie/extensions/ignore_undeclared_spec.rb
123
- - spec/hashie/extensions/indifferent_access_spec.rb
124
- - spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb
125
- - spec/hashie/extensions/key_conversion_spec.rb
126
- - spec/hashie/extensions/mash/define_accessors_spec.rb
127
- - spec/hashie/extensions/mash/keep_original_keys_spec.rb
128
- - spec/hashie/extensions/mash/safe_assignment_spec.rb
129
- - spec/hashie/extensions/mash/symbolize_keys_spec.rb
130
- - spec/hashie/extensions/merge_initializer_spec.rb
131
- - spec/hashie/extensions/method_access_spec.rb
132
- - spec/hashie/extensions/strict_key_access_spec.rb
133
- - spec/hashie/extensions/stringify_keys_spec.rb
134
- - spec/hashie/extensions/symbolize_keys_spec.rb
135
- - spec/hashie/hash_spec.rb
136
- - spec/hashie/mash_spec.rb
137
- - spec/hashie/parsers/yaml_erb_parser_spec.rb
138
- - spec/hashie/rash_spec.rb
139
- - spec/hashie/trash_spec.rb
140
- - spec/hashie/utils_spec.rb
141
- - spec/hashie/version_spec.rb
142
- - spec/hashie_spec.rb
143
- - spec/integration/elasticsearch/integration_spec.rb
144
- - spec/integration/omniauth-oauth2/app.rb
145
- - spec/integration/omniauth-oauth2/integration_spec.rb
146
- - spec/integration/omniauth-oauth2/some_site.rb
147
- - spec/integration/omniauth/app.rb
148
- - spec/integration/omniauth/integration_spec.rb
149
- - spec/integration/rails-without-dependency/integration_spec.rb
150
- - spec/integration/rails/app.rb
151
- - spec/integration/rails/integration_spec.rb
152
- - spec/spec_helper.rb
153
- - spec/support/integration_specs.rb
154
- - spec/support/logger.rb
155
- - spec/support/module_context.rb
156
- - spec/support/ruby_version_check.rb
157
- homepage: https://github.com/intridea/hashie
84
+ homepage: https://github.com/hashie/hashie
158
85
  licenses:
159
86
  - MIT
160
- metadata: {}
87
+ metadata:
88
+ bug_tracker_uri: https://github.com/hashie/hashie/issues
89
+ changelog_uri: https://github.com/hashie/hashie/blob/master/CHANGELOG.md
90
+ documentation_uri: https://www.rubydoc.info/gems/hashie
91
+ source_code_uri: https://github.com/hashie/hashie
161
92
  post_install_message:
162
93
  rdoc_options: []
163
94
  require_paths:
@@ -173,54 +104,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
173
104
  - !ruby/object:Gem::Version
174
105
  version: '0'
175
106
  requirements: []
176
- rubygems_version: 3.0.4
107
+ rubygems_version: 3.0.3
177
108
  signing_key:
178
109
  specification_version: 4
179
110
  summary: Your friendly neighborhood hash library.
180
- test_files:
181
- - spec/hashie/parsers/yaml_erb_parser_spec.rb
182
- - spec/hashie/array_spec.rb
183
- - spec/hashie/hash_spec.rb
184
- - spec/hashie/version_spec.rb
185
- - spec/hashie/clash_spec.rb
186
- - spec/hashie/mash_spec.rb
187
- - spec/hashie/extensions/stringify_keys_spec.rb
188
- - spec/hashie/extensions/deep_locate_spec.rb
189
- - spec/hashie/extensions/coercion_spec.rb
190
- - spec/hashie/extensions/mash/safe_assignment_spec.rb
191
- - spec/hashie/extensions/mash/define_accessors_spec.rb
192
- - spec/hashie/extensions/mash/symbolize_keys_spec.rb
193
- - spec/hashie/extensions/mash/keep_original_keys_spec.rb
194
- - spec/hashie/extensions/key_conversion_spec.rb
195
- - spec/hashie/extensions/ignore_undeclared_spec.rb
196
- - spec/hashie/extensions/autoload_spec.rb
197
- - spec/hashie/extensions/strict_key_access_spec.rb
198
- - spec/hashie/extensions/deep_fetch_spec.rb
199
- - spec/hashie/extensions/method_access_spec.rb
200
- - spec/hashie/extensions/deep_find_spec.rb
201
- - spec/hashie/extensions/deep_merge_spec.rb
202
- - spec/hashie/extensions/merge_initializer_spec.rb
203
- - spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb
204
- - spec/hashie/extensions/dash/coercion_spec.rb
205
- - spec/hashie/extensions/dash/indifferent_access_spec.rb
206
- - spec/hashie/extensions/symbolize_keys_spec.rb
207
- - spec/hashie/extensions/indifferent_access_spec.rb
208
- - spec/hashie/rash_spec.rb
209
- - spec/hashie/utils_spec.rb
210
- - spec/hashie/dash_spec.rb
211
- - spec/hashie/trash_spec.rb
212
- - spec/spec_helper.rb
213
- - spec/integration/rails-without-dependency/integration_spec.rb
214
- - spec/integration/omniauth-oauth2/integration_spec.rb
215
- - spec/integration/omniauth-oauth2/app.rb
216
- - spec/integration/omniauth-oauth2/some_site.rb
217
- - spec/integration/omniauth/integration_spec.rb
218
- - spec/integration/omniauth/app.rb
219
- - spec/integration/elasticsearch/integration_spec.rb
220
- - spec/integration/rails/integration_spec.rb
221
- - spec/integration/rails/app.rb
222
- - spec/support/module_context.rb
223
- - spec/support/logger.rb
224
- - spec/support/integration_specs.rb
225
- - spec/support/ruby_version_check.rb
226
- - spec/hashie_spec.rb
111
+ test_files: []
@@ -1,29 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Array do
4
- with_minimum_ruby('2.3.0') do
5
- describe '#dig' do
6
- let(:array) { Hashie::Array.new(%i[a b c]) }
7
-
8
- it 'works with a string index' do
9
- expect(array.dig('0')).to eq(:a)
10
- end
11
-
12
- it 'works with a numeric index' do
13
- expect(array.dig(1)).to eq(:b)
14
- end
15
-
16
- context 'when array is empty' do
17
- let(:array) { Hashie::Array.new([]) }
18
-
19
- it 'works with a first numeric and next string index' do
20
- expect(array.dig(0, 'hello')).to eq(nil)
21
- end
22
-
23
- it 'throws an error with first string and next numeric index' do
24
- expect { array.dig('hello', 0) }.to raise_error(TypeError)
25
- end
26
- end
27
- end
28
- end
29
- end
@@ -1,70 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Hashie::Clash do
4
- it 'is able to set an attribute via method_missing' do
5
- subject.foo('bar')
6
- expect(subject[:foo]).to eq 'bar'
7
- end
8
-
9
- it 'is able to set multiple attributes' do
10
- subject.foo('bar').baz('wok')
11
- expect(subject).to eq(foo: 'bar', baz: 'wok')
12
- end
13
-
14
- it 'converts multiple arguments into an array' do
15
- subject.foo(1, 2, 3)
16
- expect(subject[:foo]).to eq [1, 2, 3]
17
- end
18
-
19
- it 'is able to use bang notation to create a new Clash on a key' do
20
- subject.foo!
21
- expect(subject[:foo]).to be_kind_of(Hashie::Clash)
22
- end
23
-
24
- it 'is able to chain onto the new Clash when using bang notation' do
25
- subject.foo!.bar('abc').baz(123)
26
- expect(subject).to eq(foo: { bar: 'abc', baz: 123 })
27
- end
28
-
29
- it 'is able to jump back up to the parent in the chain with #_end!' do
30
- subject.foo!.bar('abc')._end!.baz(123)
31
- expect(subject).to eq(foo: { bar: 'abc' }, baz: 123)
32
- end
33
-
34
- it 'merges rather than replaces existing keys' do
35
- subject.where(abc: 'def').where(hgi: 123)
36
- expect(subject).to eq(where: { abc: 'def', hgi: 123 })
37
- end
38
-
39
- it 'is able to replace all of its own keys with #replace' do
40
- subject.foo(:bar).hello(:world)
41
- expect(subject.replace(baz: 123, hgi: 123)).to eq(baz: 123, hgi: 123)
42
- expect(subject).to eq(baz: 123, hgi: 123)
43
- expect(subject[:foo]).to be_nil
44
- expect(subject[:hello]).to be_nil
45
- end
46
-
47
- it 'merges multiple bang notation calls' do
48
- subject.where!.foo(123)
49
- subject.where!.bar(321)
50
- expect(subject).to eq(where: { foo: 123, bar: 321 })
51
- end
52
-
53
- it 'raises an exception when method is missing' do
54
- expect { subject.boo }.to raise_error(NoMethodError)
55
- end
56
-
57
- describe 'when inherited' do
58
- subject { Class.new(described_class).new }
59
-
60
- it 'bang nodes are instances of a subclass' do
61
- subject.where!.foo(123)
62
- expect(subject[:where]).to be_instance_of(subject.class)
63
- end
64
-
65
- it 'merged nodes are instances of a subclass' do
66
- subject.where(abc: 'def').where(hgi: 123)
67
- expect(subject[:where]).to be_instance_of(subject.class)
68
- end
69
- end
70
- end
@@ -1,608 +0,0 @@
1
- require 'spec_helper'
2
-
3
- Hashie::Hash.class_eval do
4
- def self.inherited(klass)
5
- klass.instance_variable_set('@inheritance_test', true)
6
- end
7
- end
8
-
9
- class DashTest < Hashie::Dash
10
- property :first_name, required: true
11
- property :email
12
- property :count, default: 0
13
- end
14
-
15
- class DashTestDefaultProc < Hashie::Dash
16
- property :fields, default: -> { [] }
17
- end
18
-
19
- class DashNoRequiredTest < Hashie::Dash
20
- property :first_name
21
- property :email
22
- property :count, default: 0
23
- end
24
-
25
- class DashWithCoercion < Hashie::Dash
26
- include Hashie::Extensions::Coercion
27
- property :person
28
- property :city
29
-
30
- coerce_key :person, ::DashNoRequiredTest
31
- end
32
-
33
- class PropertyBangTest < Hashie::Dash
34
- property :important!
35
- end
36
-
37
- class SubclassedTest < DashTest
38
- property :last_name, required: true
39
- end
40
-
41
- class RequiredMessageTest < DashTest
42
- property :first_name, required: true, message: 'must be set.'
43
- end
44
-
45
- class DashDefaultTest < Hashie::Dash
46
- property :aliases, default: ['Snake']
47
- end
48
-
49
- class DeferredTest < Hashie::Dash
50
- property :created_at, default: proc { Time.now }
51
- end
52
-
53
- class DeferredWithSelfTest < Hashie::Dash
54
- property :created_at, default: -> { Time.now }
55
- property :updated_at, default: ->(test) { test.created_at }
56
- end
57
-
58
- describe DashTestDefaultProc do
59
- it 'as_json behaves correctly with default proc' do
60
- object = described_class.new
61
- expect(object.as_json).to be == { 'fields' => [] }
62
- end
63
- end
64
-
65
- describe DashTest do
66
- def property_required_error(property)
67
- [ArgumentError, "The property '#{property}' is required for #{subject.class.name}."]
68
- end
69
-
70
- def property_required_custom_error(property)
71
- [ArgumentError, "The property '#{property}' must be set."]
72
- end
73
-
74
- def property_message_without_required_error
75
- [ArgumentError, 'The :message option should be used with :required option.']
76
- end
77
-
78
- def no_property_error(property)
79
- [NoMethodError, "The property '#{property}' is not defined for #{subject.class.name}."]
80
- end
81
-
82
- subject { DashTest.new(first_name: 'Bob', email: 'bob@example.com') }
83
- let(:required_message) { RequiredMessageTest.new(first_name: 'Bob') }
84
-
85
- it('subclasses Hashie::Hash') { should respond_to(:to_mash) }
86
-
87
- describe '#to_s' do
88
- subject { super().to_s }
89
- it { should eq '#<DashTest count=0 email="bob@example.com" first_name="Bob">' }
90
- end
91
-
92
- it 'lists all set properties in inspect' do
93
- subject.first_name = 'Bob'
94
- subject.email = 'bob@example.com'
95
- expect(subject.inspect).to eq '#<DashTest count=0 email="bob@example.com" first_name="Bob">'
96
- end
97
-
98
- describe '#count' do
99
- subject { super().count }
100
- it { should be_zero }
101
- end
102
-
103
- it { should respond_to(:first_name) }
104
- it { should respond_to(:first_name=) }
105
- it { should_not respond_to(:nonexistent) }
106
-
107
- it 'errors out for a non-existent property' do
108
- expect { subject['nonexistent'] }.to raise_error(*no_property_error('nonexistent'))
109
- end
110
-
111
- it 'errors out when attempting to set a required property to nil' do
112
- expect { subject.first_name = nil }.to raise_error(*property_required_error('first_name'))
113
- end
114
-
115
- it 'errors out when message added to not required property' do
116
- expect do
117
- class DashMessageOptionWithoutRequiredTest < Hashie::Dash
118
- property :first_name, message: 'is required.'
119
- end
120
- end.to raise_error(*property_message_without_required_error)
121
-
122
- expect do
123
- class DashMessageOptionWithoutRequiredTest < Hashie::Dash
124
- property :first_name, required: false, message: 'is required.'
125
- end
126
- end.to raise_error(*property_message_without_required_error)
127
- end
128
-
129
- context 'writing to properties' do
130
- it 'fails writing a required property to nil' do
131
- expect { subject.first_name = nil }.to raise_error(*property_required_error('first_name'))
132
- expect { required_message.first_name = nil }
133
- .to raise_error(*property_required_custom_error('first_name'))
134
- end
135
-
136
- it 'fails writing a required property to nil using []=' do
137
- expect { subject[:first_name] = nil }.to raise_error(*property_required_error('first_name'))
138
- expect { required_message[:first_name] = nil }
139
- .to raise_error(*property_required_custom_error('first_name'))
140
- end
141
-
142
- it 'fails writing to a non-existent property using []=' do
143
- expect { subject['nonexistent'] = 123 }.to raise_error(*no_property_error('nonexistent'))
144
- end
145
-
146
- it 'works for an existing property using []=' do
147
- subject[:first_name] = 'Bob'
148
- expect(subject[:first_name]).to eq 'Bob'
149
- expect { subject['first_name'] }.to raise_error(*no_property_error('first_name'))
150
- end
151
-
152
- it 'works for an existing property using a method call' do
153
- subject.first_name = 'Franklin'
154
- expect(subject.first_name).to eq 'Franklin'
155
- end
156
- end
157
-
158
- context 'reading from properties' do
159
- it 'fails reading from a non-existent property using []' do
160
- expect { subject['nonexistent'] }.to raise_error(*no_property_error('nonexistent'))
161
- end
162
-
163
- it 'is able to retrieve properties through blocks' do
164
- subject[:first_name] = 'Aiden'
165
- value = nil
166
- subject.[](:first_name) { |v| value = v }
167
- expect(value).to eq 'Aiden'
168
- end
169
-
170
- it 'is able to retrieve properties through blocks with method calls' do
171
- subject[:first_name] = 'Frodo'
172
- value = nil
173
- subject.first_name { |v| value = v }
174
- expect(value).to eq 'Frodo'
175
- end
176
- end
177
-
178
- context 'reading from deferred properties' do
179
- it 'evaluates proc after initial read' do
180
- expect(DeferredTest.new[:created_at]).to be_instance_of(Time)
181
- end
182
-
183
- it 'does not evalute proc after subsequent reads' do
184
- deferred = DeferredTest.new
185
- expect(deferred[:created_at].object_id).to eq deferred[:created_at].object_id
186
- end
187
- end
188
-
189
- context 'reading from a deferred property based on context' do
190
- it 'provides the current hash as context for evaluation' do
191
- deferred = DeferredWithSelfTest.new
192
- expect(deferred[:created_at].object_id).to eq deferred[:created_at].object_id
193
- expect(deferred[:updated_at].object_id).to eq deferred[:created_at].object_id
194
- end
195
- end
196
-
197
- context 'converting from a Mash' do
198
- class ConvertingFromMash < Hashie::Dash
199
- property :property, required: true
200
- end
201
-
202
- context 'without keeping the original keys' do
203
- let(:mash) { Hashie::Mash.new(property: 'test') }
204
-
205
- it 'does not pick up the property from the stringified key' do
206
- expect { ConvertingFromMash.new(mash) }.to raise_error(NoMethodError)
207
- end
208
- end
209
-
210
- context 'when keeping the original keys' do
211
- class KeepingMash < Hashie::Mash
212
- include Hashie::Extensions::Mash::KeepOriginalKeys
213
- end
214
-
215
- let(:mash) { KeepingMash.new(property: 'test') }
216
-
217
- it 'picks up the property from the original key' do
218
- expect { ConvertingFromMash.new(mash) }.not_to raise_error
219
- end
220
- end
221
- end
222
-
223
- describe '#new' do
224
- it 'fails with non-existent properties' do
225
- expect { described_class.new(bork: '') }.to raise_error(*no_property_error('bork'))
226
- end
227
-
228
- it 'sets properties that it is able to' do
229
- obj = described_class.new first_name: 'Michael'
230
- expect(obj.first_name).to eq 'Michael'
231
- end
232
-
233
- it 'accepts nil' do
234
- expect { DashNoRequiredTest.new(nil) }.not_to raise_error
235
- end
236
-
237
- it 'accepts block to define a global default' do
238
- obj = described_class.new { |_, key| key.to_s.upcase }
239
- expect(obj.first_name).to eq 'FIRST_NAME'
240
- expect(obj.count).to be_zero
241
- end
242
-
243
- it 'fails when required values are missing' do
244
- expect { DashTest.new }.to raise_error(*property_required_error('first_name'))
245
- end
246
-
247
- it 'does not overwrite default values' do
248
- obj1 = DashDefaultTest.new
249
- obj1.aliases << 'El Rey'
250
- obj2 = DashDefaultTest.new
251
- expect(obj2.aliases).not_to include 'El Rey'
252
- end
253
- end
254
-
255
- describe '#merge' do
256
- it 'creates a new instance of the Dash' do
257
- new_dash = subject.merge(first_name: 'Robert')
258
- expect(subject.object_id).not_to eq new_dash.object_id
259
- end
260
-
261
- it 'merges the given hash' do
262
- new_dash = subject.merge(first_name: 'Robert', email: 'robert@example.com')
263
- expect(new_dash.first_name).to eq 'Robert'
264
- expect(new_dash.email).to eq 'robert@example.com'
265
- end
266
-
267
- it 'fails with non-existent properties' do
268
- expect { subject.merge(middle_name: 'James') }
269
- .to raise_error(*no_property_error('middle_name'))
270
- end
271
-
272
- it 'errors out when attempting to set a required property to nil' do
273
- expect { subject.merge(first_name: nil) }
274
- .to raise_error(*property_required_error('first_name'))
275
- end
276
-
277
- context 'given a block' do
278
- it "sets merged key's values to the block's return value" do
279
- expect(subject.merge(first_name: 'Jim') do |key, oldval, newval|
280
- "#{key}: #{newval} #{oldval}"
281
- end.first_name).to eq 'first_name: Jim Bob'
282
- end
283
- end
284
- end
285
-
286
- describe '#merge!' do
287
- it 'modifies the existing instance of the Dash' do
288
- original_dash = subject.merge!(first_name: 'Robert')
289
- expect(subject.object_id).to eq original_dash.object_id
290
- end
291
-
292
- it 'merges the given hash' do
293
- subject.merge!(first_name: 'Robert', email: 'robert@example.com')
294
- expect(subject.first_name).to eq 'Robert'
295
- expect(subject.email).to eq 'robert@example.com'
296
- end
297
-
298
- it 'fails with non-existent properties' do
299
- expect { subject.merge!(middle_name: 'James') }.to raise_error(NoMethodError)
300
- end
301
-
302
- it 'errors out when attempting to set a required property to nil' do
303
- expect { subject.merge!(first_name: nil) }.to raise_error(ArgumentError)
304
- end
305
-
306
- context 'given a block' do
307
- it "sets merged key's values to the block's return value" do
308
- expect(subject.merge!(first_name: 'Jim') do |key, oldval, newval|
309
- "#{key}: #{newval} #{oldval}"
310
- end.first_name).to eq 'first_name: Jim Bob'
311
- end
312
- end
313
- end
314
-
315
- describe 'properties' do
316
- it 'lists defined properties' do
317
- expect(described_class.properties).to eq Set.new(%i[first_name email count])
318
- end
319
-
320
- it 'checks if a property exists' do
321
- expect(described_class.property?(:first_name)).to be_truthy
322
- expect(described_class.property?('first_name')).to be_falsy
323
- end
324
-
325
- it 'checks if a property is required' do
326
- expect(described_class.required?(:first_name)).to be_truthy
327
- expect(described_class.required?('first_name')).to be_falsy
328
- end
329
-
330
- it 'doesnt include property from subclass' do
331
- expect(described_class.property?(:last_name)).to be_falsy
332
- end
333
-
334
- it 'lists declared defaults' do
335
- expect(described_class.defaults).to eq(count: 0)
336
- end
337
-
338
- it 'allows properties that end in bang' do
339
- expect(PropertyBangTest.property?(:important!)).to be_truthy
340
- end
341
- end
342
-
343
- describe '#replace' do
344
- before { subject.replace(first_name: 'Cain') }
345
-
346
- it 'return self' do
347
- expect(subject.replace(email: 'bar').to_hash).to eq(email: 'bar', count: 0)
348
- end
349
-
350
- it 'sets all specified keys to their corresponding values' do
351
- expect(subject.first_name).to eq 'Cain'
352
- end
353
-
354
- it 'leaves only specified keys and keys with default values' do
355
- expect(subject.keys.sort_by(&:to_s)).to eq %i[count first_name]
356
- expect(subject.email).to be_nil
357
- expect(subject.count).to eq 0
358
- end
359
-
360
- context 'when replacing keys with default values' do
361
- before { subject.replace(count: 3) }
362
-
363
- it 'sets all specified keys to their corresponding values' do
364
- expect(subject.count).to eq 3
365
- end
366
- end
367
- end
368
-
369
- describe '#update_attributes!(params)' do
370
- let(:params) { { first_name: 'Alice', email: 'alice@example.com' } }
371
-
372
- context 'when there is coercion' do
373
- let(:params_before) do
374
- { city: 'nyc', person: { first_name: 'Bob', email: 'bob@example.com' } }
375
- end
376
- let(:params_after) do
377
- { city: 'sfo', person: { first_name: 'Alice', email: 'alice@example.com' } }
378
- end
379
-
380
- subject { DashWithCoercion.new(params_before) }
381
-
382
- it 'update the attributes' do
383
- expect(subject.person.first_name).to eq params_before[:person][:first_name]
384
- subject.update_attributes!(params_after)
385
- expect(subject.person.first_name).to eq params_after[:person][:first_name]
386
- end
387
- end
388
-
389
- it 'update the attributes' do
390
- subject.update_attributes!(params)
391
- expect(subject.first_name).to eq params[:first_name]
392
- expect(subject.email).to eq params[:email]
393
- expect(subject.count).to eq subject.class.defaults[:count]
394
- end
395
-
396
- context 'when required property is update to nil' do
397
- let(:params) { { first_name: nil, email: 'alice@example.com' } }
398
-
399
- it 'raise an ArgumentError' do
400
- expect { subject.update_attributes!(params) }.to raise_error(ArgumentError)
401
- end
402
- end
403
-
404
- context 'when a default property is update to nil' do
405
- let(:params) { { count: nil, email: 'alice@example.com' } }
406
-
407
- it 'set the property back to the default value' do
408
- subject.update_attributes!(params)
409
- expect(subject.email).to eq params[:email]
410
- expect(subject.count).to eq subject.class.defaults[:count]
411
- end
412
- end
413
-
414
- context 'codependent attributes' do
415
- let(:codependent) do
416
- Class.new(Hashie::Dash) do
417
- property :a, required: -> { b.nil? }, message: 'is required if b is not set.'
418
- property :b, required: -> { a.nil? }, message: 'is required if a is not set.'
419
- end
420
- end
421
-
422
- it 'does not raise an error when only the first property is set' do
423
- expect { codependent.new(a: 'ant', b: nil) }.not_to raise_error
424
- end
425
-
426
- it 'does not raise an error when only the second property is set' do
427
- expect { codependent.new(a: nil, b: 'bat') }.not_to raise_error
428
- end
429
-
430
- it 'does not raise an error when both properties are set' do
431
- expect { codependent.new(a: 'ant', b: 'bat') }.not_to raise_error
432
- end
433
-
434
- it 'raises an error when neither property is set' do
435
- expect { codependent.new(a: nil, b: nil) }.to raise_error(ArgumentError)
436
- end
437
- end
438
- end
439
- end
440
-
441
- describe Hashie::Dash, 'inheritance' do
442
- before do
443
- @top = Class.new(Hashie::Dash)
444
- @middle = Class.new(@top)
445
- @bottom = Class.new(@middle)
446
- end
447
-
448
- it 'reports empty properties when nothing defined' do
449
- expect(@top.properties).to be_empty
450
- expect(@top.defaults).to be_empty
451
- end
452
-
453
- it 'inherits properties downwards' do
454
- @top.property :echo
455
- expect(@middle.properties).to include(:echo)
456
- expect(@bottom.properties).to include(:echo)
457
- end
458
-
459
- it 'doesnt inherit properties upwards' do
460
- @middle.property :echo
461
- expect(@top.properties).not_to include(:echo)
462
- expect(@bottom.properties).to include(:echo)
463
- end
464
-
465
- it 'allows overriding a default on an existing property' do
466
- @top.property :echo
467
- @middle.property :echo, default: 123
468
- expect(@bottom.properties.to_a).to eq [:echo]
469
- expect(@bottom.new.echo).to eq 123
470
- end
471
-
472
- it 'allows clearing an existing default' do
473
- @top.property :echo
474
- @middle.property :echo, default: 123
475
- @bottom.property :echo
476
- expect(@bottom.properties.to_a).to eq [:echo]
477
- expect(@bottom.new.echo).to be_nil
478
- end
479
-
480
- it 'allows nil defaults' do
481
- @bottom.property :echo, default: nil
482
- expect(@bottom.new).to have_key(:echo)
483
- expect(@bottom.new).to_not have_key('echo')
484
- end
485
- end
486
-
487
- describe SubclassedTest do
488
- subject { SubclassedTest.new(first_name: 'Bob', last_name: 'McNob', email: 'bob@example.com') }
489
-
490
- describe '#count' do
491
- subject { super().count }
492
- it { should be_zero }
493
- end
494
-
495
- it { should respond_to(:first_name) }
496
- it { should respond_to(:first_name=) }
497
- it { should respond_to(:last_name) }
498
- it { should respond_to(:last_name=) }
499
-
500
- it 'has one additional property' do
501
- expect(described_class.property?(:last_name)).to be_truthy
502
- end
503
-
504
- it "didn't override superclass inheritance logic" do
505
- expect(described_class.instance_variable_get('@inheritance_test')).to be_truthy
506
- end
507
- end
508
-
509
- class ConditionallyRequiredTest < Hashie::Dash
510
- property :username
511
- property :password, required: -> { !username.nil? }, message: 'must be set, too.'
512
- end
513
-
514
- describe ConditionallyRequiredTest do
515
- it 'does not allow a conditionally required property to be set to nil if required' do
516
- expect { ConditionallyRequiredTest.new(username: 'bob.smith', password: nil) }
517
- .to raise_error(ArgumentError, "The property 'password' must be set, too.")
518
- end
519
-
520
- it 'allows a conditionally required property to be set to nil if not required' do
521
- expect { ConditionallyRequiredTest.new(username: nil, password: nil) }.not_to raise_error
522
- end
523
-
524
- it 'allows a conditionally required property to be set if required' do
525
- expect { ConditionallyRequiredTest.new(username: 'bob.smith', password: '$ecure!') }
526
- .not_to raise_error
527
- end
528
- end
529
-
530
- class MixedPropertiesTest < Hashie::Dash
531
- property :symbol
532
- property 'string'
533
- end
534
-
535
- describe MixedPropertiesTest do
536
- subject { MixedPropertiesTest.new('string' => 'string', symbol: 'symbol') }
537
-
538
- it { should respond_to('string') }
539
- it { should respond_to(:symbol) }
540
-
541
- it 'property?' do
542
- expect(described_class.property?('string')).to be_truthy
543
- expect(described_class.property?(:symbol)).to be_truthy
544
- end
545
-
546
- it 'fetch' do
547
- expect(subject['string']).to eq('string')
548
- expect { subject[:string] }.to raise_error(NoMethodError)
549
- expect(subject[:symbol]).to eq('symbol')
550
- expect { subject['symbol'] }.to raise_error(NoMethodError)
551
- end
552
-
553
- it 'double define' do
554
- klass = Class.new(MixedPropertiesTest) do
555
- property 'symbol'
556
- end
557
- instance = klass.new(symbol: 'one', 'symbol' => 'two')
558
- expect(instance[:symbol]).to eq('one')
559
- expect(instance['symbol']).to eq('two')
560
- end
561
-
562
- it 'assign' do
563
- subject['string'] = 'updated'
564
- expect(subject['string']).to eq('updated')
565
-
566
- expect { subject[:string] = 'updated' }.to raise_error(NoMethodError)
567
-
568
- subject[:symbol] = 'updated'
569
- expect(subject[:symbol]).to eq('updated')
570
-
571
- expect { subject['symbol'] = 'updated' }.to raise_error(NoMethodError)
572
- end
573
- end
574
-
575
- context 'Dynamic Dash Class' do
576
- it 'define property' do
577
- klass = Class.new(Hashie::Dash)
578
- my_property = 'my_property'
579
- my_orig = my_property.dup
580
-
581
- klass.property(my_property)
582
-
583
- expect(my_property).to eq(my_orig)
584
- end
585
- end
586
-
587
- context 'with method access' do
588
- class DashWithMethodAccess < Hashie::Dash
589
- include Hashie::Extensions::IndifferentAccess
590
- include Hashie::Extensions::MethodQuery
591
-
592
- property :test
593
- end
594
-
595
- subject(:dash) { DashWithMethodAccess.new(test: 'value') }
596
-
597
- describe '#test' do
598
- subject { dash.test }
599
-
600
- it { is_expected.to eq('value') }
601
- end
602
-
603
- describe '#test?' do
604
- subject { dash.test? }
605
-
606
- it { is_expected.to eq true }
607
- end
608
- end