iknow_view_models 3.2.0 → 3.2.1
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 +4 -4
- data/.rubocop.yml +13 -0
- data/Appraisals +6 -6
- data/Rakefile +5 -5
- data/gemfiles/rails_5_2.gemfile +5 -5
- data/gemfiles/rails_6_0.gemfile +5 -5
- data/iknow_view_models.gemspec +40 -39
- data/lib/iknow_view_models.rb +9 -7
- data/lib/iknow_view_models/version.rb +1 -1
- data/lib/view_model.rb +17 -14
- data/lib/view_model/access_control.rb +5 -2
- data/lib/view_model/access_control/composed.rb +10 -9
- data/lib/view_model/access_control/open.rb +2 -0
- data/lib/view_model/access_control/read_only.rb +2 -0
- data/lib/view_model/access_control/tree.rb +11 -6
- data/lib/view_model/access_control_error.rb +4 -1
- data/lib/view_model/active_record.rb +12 -11
- data/lib/view_model/active_record/association_data.rb +2 -1
- data/lib/view_model/active_record/association_manipulation.rb +6 -4
- data/lib/view_model/active_record/cache.rb +4 -2
- data/lib/view_model/active_record/collection_nested_controller.rb +3 -3
- data/lib/view_model/active_record/controller_base.rb +4 -1
- data/lib/view_model/active_record/nested_controller_base.rb +1 -0
- data/lib/view_model/active_record/update_context.rb +8 -6
- data/lib/view_model/active_record/update_data.rb +32 -30
- data/lib/view_model/active_record/update_operation.rb +17 -13
- data/lib/view_model/active_record/visitor.rb +0 -1
- data/lib/view_model/after_transaction_runner.rb +0 -1
- data/lib/view_model/callbacks.rb +3 -1
- data/lib/view_model/controller.rb +13 -3
- data/lib/view_model/deserialization_error.rb +15 -12
- data/lib/view_model/error.rb +12 -10
- data/lib/view_model/error_view.rb +3 -1
- data/lib/view_model/migration/no_path_error.rb +1 -0
- data/lib/view_model/migration/one_way_error.rb +1 -0
- data/lib/view_model/migration/unspecified_version_error.rb +1 -0
- data/lib/view_model/record.rb +11 -13
- data/lib/view_model/reference.rb +3 -1
- data/lib/view_model/references.rb +8 -5
- data/lib/view_model/registry.rb +1 -1
- data/lib/view_model/schemas.rb +9 -4
- data/lib/view_model/serialization_error.rb +4 -1
- data/lib/view_model/serialize_context.rb +4 -4
- data/lib/view_model/test_helpers.rb +8 -3
- data/lib/view_model/test_helpers/arvm_builder.rb +19 -14
- data/lib/view_model/traversal_context.rb +2 -1
- data/test/.rubocop.yml +14 -0
- data/test/helpers/arvm_test_models.rb +12 -9
- data/test/helpers/arvm_test_utilities.rb +5 -3
- data/test/helpers/controller_test_helpers.rb +31 -29
- data/test/helpers/match_enumerator.rb +1 -0
- data/test/helpers/query_logging.rb +2 -1
- data/test/helpers/test_access_control.rb +5 -3
- data/test/helpers/viewmodel_spec_helpers.rb +21 -20
- data/test/unit/view_model/access_control_test.rb +144 -144
- data/test/unit/view_model/active_record/alias_test.rb +15 -13
- data/test/unit/view_model/active_record/belongs_to_test.rb +40 -39
- data/test/unit/view_model/active_record/cache_test.rb +27 -26
- data/test/unit/view_model/active_record/cloner_test.rb +67 -63
- data/test/unit/view_model/active_record/controller_test.rb +37 -38
- data/test/unit/view_model/active_record/counter_test.rb +10 -9
- data/test/unit/view_model/active_record/customization_test.rb +59 -58
- data/test/unit/view_model/active_record/has_many_test.rb +112 -111
- data/test/unit/view_model/active_record/has_many_through_poly_test.rb +15 -14
- data/test/unit/view_model/active_record/has_many_through_test.rb +33 -38
- data/test/unit/view_model/active_record/has_one_test.rb +37 -36
- data/test/unit/view_model/active_record/migration_test.rb +13 -13
- data/test/unit/view_model/active_record/namespacing_test.rb +19 -17
- data/test/unit/view_model/active_record/poly_test.rb +44 -45
- data/test/unit/view_model/active_record/shared_test.rb +30 -28
- data/test/unit/view_model/active_record/version_test.rb +9 -7
- data/test/unit/view_model/active_record_test.rb +72 -72
- data/test/unit/view_model/callbacks_test.rb +19 -15
- data/test/unit/view_model/controller_test.rb +4 -2
- data/test/unit/view_model/record_test.rb +92 -97
- data/test/unit/view_model/traversal_context_test.rb +4 -5
- data/test/unit/view_model_test.rb +18 -16
- metadata +7 -5
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'minitest/autorun'
|
2
4
|
require 'minitest/unit'
|
3
5
|
require 'minitest/hooks'
|
4
6
|
|
5
|
-
require_relative '../../helpers/arvm_test_utilities
|
6
|
-
require_relative '../../helpers/viewmodel_spec_helpers
|
7
|
+
require_relative '../../helpers/arvm_test_utilities'
|
8
|
+
require_relative '../../helpers/viewmodel_spec_helpers'
|
7
9
|
|
8
10
|
require 'view_model'
|
9
11
|
require 'view_model/controller'
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative '../../helpers/test_access_control'
|
4
4
|
|
5
|
-
require
|
5
|
+
require 'minitest/autorun'
|
6
6
|
require 'minitest/unit'
|
7
7
|
|
8
|
-
require
|
9
|
-
require
|
8
|
+
require 'view_model'
|
9
|
+
require 'view_model/record'
|
10
10
|
|
11
11
|
class ViewModel::RecordTest < ActiveSupport::TestCase
|
12
12
|
using ViewModel::Utils::Collections
|
@@ -15,6 +15,7 @@ class ViewModel::RecordTest < ActiveSupport::TestCase
|
|
15
15
|
class TestDeserializeContext < ViewModel::DeserializeContext
|
16
16
|
class SharedContext < ViewModel::DeserializeContext::SharedContext
|
17
17
|
attr_reader :targets
|
18
|
+
|
18
19
|
def initialize(targets: [], **rest)
|
19
20
|
super(**rest)
|
20
21
|
@targets = targets
|
@@ -26,16 +27,9 @@ class ViewModel::RecordTest < ActiveSupport::TestCase
|
|
26
27
|
end
|
27
28
|
|
28
29
|
delegate :targets, to: :shared_context
|
29
|
-
|
30
|
-
def initialize(**rest)
|
31
|
-
super(**rest)
|
32
|
-
end
|
33
30
|
end
|
34
31
|
|
35
32
|
class TestSerializeContext < ViewModel::SerializeContext
|
36
|
-
def initialize(**rest)
|
37
|
-
super(**rest)
|
38
|
-
end
|
39
33
|
end
|
40
34
|
|
41
35
|
class TestViewModel < ViewModel::Record
|
@@ -79,7 +73,7 @@ class ViewModel::RecordTest < ActiveSupport::TestCase
|
|
79
73
|
# associations.
|
80
74
|
self.unregistered = true
|
81
75
|
|
82
|
-
self.view_name =
|
76
|
+
self.view_name = 'Model'
|
83
77
|
self.model_class = mc
|
84
78
|
|
85
79
|
attrs.each { |a, opts| attribute(a, **opts) }
|
@@ -90,8 +84,8 @@ class ViewModel::RecordTest < ActiveSupport::TestCase
|
|
90
84
|
|
91
85
|
let(:view_base) do
|
92
86
|
{
|
93
|
-
|
94
|
-
|
87
|
+
'_type' => 'Model',
|
88
|
+
'_version' => 1,
|
95
89
|
}
|
96
90
|
end
|
97
91
|
|
@@ -144,7 +138,7 @@ class ViewModel::RecordTest < ActiveSupport::TestCase
|
|
144
138
|
module CanDeserializeToNew
|
145
139
|
def self.included(base)
|
146
140
|
base.instance_eval do
|
147
|
-
it
|
141
|
+
it 'can deserialize to a new model' do
|
148
142
|
vm = viewmodel_class.deserialize_from_view(default_view, deserialize_context: create_context)
|
149
143
|
assert_equal(default_model, vm.model)
|
150
144
|
refute(default_model.equal?(vm.model))
|
@@ -159,7 +153,7 @@ class ViewModel::RecordTest < ActiveSupport::TestCase
|
|
159
153
|
module CanDeserializeToExisting
|
160
154
|
def self.included(base)
|
161
155
|
base.instance_eval do
|
162
|
-
it
|
156
|
+
it 'can deserialize to existing model with no changes' do
|
163
157
|
vm = viewmodel_class.deserialize_from_view(default_view, deserialize_context: update_context)
|
164
158
|
assert(default_model.equal?(vm.model))
|
165
159
|
|
@@ -172,7 +166,7 @@ class ViewModel::RecordTest < ActiveSupport::TestCase
|
|
172
166
|
module CanSerialize
|
173
167
|
def self.included(base)
|
174
168
|
base.instance_eval do
|
175
|
-
it
|
169
|
+
it 'can serialize to the expected view' do
|
176
170
|
h = viewmodel_class.new(default_model).to_hash
|
177
171
|
assert_equal(default_view, h)
|
178
172
|
end
|
@@ -180,44 +174,44 @@ class ViewModel::RecordTest < ActiveSupport::TestCase
|
|
180
174
|
end
|
181
175
|
end
|
182
176
|
|
183
|
-
describe
|
177
|
+
describe 'with simple attribute' do
|
184
178
|
let(:attributes) { { simple: {} } }
|
185
179
|
include CanSerialize
|
186
180
|
include CanDeserializeToNew
|
187
181
|
include CanDeserializeToExisting
|
188
182
|
|
189
|
-
it
|
190
|
-
new_view = default_view.merge(
|
183
|
+
it 'can be updated' do
|
184
|
+
new_view = default_view.merge('simple' => 'changed')
|
191
185
|
|
192
186
|
vm = viewmodel_class.deserialize_from_view(new_view, deserialize_context: update_context)
|
193
187
|
|
194
|
-
assert(default_model.equal?(vm.model),
|
195
|
-
assert_equal(
|
188
|
+
assert(default_model.equal?(vm.model), 'returned model was not the same')
|
189
|
+
assert_equal('changed', default_model.simple)
|
196
190
|
assert_edited(vm, changed_attributes: [:simple])
|
197
191
|
end
|
198
192
|
|
199
|
-
it
|
200
|
-
view = default_view.merge(
|
193
|
+
it 'rejects unknown attributes' do
|
194
|
+
view = default_view.merge('unknown' => 'illegal')
|
201
195
|
ex = assert_raises(ViewModel::DeserializationError::UnknownAttribute) do
|
202
196
|
viewmodel_class.deserialize_from_view(view, deserialize_context: create_context)
|
203
197
|
end
|
204
|
-
assert_equal(
|
198
|
+
assert_equal('unknown', ex.attribute)
|
205
199
|
end
|
206
200
|
|
207
|
-
it
|
201
|
+
it 'edit checks when creating empty' do
|
208
202
|
vm = viewmodel_class.deserialize_from_view(view_base, deserialize_context: create_context)
|
209
|
-
refute(default_model.equal?(vm.model),
|
203
|
+
refute(default_model.equal?(vm.model), 'returned model was the same')
|
210
204
|
assert_edited(vm, new: true)
|
211
205
|
end
|
212
206
|
end
|
213
207
|
|
214
|
-
describe
|
208
|
+
describe 'with validated simple attribute' do
|
215
209
|
let(:attributes) { { validated: {} } }
|
216
210
|
let(:viewmodel_body) do
|
217
211
|
->(_x) do
|
218
212
|
def validate!
|
219
|
-
if validated ==
|
220
|
-
raise ViewModel::DeserializationError::Validation.new(
|
213
|
+
if validated == 'naughty'
|
214
|
+
raise ViewModel::DeserializationError::Validation.new('validated', 'was naughty', nil, self.blame_reference)
|
221
215
|
end
|
222
216
|
end
|
223
217
|
end
|
@@ -227,34 +221,34 @@ class ViewModel::RecordTest < ActiveSupport::TestCase
|
|
227
221
|
include CanDeserializeToNew
|
228
222
|
include CanDeserializeToExisting
|
229
223
|
|
230
|
-
it
|
231
|
-
new_view = default_view.merge(
|
224
|
+
it 'rejects update when validation fails' do
|
225
|
+
new_view = default_view.merge('validated' => 'naughty')
|
232
226
|
|
233
227
|
ex = assert_raises(ViewModel::DeserializationError::Validation) do
|
234
228
|
viewmodel_class.deserialize_from_view(new_view, deserialize_context: update_context)
|
235
229
|
end
|
236
|
-
assert_equal(
|
237
|
-
assert_equal(
|
230
|
+
assert_equal('validated', ex.attribute)
|
231
|
+
assert_equal('was naughty', ex.reason)
|
238
232
|
end
|
239
233
|
end
|
240
234
|
|
241
|
-
describe
|
235
|
+
describe 'with renamed attribute' do
|
242
236
|
let(:attributes) { { modelname: { as: :viewname } } }
|
243
|
-
let(:default_model_values) { { modelname:
|
244
|
-
let(:default_view_values) { { viewname:
|
237
|
+
let(:default_model_values) { { modelname: 'value' } }
|
238
|
+
let(:default_view_values) { { viewname: 'value' } }
|
245
239
|
|
246
240
|
include CanSerialize
|
247
241
|
include CanDeserializeToNew
|
248
242
|
include CanDeserializeToExisting
|
249
243
|
|
250
|
-
it
|
251
|
-
value(default_model.modelname).must_equal(
|
244
|
+
it 'makes attributes available on their new names' do
|
245
|
+
value(default_model.modelname).must_equal('value')
|
252
246
|
vm = viewmodel_class.new(default_model)
|
253
|
-
value(vm.viewname).must_equal(
|
247
|
+
value(vm.viewname).must_equal('value')
|
254
248
|
end
|
255
249
|
end
|
256
250
|
|
257
|
-
describe
|
251
|
+
describe 'with formatted attribute' do
|
258
252
|
let(:attributes) { { moment: { format: IknowParams::Serializer::Time } } }
|
259
253
|
let(:moment) { 1.week.ago.change(usec: 0) }
|
260
254
|
let(:default_model_values) { { moment: moment } }
|
@@ -264,8 +258,8 @@ class ViewModel::RecordTest < ActiveSupport::TestCase
|
|
264
258
|
include CanDeserializeToNew
|
265
259
|
include CanDeserializeToExisting
|
266
260
|
|
267
|
-
it
|
268
|
-
bad_view = default_view.tap { |v| v[
|
261
|
+
it 'raises correctly on an unparseable value' do
|
262
|
+
bad_view = default_view.tap { |v| v['moment'] = 'not a timestamp' }
|
269
263
|
ex = assert_raises(ViewModel::DeserializationError::Validation) do
|
270
264
|
viewmodel_class.deserialize_from_view(bad_view, deserialize_context: create_context)
|
271
265
|
end
|
@@ -273,7 +267,7 @@ class ViewModel::RecordTest < ActiveSupport::TestCase
|
|
273
267
|
assert_match(/could not be deserialized because.*Time/, ex.detail)
|
274
268
|
end
|
275
269
|
|
276
|
-
it
|
270
|
+
it 'raises correctly on an undeserializable value' do
|
277
271
|
bad_model = default_model.tap { |m| m.moment = 2.7 }
|
278
272
|
ex = assert_raises(ViewModel::SerializationError) do
|
279
273
|
viewmodel_class.new(bad_model).to_hash
|
@@ -282,40 +276,40 @@ class ViewModel::RecordTest < ActiveSupport::TestCase
|
|
282
276
|
end
|
283
277
|
end
|
284
278
|
|
285
|
-
describe
|
279
|
+
describe 'with read-only attribute' do
|
286
280
|
let(:attributes) { { read_only: { read_only: true } } }
|
287
281
|
|
288
282
|
include CanSerialize
|
289
283
|
include CanDeserializeToExisting
|
290
284
|
|
291
|
-
it
|
292
|
-
new_view = default_view.tap { |v| v.delete(
|
285
|
+
it 'deserializes to new without the attribute' do
|
286
|
+
new_view = default_view.tap { |v| v.delete('read_only') }
|
293
287
|
vm = viewmodel_class.deserialize_from_view(new_view, deserialize_context: create_context)
|
294
288
|
refute(default_model.equal?(vm.model))
|
295
289
|
assert_nil(vm.model.read_only)
|
296
290
|
assert_edited(vm, new: true)
|
297
291
|
end
|
298
292
|
|
299
|
-
it
|
293
|
+
it 'rejects deserialize from new' do
|
300
294
|
ex = assert_raises(ViewModel::DeserializationError::ReadOnlyAttribute) do
|
301
295
|
viewmodel_class.deserialize_from_view(default_view, deserialize_context: create_context)
|
302
296
|
end
|
303
|
-
assert_equal(
|
297
|
+
assert_equal('read_only', ex.attribute)
|
304
298
|
end
|
305
299
|
|
306
|
-
it
|
307
|
-
new_view = default_view.merge(
|
300
|
+
it 'rejects update if changed' do
|
301
|
+
new_view = default_view.merge('read_only' => 'written')
|
308
302
|
ex = assert_raises(ViewModel::DeserializationError::ReadOnlyAttribute) do
|
309
303
|
viewmodel_class.deserialize_from_view(new_view, deserialize_context: update_context)
|
310
304
|
end
|
311
|
-
assert_equal(
|
305
|
+
assert_equal('read_only', ex.attribute)
|
312
306
|
end
|
313
307
|
end
|
314
308
|
|
315
|
-
describe
|
309
|
+
describe 'with read-only write-once attribute' do
|
316
310
|
let(:attributes) { { write_once: { read_only: true, write_once: true } } }
|
317
311
|
let(:model_body) do
|
318
|
-
->(
|
312
|
+
->(_x) do
|
319
313
|
# For the purposes of testing, we assume a record is new and can be
|
320
314
|
# written once to if write_once is nil. We will never write a nil.
|
321
315
|
def new_record?
|
@@ -328,21 +322,21 @@ class ViewModel::RecordTest < ActiveSupport::TestCase
|
|
328
322
|
include CanDeserializeToNew
|
329
323
|
include CanDeserializeToExisting
|
330
324
|
|
331
|
-
it
|
332
|
-
new_view = default_view.merge(
|
325
|
+
it 'rejects change to attribute' do
|
326
|
+
new_view = default_view.merge('write_once' => 'written')
|
333
327
|
ex = assert_raises(ViewModel::DeserializationError::ReadOnlyAttribute) do
|
334
328
|
viewmodel_class.deserialize_from_view(new_view, deserialize_context: update_context)
|
335
329
|
end
|
336
|
-
assert_equal(
|
330
|
+
assert_equal('write_once', ex.attribute)
|
337
331
|
end
|
338
332
|
end
|
339
333
|
|
340
|
-
describe
|
334
|
+
describe 'with custom serialization' do
|
341
335
|
let(:attributes) { { overridden: {} } }
|
342
336
|
let(:default_view_values) { { overridden: 10 } }
|
343
337
|
let(:default_model_values) { { overridden: 5 } }
|
344
338
|
let(:viewmodel_body) do
|
345
|
-
->(
|
339
|
+
->(_x) do
|
346
340
|
def serialize_overridden(json, serialize_context:)
|
347
341
|
json.overridden model.overridden.try { |o| o * 2 }
|
348
342
|
end
|
@@ -359,12 +353,12 @@ class ViewModel::RecordTest < ActiveSupport::TestCase
|
|
359
353
|
include CanDeserializeToNew
|
360
354
|
include CanDeserializeToExisting
|
361
355
|
|
362
|
-
it
|
363
|
-
new_view = default_view.merge(
|
356
|
+
it 'can be updated' do
|
357
|
+
new_view = default_view.merge('overridden' => '20')
|
364
358
|
|
365
359
|
vm = viewmodel_class.deserialize_from_view(new_view, deserialize_context: update_context)
|
366
360
|
|
367
|
-
assert(default_model.equal?(vm.model),
|
361
|
+
assert(default_model.equal?(vm.model), 'returned model was not the same')
|
368
362
|
assert_equal(10, default_model.overridden)
|
369
363
|
|
370
364
|
assert_edited(vm, changed_attributes: [:overridden])
|
@@ -374,66 +368,68 @@ class ViewModel::RecordTest < ActiveSupport::TestCase
|
|
374
368
|
Nested = Struct.new(:member)
|
375
369
|
|
376
370
|
class NestedView < TestViewModel
|
377
|
-
self.view_name =
|
371
|
+
self.view_name = 'Nested'
|
378
372
|
self.model_class = Nested
|
379
373
|
attribute :member
|
380
374
|
end
|
381
375
|
|
382
|
-
describe
|
383
|
-
let(:default_nested_model) { Nested.new(
|
384
|
-
let(:default_nested_view) { view_base.merge(
|
376
|
+
describe 'with nested viewmodel' do
|
377
|
+
let(:default_nested_model) { Nested.new('member') }
|
378
|
+
let(:default_nested_view) { view_base.merge('_type' => 'Nested', 'member' => 'member') }
|
385
379
|
|
386
|
-
let(:attributes) {{ simple: {}, nested: { using: NestedView } }}
|
380
|
+
let(:attributes) { { simple: {}, nested: { using: NestedView } } }
|
387
381
|
|
388
382
|
let(:default_view_values) { { nested: default_nested_view } }
|
389
383
|
let(:default_model_values) { { nested: default_nested_model } }
|
390
384
|
|
391
|
-
let(:update_context)
|
392
|
-
|
385
|
+
let(:update_context) do
|
386
|
+
TestDeserializeContext.new(targets: [default_model, default_nested_model],
|
387
|
+
access_control: access_control)
|
388
|
+
end
|
393
389
|
|
394
390
|
include CanSerialize
|
395
391
|
include CanDeserializeToNew
|
396
392
|
include CanDeserializeToExisting
|
397
393
|
|
398
|
-
it
|
399
|
-
new_view = default_view.merge(
|
394
|
+
it 'can update the nested value' do
|
395
|
+
new_view = default_view.merge('nested' => default_nested_view.merge('member' => 'changed'))
|
400
396
|
|
401
397
|
vm = viewmodel_class.deserialize_from_view(new_view, deserialize_context: update_context)
|
402
398
|
|
403
|
-
assert(default_model.equal?(vm.model),
|
404
|
-
assert(default_nested_model.equal?(vm.model.nested),
|
399
|
+
assert(default_model.equal?(vm.model), 'returned model was not the same')
|
400
|
+
assert(default_nested_model.equal?(vm.model.nested), 'returned nested model was not the same')
|
405
401
|
|
406
|
-
assert_equal(
|
402
|
+
assert_equal('changed', default_model.nested.member)
|
407
403
|
|
408
404
|
assert_unchanged(vm)
|
409
405
|
assert_edited(vm.nested, changed_attributes: [:member])
|
410
406
|
end
|
411
407
|
|
412
|
-
it
|
408
|
+
it 'can replace the nested value' do
|
413
409
|
# The value will be unified if it is different after deserialization
|
414
|
-
new_view = default_view.merge(
|
410
|
+
new_view = default_view.merge('nested' => default_nested_view.merge('member' => 'changed'))
|
415
411
|
|
416
412
|
partial_update_context = TestDeserializeContext.new(targets: [default_model],
|
417
413
|
access_control: access_control)
|
418
414
|
|
419
415
|
vm = viewmodel_class.deserialize_from_view(new_view, deserialize_context: partial_update_context)
|
420
416
|
|
421
|
-
assert(default_model.equal?(vm.model),
|
422
|
-
refute(default_nested_model.equal?(vm.model.nested),
|
417
|
+
assert(default_model.equal?(vm.model), 'returned model was not the same')
|
418
|
+
refute(default_nested_model.equal?(vm.model.nested), 'returned nested model was the same')
|
423
419
|
|
424
420
|
assert_edited(vm, new: false, changed_attributes: [:nested])
|
425
421
|
assert_edited(vm.nested, new: true, changed_attributes: [:member])
|
426
422
|
end
|
427
423
|
end
|
428
424
|
|
429
|
-
describe
|
430
|
-
let(:default_nested_model_1) { Nested.new(
|
431
|
-
let(:default_nested_view_1) { view_base.merge(
|
425
|
+
describe 'with array of nested viewmodel' do
|
426
|
+
let(:default_nested_model_1) { Nested.new('member1') }
|
427
|
+
let(:default_nested_view_1) { view_base.merge('_type' => 'Nested', 'member' => 'member1') }
|
432
428
|
|
433
|
-
let(:default_nested_model_2) { Nested.new(
|
434
|
-
let(:default_nested_view_2) { view_base.merge(
|
429
|
+
let(:default_nested_model_2) { Nested.new('member2') }
|
430
|
+
let(:default_nested_view_2) { view_base.merge('_type' => 'Nested', 'member' => 'member2') }
|
435
431
|
|
436
|
-
let(:attributes) {{ simple: {}, nested: { using: NestedView, array: true } }}
|
432
|
+
let(:attributes) { { simple: {}, nested: { using: NestedView, array: true } } }
|
437
433
|
|
438
434
|
let(:default_view_values) { { nested: [default_nested_view_1, default_nested_view_2] } }
|
439
435
|
let(:default_model_values) { { nested: [default_nested_model_1, default_nested_model_2] } }
|
@@ -447,20 +443,20 @@ class ViewModel::RecordTest < ActiveSupport::TestCase
|
|
447
443
|
include CanDeserializeToNew
|
448
444
|
include CanDeserializeToExisting
|
449
445
|
|
450
|
-
it
|
451
|
-
new_view = default_view.merge(
|
446
|
+
it 'rejects change to attribute' do
|
447
|
+
new_view = default_view.merge('nested' => 'terrible')
|
452
448
|
ex = assert_raises(ViewModel::DeserializationError::InvalidAttributeType) do
|
453
449
|
viewmodel_class.deserialize_from_view(new_view, deserialize_context: update_context)
|
454
450
|
end
|
455
|
-
assert_equal(
|
456
|
-
assert_equal(
|
457
|
-
assert_equal(
|
451
|
+
assert_equal('nested', ex.attribute)
|
452
|
+
assert_equal('Array', ex.expected_type)
|
453
|
+
assert_equal('String', ex.provided_type)
|
458
454
|
end
|
459
455
|
|
460
|
-
it
|
461
|
-
default_view[
|
456
|
+
it 'can edit a nested value' do
|
457
|
+
default_view['nested'][0]['member'] = 'changed'
|
462
458
|
vm = viewmodel_class.deserialize_from_view(default_view, deserialize_context: update_context)
|
463
|
-
assert(default_model.equal?(vm.model),
|
459
|
+
assert(default_model.equal?(vm.model), 'returned model was not the same')
|
464
460
|
assert_equal(2, vm.model.nested.size)
|
465
461
|
assert(default_nested_model_1.equal?(vm.model.nested[0]))
|
466
462
|
assert(default_nested_model_2.equal?(vm.model.nested[1]))
|
@@ -469,18 +465,18 @@ class ViewModel::RecordTest < ActiveSupport::TestCase
|
|
469
465
|
assert_edited(vm.nested[0], changed_attributes: [:member])
|
470
466
|
end
|
471
467
|
|
472
|
-
it
|
473
|
-
default_view[
|
468
|
+
it 'can append a nested value' do
|
469
|
+
default_view['nested'] << view_base.merge('_type' => 'Nested', 'member' => 'member3')
|
474
470
|
|
475
471
|
vm = viewmodel_class.deserialize_from_view(default_view, deserialize_context: update_context)
|
476
472
|
|
477
|
-
assert(default_model.equal?(vm.model),
|
473
|
+
assert(default_model.equal?(vm.model), 'returned model was not the same')
|
478
474
|
assert_equal(3, vm.model.nested.size)
|
479
475
|
assert(default_nested_model_1.equal?(vm.model.nested[0]))
|
480
476
|
assert(default_nested_model_2.equal?(vm.model.nested[1]))
|
481
477
|
|
482
478
|
vm.model.nested.each_with_index do |nvm, i|
|
483
|
-
assert_equal("member#{i+1}", nvm.member)
|
479
|
+
assert_equal("member#{i + 1}", nvm.member)
|
484
480
|
end
|
485
481
|
|
486
482
|
assert_edited(vm, changed_attributes: [:nested])
|
@@ -488,5 +484,4 @@ class ViewModel::RecordTest < ActiveSupport::TestCase
|
|
488
484
|
end
|
489
485
|
end
|
490
486
|
end
|
491
|
-
|
492
487
|
end
|