iknow_view_models 3.2.0 → 3.2.5
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 +17 -15
- 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.rb +20 -5
- 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 +14 -2
- 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 +42 -33
- 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 +81 -67
- 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 +158 -145
- data/test/unit/view_model/registry_test.rb +38 -0
- data/test/unit/view_model/traversal_context_test.rb +4 -5
- data/test/unit/view_model_test.rb +18 -16
- metadata +10 -6
@@ -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,134 +353,153 @@ 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])
|
371
365
|
end
|
372
366
|
end
|
373
367
|
|
374
|
-
|
368
|
+
describe 'nesting' do
|
369
|
+
let(:nested_model_class) do
|
370
|
+
klass = Struct.new(:member)
|
371
|
+
Object.const_set(:Nested, klass)
|
372
|
+
klass
|
373
|
+
end
|
375
374
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
375
|
+
let(:nested_viewmodel_class) do
|
376
|
+
mc = nested_model_class
|
377
|
+
klass = Class.new(TestViewModel) do
|
378
|
+
self.view_name = 'Nested'
|
379
|
+
self.model_class = mc
|
380
|
+
attribute :member
|
381
|
+
end
|
382
|
+
Object.const_set(:NestedView, klass)
|
383
|
+
klass
|
384
|
+
end
|
381
385
|
|
382
|
-
|
383
|
-
|
384
|
-
|
386
|
+
def teardown
|
387
|
+
Object.send(:remove_const, :Nested)
|
388
|
+
Object.send(:remove_const, :NestedView)
|
389
|
+
ActiveSupport::Dependencies::Reference.clear!
|
390
|
+
super
|
391
|
+
end
|
385
392
|
|
386
|
-
|
393
|
+
describe 'with nested viewmodel' do
|
394
|
+
let(:default_nested_model) { nested_model_class.new('member') }
|
395
|
+
let(:default_nested_view) { view_base.merge('_type' => 'Nested', 'member' => 'member') }
|
387
396
|
|
388
|
-
|
389
|
-
let(:default_model_values) { { nested: default_nested_model } }
|
397
|
+
let(:attributes) { { simple: {}, nested: { using: nested_viewmodel_class } } }
|
390
398
|
|
391
|
-
|
392
|
-
|
399
|
+
let(:default_view_values) { { nested: default_nested_view } }
|
400
|
+
let(:default_model_values) { { nested: default_nested_model } }
|
393
401
|
|
394
|
-
|
395
|
-
|
396
|
-
|
402
|
+
let(:update_context) do
|
403
|
+
TestDeserializeContext.new(targets: [default_model, default_nested_model],
|
404
|
+
access_control: access_control)
|
405
|
+
end
|
397
406
|
|
398
|
-
|
399
|
-
|
407
|
+
include CanSerialize
|
408
|
+
include CanDeserializeToNew
|
409
|
+
include CanDeserializeToExisting
|
400
410
|
|
401
|
-
|
411
|
+
it 'can update the nested value' do
|
412
|
+
new_view = default_view.merge('nested' => default_nested_view.merge('member' => 'changed'))
|
402
413
|
|
403
|
-
|
404
|
-
assert(default_nested_model.equal?(vm.model.nested), "returned nested model was not the same")
|
414
|
+
vm = viewmodel_class.deserialize_from_view(new_view, deserialize_context: update_context)
|
405
415
|
|
406
|
-
|
416
|
+
assert(default_model.equal?(vm.model), 'returned model was not the same')
|
417
|
+
assert(default_nested_model.equal?(vm.model.nested), 'returned nested model was not the same')
|
407
418
|
|
408
|
-
|
409
|
-
assert_edited(vm.nested, changed_attributes: [:member])
|
410
|
-
end
|
419
|
+
assert_equal('changed', default_model.nested.member)
|
411
420
|
|
412
|
-
|
413
|
-
|
414
|
-
|
421
|
+
assert_unchanged(vm)
|
422
|
+
assert_edited(vm.nested, changed_attributes: [:member])
|
423
|
+
end
|
424
|
+
|
425
|
+
it 'can replace the nested value' do
|
426
|
+
# The value will be unified if it is different after deserialization
|
427
|
+
new_view = default_view.merge('nested' => default_nested_view.merge('member' => 'changed'))
|
415
428
|
|
416
|
-
|
417
|
-
|
429
|
+
partial_update_context = TestDeserializeContext.new(targets: [default_model],
|
430
|
+
access_control: access_control)
|
418
431
|
|
419
|
-
|
432
|
+
vm = viewmodel_class.deserialize_from_view(new_view, deserialize_context: partial_update_context)
|
420
433
|
|
421
|
-
|
422
|
-
|
434
|
+
assert(default_model.equal?(vm.model), 'returned model was not the same')
|
435
|
+
refute(default_nested_model.equal?(vm.model.nested), 'returned nested model was the same')
|
423
436
|
|
424
|
-
|
425
|
-
|
437
|
+
assert_edited(vm, new: false, changed_attributes: [:nested])
|
438
|
+
assert_edited(vm.nested, new: true, changed_attributes: [:member])
|
439
|
+
end
|
426
440
|
end
|
427
|
-
end
|
428
441
|
|
429
|
-
|
430
|
-
|
431
|
-
|
442
|
+
describe 'with array of nested viewmodel' do
|
443
|
+
let(:default_nested_model_1) { nested_model_class.new('member1') }
|
444
|
+
let(:default_nested_view_1) { view_base.merge('_type' => 'Nested', 'member' => 'member1') }
|
432
445
|
|
433
|
-
|
434
|
-
|
446
|
+
let(:default_nested_model_2) { nested_model_class.new('member2') }
|
447
|
+
let(:default_nested_view_2) { view_base.merge('_type' => 'Nested', 'member' => 'member2') }
|
435
448
|
|
436
|
-
|
449
|
+
let(:attributes) { { simple: {}, nested: { using: nested_viewmodel_class, array: true } } }
|
437
450
|
|
438
|
-
|
439
|
-
|
451
|
+
let(:default_view_values) { { nested: [default_nested_view_1, default_nested_view_2] } }
|
452
|
+
let(:default_model_values) { { nested: [default_nested_model_1, default_nested_model_2] } }
|
440
453
|
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
454
|
+
let(:update_context) {
|
455
|
+
TestDeserializeContext.new(targets: [default_model, default_nested_model_1, default_nested_model_2],
|
456
|
+
access_control: access_control)
|
457
|
+
}
|
445
458
|
|
446
|
-
|
447
|
-
|
448
|
-
|
459
|
+
include CanSerialize
|
460
|
+
include CanDeserializeToNew
|
461
|
+
include CanDeserializeToExisting
|
449
462
|
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
463
|
+
it 'rejects change to attribute' do
|
464
|
+
new_view = default_view.merge('nested' => 'terrible')
|
465
|
+
ex = assert_raises(ViewModel::DeserializationError::InvalidAttributeType) do
|
466
|
+
viewmodel_class.deserialize_from_view(new_view, deserialize_context: update_context)
|
467
|
+
end
|
468
|
+
assert_equal('nested', ex.attribute)
|
469
|
+
assert_equal('Array', ex.expected_type)
|
470
|
+
assert_equal('String', ex.provided_type)
|
454
471
|
end
|
455
|
-
assert_equal("nested", ex.attribute)
|
456
|
-
assert_equal("Array", ex.expected_type)
|
457
|
-
assert_equal("String", ex.provided_type)
|
458
|
-
end
|
459
472
|
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
473
|
+
it 'can edit a nested value' do
|
474
|
+
default_view['nested'][0]['member'] = 'changed'
|
475
|
+
vm = viewmodel_class.deserialize_from_view(default_view, deserialize_context: update_context)
|
476
|
+
assert(default_model.equal?(vm.model), 'returned model was not the same')
|
477
|
+
assert_equal(2, vm.model.nested.size)
|
478
|
+
assert(default_nested_model_1.equal?(vm.model.nested[0]))
|
479
|
+
assert(default_nested_model_2.equal?(vm.model.nested[1]))
|
467
480
|
|
468
|
-
|
469
|
-
|
470
|
-
|
481
|
+
assert_unchanged(vm)
|
482
|
+
assert_edited(vm.nested[0], changed_attributes: [:member])
|
483
|
+
end
|
471
484
|
|
472
|
-
|
473
|
-
|
485
|
+
it 'can append a nested value' do
|
486
|
+
default_view['nested'] << view_base.merge('_type' => 'Nested', 'member' => 'member3')
|
474
487
|
|
475
|
-
|
488
|
+
vm = viewmodel_class.deserialize_from_view(default_view, deserialize_context: update_context)
|
476
489
|
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
490
|
+
assert(default_model.equal?(vm.model), 'returned model was not the same')
|
491
|
+
assert_equal(3, vm.model.nested.size)
|
492
|
+
assert(default_nested_model_1.equal?(vm.model.nested[0]))
|
493
|
+
assert(default_nested_model_2.equal?(vm.model.nested[1]))
|
481
494
|
|
482
|
-
|
483
|
-
|
484
|
-
|
495
|
+
vm.model.nested.each_with_index do |nvm, i|
|
496
|
+
assert_equal("member#{i + 1}", nvm.member)
|
497
|
+
end
|
485
498
|
|
486
|
-
|
487
|
-
|
499
|
+
assert_edited(vm, changed_attributes: [:nested])
|
500
|
+
assert_edited(vm.nested[2], new: true, changed_attributes: [:member])
|
501
|
+
end
|
488
502
|
end
|
489
503
|
end
|
490
504
|
end
|
491
|
-
|
492
505
|
end
|