iknow_view_models 3.4.1 → 3.5.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.
@@ -13,8 +13,11 @@ require 'acts_as_manual_list'
13
13
 
14
14
  # models for ARVM controller test
15
15
  module ControllerTestModels
16
- def before_all
17
- super
16
+ def build_controller_test_models(externalize: [])
17
+ unsupported_externals = externalize - [:label, :target, :child]
18
+ unless unsupported_externals.empty?
19
+ raise ArgumentError.new("build_controller_test_models cannot externalize: #{unsupported_externals.join(", ")}")
20
+ end
18
21
 
19
22
  build_viewmodel(:Label) do
20
23
  define_schema do |t|
@@ -25,6 +28,7 @@ module ControllerTestModels
25
28
  has_one :target
26
29
  end
27
30
  define_viewmodel do
31
+ root! if externalize.include?(:label)
28
32
  attributes :text
29
33
  end
30
34
  end
@@ -80,16 +84,19 @@ module ControllerTestModels
80
84
  has_one :target, dependent: :destroy, inverse_of: :parent
81
85
  belongs_to :poly, polymorphic: true, dependent: :destroy, inverse_of: :parent
82
86
  belongs_to :category
87
+ has_many :parent_tags
83
88
  end
84
89
  define_viewmodel do
85
90
  root!
86
91
  self.schema_version = 2
87
92
 
88
93
  attributes :name
89
- associations :label, :target
90
- association :children
94
+ association :target, external: externalize.include?(:target)
95
+ association :label, external: externalize.include?(:label)
96
+ association :children, external: externalize.include?(:child)
91
97
  association :poly, viewmodels: [:PolyOne, :PolyTwo]
92
98
  association :category, external: true
99
+ association :tags, through: :parent_tags, external: true
93
100
 
94
101
  migrates from: 1, to: 2 do
95
102
  up do |view, _refs|
@@ -105,6 +112,31 @@ module ControllerTestModels
105
112
  end
106
113
  end
107
114
 
115
+ build_viewmodel(:Tag) do
116
+ define_schema do |t|
117
+ t.string :name, null: false
118
+ end
119
+ define_model do
120
+ has_many :parent_tags
121
+ end
122
+ define_viewmodel do
123
+ root!
124
+ attributes :name
125
+ end
126
+ end
127
+
128
+ build_viewmodel(:ParentTag) do
129
+ define_schema do |t|
130
+ t.references :parent, foreign_key: true
131
+ t.references :tag, foreign_key: true
132
+ end
133
+ define_model do
134
+ belongs_to :parent
135
+ belongs_to :tag
136
+ end
137
+ no_viewmodel
138
+ end
139
+
108
140
  build_viewmodel(:Child) do
109
141
  define_schema do |t|
110
142
  t.references :parent, null: false, foreign_key: true
@@ -122,6 +154,7 @@ module ControllerTestModels
122
154
  validates :age, numericality: { less_than: 42 }, allow_nil: true
123
155
  end
124
156
  define_viewmodel do
157
+ root! if externalize.include?(:child)
125
158
  attributes :name, :age
126
159
  acts_as_list :position
127
160
  end
@@ -138,11 +171,22 @@ module ControllerTestModels
138
171
  belongs_to :label, dependent: :destroy
139
172
  end
140
173
  define_viewmodel do
174
+ root! if externalize.include?(:target)
141
175
  attributes :text
142
176
  association :label
143
177
  end
144
178
  end
145
179
  end
180
+
181
+ def make_parent(name: 'p', child_names: ['c1', 'c2'])
182
+ Parent.create(
183
+ name: name,
184
+ children: child_names.each_with_index.map { |c, pos|
185
+ Child.new(name: "c#{pos + 1}", position: (pos + 1).to_f)
186
+ },
187
+ label: Label.new,
188
+ target: Target.new)
189
+ end
146
190
  end
147
191
 
148
192
  ## Dummy Rails Controllers
@@ -253,43 +297,30 @@ module CallbackTracing
253
297
  end
254
298
 
255
299
  module ControllerTestControllers
300
+ CONTROLLER_NAMES = [
301
+ :ParentController,
302
+ :ChildController,
303
+ :LabelController,
304
+ :TargetController,
305
+ :CategoryController,
306
+ :TagController,
307
+ ]
308
+
256
309
  def before_all
257
310
  super
258
311
 
259
- Class.new(DummyController) do |_c|
260
- Object.const_set(:ParentController, self)
261
- include ViewModel::ActiveRecord::Controller
262
- include CallbackTracing
263
- self.access_control = ViewModel::AccessControl::Open
264
- end
265
-
266
- Class.new(DummyController) do |_c|
267
- Object.const_set(:ChildController, self)
268
- include ViewModel::ActiveRecord::Controller
269
- include CallbackTracing
270
- self.access_control = ViewModel::AccessControl::Open
271
- nested_in :parent, as: :children
272
- end
273
-
274
- Class.new(DummyController) do |_c|
275
- Object.const_set(:LabelController, self)
276
- include ViewModel::ActiveRecord::Controller
277
- include CallbackTracing
278
- self.access_control = ViewModel::AccessControl::Open
279
- nested_in :parent, as: :label
280
- end
281
-
282
- Class.new(DummyController) do |_c|
283
- Object.const_set(:TargetController, self)
284
- include ViewModel::ActiveRecord::Controller
285
- include CallbackTracing
286
- self.access_control = ViewModel::AccessControl::Open
287
- nested_in :parent, as: :target
312
+ CONTROLLER_NAMES.each do |name|
313
+ Class.new(DummyController) do |_c|
314
+ Object.const_set(name, self)
315
+ include ViewModel::ActiveRecord::Controller
316
+ include CallbackTracing
317
+ self.access_control = ViewModel::AccessControl::Open
318
+ end
288
319
  end
289
320
  end
290
321
 
291
322
  def after_all
292
- [:ParentController, :ChildController, :LabelController, :TargetController].each do |name|
323
+ CONTROLLER_NAMES.each do |name|
293
324
  Object.send(:remove_const, name)
294
325
  end
295
326
  ActiveSupport::Dependencies::Reference.clear!
@@ -0,0 +1,599 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require 'minitest/unit'
5
+ require 'minitest/hooks'
6
+
7
+ require 'view_model'
8
+ require 'view_model/active_record'
9
+
10
+ require_relative '../../../helpers/controller_test_helpers'
11
+ require_relative '../../../helpers/callback_tracer'
12
+
13
+ class ViewModel::ActiveRecord::ControllerNestedTest < ActiveSupport::TestCase
14
+ include ARVMTestUtilities
15
+ include ControllerTestModels
16
+ include ControllerTestControllers
17
+
18
+ def before_all
19
+ super
20
+
21
+ build_controller_test_models(externalize: [:label, :child, :target])
22
+ end
23
+
24
+ def setup
25
+ super
26
+ @parent = make_parent
27
+ @parent_view = ParentView.new(@parent)
28
+
29
+ enable_logging!
30
+ end
31
+
32
+ #### Controller for nested model
33
+
34
+ def test_nested_collection_index_associated
35
+ _distractor = Parent.create(name: 'p2', children: [Child.new(name: 'c3', position: 1)])
36
+
37
+ childcontroller = ChildController.new(params: {
38
+ owner_viewmodel: 'parent',
39
+ association_name: 'children',
40
+ parent_id: @parent.id
41
+ })
42
+ childcontroller.invoke(:index_associated)
43
+
44
+ assert_equal(200, childcontroller.status)
45
+
46
+ expected_children = @parent.children
47
+ assert_equal({ 'data' => expected_children.map { |c| ChildView.new(c).to_hash } },
48
+ childcontroller.hash_response)
49
+
50
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
51
+ end
52
+
53
+ def test_nested_collection_index
54
+ distractor = Parent.create(name: 'p2', children: [Child.new(name: 'c3', position: 1)])
55
+ childcontroller = ChildController.new
56
+
57
+ childcontroller.invoke(:index)
58
+
59
+ assert_equal(200, childcontroller.status)
60
+
61
+ expected_children = @parent.children + distractor.children
62
+ assert_equal({ 'data' => expected_children.map { |c| ChildView.new(c).to_hash } },
63
+ childcontroller.hash_response)
64
+ end
65
+
66
+ def test_nested_collection_append_one
67
+ data = { '_type' => 'Child', 'name' => 'c3' }
68
+ childcontroller = ChildController.new(params: {
69
+ owner_viewmodel: 'parent',
70
+ association_name: 'children',
71
+ parent_id: @parent.id,
72
+ data: data,
73
+ })
74
+
75
+ childcontroller.invoke(:append)
76
+
77
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
78
+
79
+ @parent.reload
80
+
81
+ assert_equal(%w[c1 c2 c3], @parent.children.order(:position).pluck(:name))
82
+ assert_equal({ 'data' => ChildView.new(@parent.children.last).to_hash },
83
+ childcontroller.hash_response)
84
+
85
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
86
+ end
87
+
88
+ def test_nested_collection_append_many
89
+ data = [{ '_type' => 'Child', 'name' => 'c3' },
90
+ { '_type' => 'Child', 'name' => 'c4' },]
91
+
92
+ childcontroller = ChildController.new(params: {
93
+ owner_viewmodel: 'parent',
94
+ association_name: 'children',
95
+ parent_id: @parent.id,
96
+ data: data,
97
+ })
98
+ childcontroller.invoke(:append)
99
+
100
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
101
+
102
+ @parent.reload
103
+
104
+ assert_equal(%w[c1 c2 c3 c4], @parent.children.order(:position).pluck(:name))
105
+ new_children_hashes = @parent.children.last(2).map { |c| ChildView.new(c).to_hash }
106
+ assert_equal({ 'data' => new_children_hashes },
107
+ childcontroller.hash_response)
108
+
109
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
110
+ end
111
+
112
+ # FIXME: nested controllers really need to be to other roots; children aren't roots.
113
+ def test_nested_collection_replace
114
+ # Parent.children
115
+ old_children = @parent.children
116
+
117
+ data = [{ '_type' => 'Child', 'name' => 'newc1' },
118
+ { '_type' => 'Child', 'name' => 'newc2' },]
119
+
120
+ childcontroller = ChildController.new(params: {
121
+ owner_viewmodel: 'parent',
122
+ association_name: 'children',
123
+ parent_id: @parent.id,
124
+ data: data,
125
+ })
126
+ childcontroller.invoke(:replace)
127
+
128
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
129
+
130
+ @parent.reload
131
+
132
+ assert_equal(%w[newc1 newc2], @parent.children.order(:position).pluck(:name))
133
+ assert_predicate(Child.where(id: old_children.map(&:id)), :empty?)
134
+
135
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
136
+ end
137
+
138
+ def test_nested_collection_replace_bad_data
139
+ data = [{ 'name' => 'nc' }]
140
+ childcontroller = ChildController.new(params: {
141
+ owner_viewmodel: 'parent',
142
+ association_name: 'children',
143
+ parent_id: @parent.id,
144
+ data: data,
145
+ })
146
+
147
+ childcontroller.invoke(:replace)
148
+
149
+ assert_equal(400, childcontroller.status)
150
+
151
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
152
+ end
153
+
154
+ def test_nested_collection_replace_bulk
155
+ other_parent = make_parent(name: 'p_other', child_names: ['other_c1', 'other_c2'])
156
+
157
+ old_children = other_parent.children + @parent.children
158
+
159
+ data = {
160
+ '_type' => '_bulk_update',
161
+ 'updates' => [
162
+ {
163
+ 'id' => @parent.id,
164
+ 'update' => [
165
+ { '_type' => 'Child', 'name' => 'newc1' },
166
+ { '_type' => 'Child', 'name' => 'newc2' },],
167
+ },
168
+ {
169
+ 'id' => other_parent.id,
170
+ 'update' => [
171
+ { '_type' => 'Child', 'name' => 'other_newc1' },
172
+ { '_type' => 'Child', 'name' => 'other_newc2' },],
173
+ }
174
+ ],
175
+ }
176
+
177
+ childcontroller = ChildController.new(params: {
178
+ owner_viewmodel: 'parent',
179
+ association_name: 'children',
180
+ data: data,
181
+ })
182
+
183
+ childcontroller.invoke(:replace_bulk)
184
+
185
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
186
+
187
+ @parent.reload
188
+ other_parent.reload
189
+
190
+ assert_equal(%w[newc1 newc2], @parent.children.order(:position).pluck(:name))
191
+ assert_equal(%w[other_newc1 other_newc2], other_parent.children.order(:position).pluck(:name))
192
+
193
+ assert_predicate(Child.where(id: old_children.map(&:id)), :empty?)
194
+
195
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
196
+ end
197
+
198
+
199
+ def test_nested_collection_disassociate_one
200
+ old_child = @parent.children.first
201
+ childcontroller = ChildController.new(params: {
202
+ owner_viewmodel: 'parent',
203
+ association_name: 'children',
204
+ parent_id: @parent.id,
205
+ id: old_child.id,
206
+ })
207
+ childcontroller.invoke(:disassociate)
208
+
209
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
210
+
211
+ @parent.reload
212
+
213
+ assert_equal(%w[c2], @parent.children.order(:position).pluck(:name))
214
+ assert_predicate(Child.where(id: old_child.id), :empty?)
215
+
216
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
217
+ end
218
+
219
+ def test_nested_collection_disassociate_many
220
+ old_children = @parent.children
221
+
222
+ childcontroller = ChildController.new(params: {
223
+ owner_viewmodel: 'parent',
224
+ association_name: 'children',
225
+ parent_id: @parent.id,
226
+ })
227
+ childcontroller.invoke(:disassociate_all)
228
+
229
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
230
+
231
+ @parent.reload
232
+
233
+ assert_predicate(@parent.children, :empty?)
234
+ assert_predicate(Child.where(id: old_children.map(&:id)), :empty?)
235
+
236
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
237
+ end
238
+
239
+ # direct methods on nested controller
240
+ def test_nested_collection_destroy
241
+ old_child = @parent.children.first
242
+ childcontroller = ChildController.new(params: { id: old_child.id })
243
+ childcontroller.invoke(:destroy)
244
+
245
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
246
+
247
+ @parent.reload
248
+
249
+ assert_equal(%w[c2], @parent.children.order(:position).pluck(:name))
250
+ assert_predicate(Child.where(id: old_child.id), :empty?)
251
+ end
252
+
253
+ def test_nested_collection_update
254
+ old_child = @parent.children.first
255
+
256
+ data = { 'id' => old_child.id,
257
+ '_type' => 'Child',
258
+ 'name' => 'new_name' }
259
+
260
+ childcontroller = ChildController.new(params: { data: data })
261
+ childcontroller.invoke(:create)
262
+
263
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
264
+
265
+ old_child.reload
266
+
267
+ assert_equal('new_name', old_child.name)
268
+ assert_equal({ 'data' => ChildView.new(old_child).to_hash },
269
+ childcontroller.hash_response)
270
+ end
271
+
272
+ def test_nested_collection_show
273
+ old_child = @parent.children.first
274
+
275
+ childcontroller = ChildController.new(params: { id: old_child.id })
276
+ childcontroller.invoke(:show)
277
+
278
+ assert_equal({ 'data' => ChildView.new(old_child).to_hash },
279
+ childcontroller.hash_response)
280
+
281
+ assert_equal(200, childcontroller.status)
282
+ end
283
+
284
+ ## Single association
285
+
286
+ def test_nested_singular_replace_from_parent
287
+ old_label = @parent.label
288
+
289
+ data = { '_type' => 'Label', 'text' => 'new label' }
290
+ labelcontroller = LabelController.new(params: {
291
+ owner_viewmodel: 'parent',
292
+ association_name: 'label',
293
+ parent_id: @parent.id,
294
+ data: data,
295
+ })
296
+ labelcontroller.invoke(:create_associated)
297
+
298
+ assert_equal(200, labelcontroller.status, labelcontroller.hash_response)
299
+
300
+ @parent.reload
301
+
302
+ assert_equal({ 'data' => { '_type' => 'Label',
303
+ '_version' => 1,
304
+ 'id' => @parent.label.id,
305
+ 'text' => 'new label' } },
306
+ labelcontroller.hash_response)
307
+
308
+ refute_equal(old_label, @parent.label)
309
+ assert_equal('new label', @parent.label.text)
310
+
311
+ assert_all_hooks_nested_inside_parent_hook(labelcontroller.hook_trace)
312
+ end
313
+
314
+ def test_nested_singular_show_from_parent
315
+ old_label = @parent.label
316
+
317
+ labelcontroller = LabelController.new(params: {
318
+ owner_viewmodel: 'parent',
319
+ association_name: 'label',
320
+ parent_id: @parent.id,
321
+ })
322
+ labelcontroller.invoke(:show_associated)
323
+
324
+ assert_equal(200, labelcontroller.status, labelcontroller.hash_response)
325
+
326
+ assert_equal({ 'data' => LabelView.new(old_label).to_hash },
327
+ labelcontroller.hash_response)
328
+
329
+ assert_all_hooks_nested_inside_parent_hook(labelcontroller.hook_trace)
330
+ end
331
+
332
+ def test_nested_singular_destroy_from_parent
333
+ old_target = @parent.target
334
+
335
+ targetcontroller = TargetController.new(params: {
336
+ owner_viewmodel: 'parent',
337
+ association_name: 'target',
338
+ parent_id: @parent.id,
339
+ })
340
+ targetcontroller.invoke(:destroy_associated)
341
+
342
+ @parent.reload
343
+
344
+ assert_equal(200, targetcontroller.status, targetcontroller.hash_response)
345
+ assert_equal({ 'data' => nil }, targetcontroller.hash_response)
346
+
347
+ assert_nil(@parent.target)
348
+ assert_predicate(Target.where(id: old_target.id), :empty?)
349
+
350
+ assert_all_hooks_nested_inside_parent_hook(targetcontroller.hook_trace)
351
+ end
352
+
353
+ def test_nested_singular_update_from_parent
354
+ old_label = @parent.label
355
+
356
+ data = { '_type' => 'Label', 'id' => old_label.id, 'text' => 'new label' }
357
+ labelcontroller = LabelController.new(params: {
358
+ owner_viewmodel: 'parent',
359
+ association_name: 'label',
360
+ parent_id: @parent.id,
361
+ data: data,
362
+ })
363
+ labelcontroller.invoke(:create_associated)
364
+
365
+ assert_equal(200, labelcontroller.status, labelcontroller.hash_response)
366
+
367
+ old_label.reload
368
+
369
+ assert_equal('new label', old_label.text)
370
+ assert_equal({ 'data' => LabelView.new(old_label).to_hash },
371
+ labelcontroller.hash_response)
372
+
373
+ assert_all_hooks_nested_inside_parent_hook(labelcontroller.hook_trace)
374
+ end
375
+
376
+ def test_nested_singular_show_from_id
377
+ old_label = @parent.label
378
+
379
+ labelcontroller = LabelController.new(params: { id: old_label.id })
380
+ labelcontroller.invoke(:show)
381
+
382
+ assert_equal(200, labelcontroller.status, labelcontroller.hash_response)
383
+
384
+ assert_equal({ 'data' => LabelView.new(old_label).to_hash },
385
+ labelcontroller.hash_response)
386
+ end
387
+
388
+ def test_nested_singular_destroy_from_id
389
+ # can't directly destroy pointed-to label that's referenced from parent:
390
+ # foreign key violation. Destroy target instead.
391
+ old_target = @parent.target
392
+
393
+ targetcontroller = TargetController.new(params: { id: old_target.id })
394
+ targetcontroller.invoke(:destroy)
395
+
396
+ @parent.reload
397
+
398
+ assert_equal(200, targetcontroller.status, targetcontroller.hash_response)
399
+ assert_equal({ 'data' => nil }, targetcontroller.hash_response)
400
+
401
+ assert_nil(@parent.target)
402
+ assert_predicate(Target.where(id: old_target.id), :empty?)
403
+ end
404
+
405
+ def test_nested_singular_update
406
+ old_label = @parent.label
407
+
408
+ data = { '_type' => 'Label', 'id' => old_label.id, 'text' => 'new label' }
409
+ labelcontroller = LabelController.new(params: { data: data })
410
+ labelcontroller.invoke(:create)
411
+
412
+ assert_equal(200, labelcontroller.status, labelcontroller.hash_response)
413
+
414
+ old_label.reload
415
+
416
+ assert_equal('new label', old_label.text)
417
+ assert_equal({ 'data' => LabelView.new(old_label).to_hash },
418
+ labelcontroller.hash_response)
419
+ end
420
+
421
+ def test_nested_singular_replace_bulk
422
+ other_parent = make_parent(name: 'p_other', child_names: ['other_c1', 'other_c2'])
423
+
424
+ target = @parent.target
425
+ other_target = other_parent.target
426
+
427
+ data = {
428
+ '_type' => '_bulk_update',
429
+ 'updates' => [
430
+ {
431
+ 'id' => @parent.id,
432
+ 'update' => {
433
+ '_type' => 'Target',
434
+ 'id' => @parent.target.id,
435
+ 'text' => 'parent, new target text'
436
+ }
437
+ },
438
+ {
439
+ 'id' => other_parent.id,
440
+ 'update' => {
441
+ '_type' => 'Target',
442
+ 'id' => other_parent.target.id,
443
+ 'text' => 'other parent, new target text'
444
+ }
445
+ }
446
+ ],
447
+ }
448
+
449
+ targetcontroller = TargetController.new(params: {
450
+ owner_viewmodel: 'parent',
451
+ association_name: 'target',
452
+ data: data,
453
+ })
454
+
455
+ targetcontroller.invoke(:create_associated_bulk)
456
+
457
+ assert_equal(200, targetcontroller.status, targetcontroller.hash_response)
458
+
459
+ target.reload
460
+ other_target.reload
461
+
462
+ assert_equal('parent, new target text', target.text)
463
+ assert_equal('other parent, new target text', other_target.text)
464
+
465
+ response = targetcontroller.hash_response
466
+ response['data']['updates'].sort_by! { |x| x.fetch('id') }
467
+
468
+ assert_equal(
469
+ {
470
+ 'data' => {
471
+ '_type' => '_bulk_update',
472
+ 'updates' => [
473
+ {
474
+ 'id' => @parent.id,
475
+ 'update' => TargetView.new(target).to_hash,
476
+ },
477
+ {
478
+ 'id' => other_parent.id,
479
+ 'update' => TargetView.new(other_target).to_hash,
480
+ },
481
+ ].sort_by { |x| x.fetch('id') }
482
+ }
483
+ },
484
+ response,
485
+ )
486
+ end
487
+
488
+ # Singular shared
489
+
490
+ def test_nested_shared_singular_replace_bulk
491
+ data = {
492
+ '_type' => '_bulk_update',
493
+ 'updates' => [
494
+ {
495
+ 'id' => @parent.id,
496
+ 'update' => { '_ref' => 'new_cat' },
497
+ }
498
+ ]
499
+ }
500
+
501
+ references = {
502
+ 'new_cat' => {
503
+ '_type' => 'Category',
504
+ '_new' => true,
505
+ 'name' => 'cat name'
506
+ }
507
+ }
508
+
509
+ category_controller = CategoryController.new(params: {
510
+ owner_viewmodel: 'parent',
511
+ association_name: 'category',
512
+ data: data,
513
+ references: references,
514
+ })
515
+
516
+ category_controller.invoke(:replace_bulk)
517
+
518
+ response = category_controller.hash_response
519
+
520
+ data, references = response.values_at('data', 'references')
521
+ ref_key = references.keys.first
522
+
523
+ assert_equal(
524
+ {
525
+ '_type' => '_bulk_update',
526
+ 'updates' => [{
527
+ 'id' => @parent.id,
528
+ 'update' => { '_ref' => ref_key }
529
+ }],
530
+ },
531
+ data,
532
+ )
533
+
534
+ @parent.reload
535
+
536
+ assert_equal(
537
+ {
538
+ ref_key => CategoryView.new(@parent.category).to_hash,
539
+ },
540
+ references,
541
+ )
542
+ end
543
+
544
+ # Collection shared
545
+
546
+ def test_nested_shared_collection_replace_bulk
547
+ data = {
548
+ '_type' => '_bulk_update',
549
+ 'updates' => [
550
+ {
551
+ 'id' => @parent.id,
552
+ 'update' => [{ '_ref' => 'new_tag' }],
553
+ }
554
+ ]
555
+ }
556
+
557
+ references = {
558
+ 'new_tag' => {
559
+ '_type' => 'Tag',
560
+ '_new' => true,
561
+ 'name' => 'tag name'
562
+ }
563
+ }
564
+
565
+ tags_controller = TagController.new(params: {
566
+ owner_viewmodel: 'parent',
567
+ association_name: 'tags',
568
+ data: data,
569
+ references: references,
570
+ })
571
+
572
+ tags_controller.invoke(:replace_bulk)
573
+
574
+ response = tags_controller.hash_response
575
+
576
+ data, references = response.values_at('data', 'references')
577
+ ref_key = references.keys.first
578
+
579
+ assert_equal(
580
+ {
581
+ '_type' => '_bulk_update',
582
+ 'updates' => [{
583
+ 'id' => @parent.id,
584
+ 'update' => [{ '_ref' => ref_key }]
585
+ }],
586
+ },
587
+ data,
588
+ )
589
+
590
+ @parent.reload
591
+
592
+ assert_equal(
593
+ {
594
+ ref_key => TagView.new(@parent.parent_tags.first.tag).to_hash,
595
+ },
596
+ references,
597
+ )
598
+ end
599
+ end