iknow_view_models 2.9.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/iknow_view_models.gemspec +1 -1
  3. data/lib/iknow_view_models/version.rb +1 -1
  4. data/lib/view_model/active_record/association_data.rb +206 -92
  5. data/lib/view_model/active_record/association_manipulation.rb +22 -12
  6. data/lib/view_model/active_record/cache/cacheable_view.rb +3 -13
  7. data/lib/view_model/active_record/cache.rb +2 -2
  8. data/lib/view_model/active_record/cloner.rb +11 -11
  9. data/lib/view_model/active_record/controller.rb +0 -2
  10. data/lib/view_model/active_record/update_context.rb +21 -3
  11. data/lib/view_model/active_record/update_data.rb +43 -45
  12. data/lib/view_model/active_record/update_operation.rb +265 -153
  13. data/lib/view_model/active_record/visitor.rb +9 -6
  14. data/lib/view_model/active_record.rb +94 -74
  15. data/lib/view_model/after_transaction_runner.rb +3 -18
  16. data/lib/view_model/changes.rb +24 -16
  17. data/lib/view_model/config.rb +6 -2
  18. data/lib/view_model/deserialization_error.rb +31 -0
  19. data/lib/view_model/deserialize_context.rb +2 -6
  20. data/lib/view_model/error_view.rb +6 -5
  21. data/lib/view_model/record/attribute_data.rb +11 -6
  22. data/lib/view_model/record.rb +44 -24
  23. data/lib/view_model/serialize_context.rb +2 -63
  24. data/lib/view_model.rb +17 -8
  25. data/shell.nix +1 -1
  26. data/test/helpers/arvm_test_utilities.rb +6 -0
  27. data/test/helpers/controller_test_helpers.rb +5 -3
  28. data/test/helpers/viewmodel_spec_helpers.rb +63 -52
  29. data/test/unit/view_model/access_control_test.rb +88 -37
  30. data/test/unit/view_model/active_record/belongs_to_test.rb +110 -178
  31. data/test/unit/view_model/active_record/cache_test.rb +3 -2
  32. data/test/unit/view_model/active_record/cloner_test.rb +1 -1
  33. data/test/unit/view_model/active_record/controller_test.rb +12 -20
  34. data/test/unit/view_model/active_record/has_many_test.rb +540 -316
  35. data/test/unit/view_model/active_record/has_many_through_poly_test.rb +12 -15
  36. data/test/unit/view_model/active_record/has_many_through_test.rb +15 -58
  37. data/test/unit/view_model/active_record/has_one_test.rb +288 -135
  38. data/test/unit/view_model/active_record/poly_test.rb +0 -1
  39. data/test/unit/view_model/active_record/shared_test.rb +21 -39
  40. data/test/unit/view_model/active_record/version_test.rb +3 -2
  41. data/test/unit/view_model/active_record_test.rb +5 -63
  42. data/test/unit/view_model/callbacks_test.rb +1 -0
  43. data/test/unit/view_model/record_test.rb +0 -32
  44. data/test/unit/view_model/traversal_context_test.rb +13 -12
  45. metadata +5 -8
  46. data/test/unit/view_model/active_record/optional_attribute_view_test.rb +0 -58
@@ -1,5 +1,6 @@
1
1
  require_relative "../../../helpers/arvm_test_utilities.rb"
2
2
  require_relative "../../../helpers/arvm_test_models.rb"
3
+ require_relative '../../../helpers/viewmodel_spec_helpers.rb'
3
4
 
4
5
  require "minitest/autorun"
5
6
 
@@ -8,69 +9,29 @@ require "view_model/active_record"
8
9
  class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
9
10
  include ARVMTestUtilities
10
11
 
11
- def self.build_parent(arvm_test_case)
12
- arvm_test_case.build_viewmodel(:Parent) do
13
- define_schema do |t|
14
- t.string :name
15
- end
16
-
17
- define_model do
18
- has_many :children, dependent: :destroy, inverse_of: :parent
19
- end
20
-
21
- define_viewmodel do
22
- attributes :name
23
- associations :children
24
- end
25
- end
26
- end
27
-
28
- def self.build_child(arvm_test_case)
29
- arvm_test_case.build_viewmodel(:Child) do
30
- define_schema do |t|
31
- t.references :parent, null: false, foreign_key: true
32
- t.string :name
33
- t.float :position
34
- end
35
-
36
- define_model do
37
- belongs_to :parent, inverse_of: :children
38
- acts_as_manual_list scope: :parent
39
- end
40
-
41
- define_viewmodel do
42
- attributes :name
43
- acts_as_list :position
44
- end
45
- end
46
-
47
- end
48
-
49
- def before_all
50
- self.class.build_parent(self)
51
- self.class.build_child(self)
52
- end
12
+ extend Minitest::Spec::DSL
13
+ include ViewModelSpecHelpers::ParentAndOrderedChildren
53
14
 
54
15
  def setup
55
16
  super
56
17
 
57
- @parent1 = Parent.new(name: "p1",
58
- children: [Child.new(name: "p1c1", position: 1),
59
- Child.new(name: "p1c2", position: 2),
60
- Child.new(name: "p1c3", position: 3)])
61
- @parent1.save!
18
+ @model1 = model_class.new(name: "p1",
19
+ children: [child_model_class.new(name: "p1c1", position: 1),
20
+ child_model_class.new(name: "p1c2", position: 2),
21
+ child_model_class.new(name: "p1c3", position: 3)])
22
+ @model1.save!
62
23
 
63
- @parent2 = Parent.new(name: "p2",
64
- children: [Child.new(name: "p2c1").tap { |c| c.position = 1 },
65
- Child.new(name: "p2c2").tap { |c| c.position = 2 }])
24
+ @model2 = model_class.new(name: "p2",
25
+ children: [child_model_class.new(name: "p2c1").tap { |c| c.position = 1 },
26
+ child_model_class.new(name: "p2c2").tap { |c| c.position = 2 }])
66
27
 
67
- @parent2.save!
28
+ @model2.save!
68
29
 
69
30
  enable_logging!
70
31
  end
71
32
 
72
33
  def test_load_associated
73
- parentview = ParentView.new(@parent1)
34
+ parentview = viewmodel_class.new(@model1)
74
35
 
75
36
  childviews = parentview.load_associated(:children)
76
37
  assert_equal(3, childviews.size)
@@ -79,14 +40,14 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
79
40
  end
80
41
 
81
42
  def test_serialize_view
82
- view, _refs = serialize_with_references(ParentView.new(@parent1))
43
+ view, _refs = serialize_with_references(viewmodel_class.new(@model1))
83
44
 
84
45
 
85
- assert_equal({ "_type" => "Parent",
46
+ assert_equal({ "_type" => "Model",
86
47
  "_version" => 1,
87
- "id" => @parent1.id,
88
- "name" => @parent1.name,
89
- "children" => @parent1.children.map { |child| { "_type" => "Child",
48
+ "id" => @model1.id,
49
+ "name" => @model1.name,
50
+ "children" => @model1.children.map { |child| { "_type" => "Child",
90
51
  "_version" => 1,
91
52
  "id" => child.id,
92
53
  "name" => child.name } } },
@@ -95,21 +56,21 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
95
56
 
96
57
  def test_loading_batching
97
58
  log_queries do
98
- serialize(ParentView.load)
59
+ serialize(viewmodel_class.load)
99
60
  end
100
- assert_equal(['Parent Load', 'Child Load'],
61
+ assert_equal(['Model Load', 'Child Load'],
101
62
  logged_load_queries)
102
63
  end
103
64
 
104
65
  def test_create_from_view
105
66
  view = {
106
- "_type" => "Parent",
67
+ "_type" => "Model",
107
68
  "name" => "p",
108
69
  "children" => [{ "_type" => "Child", "name" => "c1" },
109
70
  { "_type" => "Child", "name" => "c2" }]
110
71
  }
111
72
 
112
- pv = ParentView.deserialize_from_view(view)
73
+ pv = viewmodel_class.deserialize_from_view(view)
113
74
  p = pv.model
114
75
 
115
76
  assert(!p.changed?)
@@ -126,33 +87,33 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
126
87
  end
127
88
 
128
89
  def test_editability_raises
129
- no_edit_context = ParentView.new_deserialize_context(can_edit: false)
90
+ no_edit_context = viewmodel_class.new_deserialize_context(can_edit: false)
130
91
 
131
92
  assert_raises(ViewModel::AccessControlError) do
132
93
  # append child
133
- ParentView.new(@parent1).append_associated(:children, { "_type" => "Child", "name" => "hi" }, deserialize_context: no_edit_context)
94
+ viewmodel_class.new(@model1).append_associated(:children, { "_type" => "Child", "name" => "hi" }, deserialize_context: no_edit_context)
134
95
  end
135
96
 
136
97
  assert_raises(ViewModel::AccessControlError) do
137
98
  # destroy child
138
- ParentView.new(@parent1).delete_associated(:children, @parent1.children.first.id, deserialize_context: no_edit_context)
99
+ viewmodel_class.new(@model1).delete_associated(:children, @model1.children.first.id, deserialize_context: no_edit_context)
139
100
  end
140
101
  end
141
102
 
142
103
  def test_create_has_many_empty
143
- view = { '_type' => 'Parent', 'name' => 'p', 'children' => [] }
144
- pv = ParentView.deserialize_from_view(view)
104
+ view = { '_type' => 'Model', 'name' => 'p', 'children' => [] }
105
+ pv = viewmodel_class.deserialize_from_view(view)
145
106
  assert(pv.model.children.blank?)
146
107
  end
147
108
 
148
109
  def test_create_has_many
149
- view = { '_type' => 'Parent',
110
+ view = { '_type' => 'Model',
150
111
  'name' => 'p',
151
112
  'children' => [{ '_type' => 'Child', 'name' => 'c1' },
152
113
  { '_type' => 'Child', 'name' => 'c2' }] }
153
114
 
154
- context = ParentView.new_deserialize_context
155
- pv = ParentView.deserialize_from_view(view, deserialize_context: context)
115
+ context = viewmodel_class.new_deserialize_context
116
+ pv = viewmodel_class.deserialize_from_view(view, deserialize_context: context)
156
117
 
157
118
  assert_contains_exactly(
158
119
  [pv.to_reference, pv.children[0].to_reference, pv.children[1].to_reference],
@@ -163,11 +124,11 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
163
124
 
164
125
  def test_nil_multiple_association
165
126
  view = {
166
- "_type" => "Parent",
127
+ "_type" => "Model",
167
128
  "children" => nil
168
129
  }
169
130
  ex = assert_raises(ViewModel::DeserializationError::InvalidSyntax) do
170
- ParentView.deserialize_from_view(view)
131
+ viewmodel_class.deserialize_from_view(view)
171
132
  end
172
133
 
173
134
  assert_match(/Invalid collection update value 'nil'/, ex.message)
@@ -175,39 +136,39 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
175
136
 
176
137
  def test_non_array_multiple_association
177
138
  view = {
178
- "_type" => "Parent",
139
+ "_type" => "Model",
179
140
  "children" => { '_type' => 'Child', 'name' => 'c1' }
180
141
  }
181
142
  ex = assert_raises(ViewModel::DeserializationError::InvalidSyntax) do
182
- ParentView.deserialize_from_view(view)
143
+ viewmodel_class.deserialize_from_view(view)
183
144
  end
184
145
 
185
146
  assert_match(/Errors parsing collection functional update/, ex.message)
186
147
  end
187
148
 
188
149
  def test_replace_has_many
189
- old_children = @parent1.children
150
+ old_children = @model1.children
190
151
 
191
- alter_by_view!(ParentView, @parent1) do |view, refs|
152
+ alter_by_view!(viewmodel_class, @model1) do |view, refs|
192
153
  view['children'] = [{ '_type' => 'Child', 'name' => 'new_child' }]
193
154
  end
194
155
 
195
- assert_equal(['new_child'], @parent1.children.map(&:name))
196
- assert_equal([], Child.where(id: old_children.map(&:id)))
156
+ assert_equal(['new_child'], @model1.children.map(&:name))
157
+ assert_equal([], child_model_class.where(id: old_children.map(&:id)))
197
158
  end
198
159
 
199
160
  def test_replace_associated_has_many
200
- old_children = @parent1.children
161
+ old_children = @model1.children
201
162
 
202
- pv = ParentView.new(@parent1)
203
- context = ParentView.new_deserialize_context
163
+ pv = viewmodel_class.new(@model1)
164
+ context = viewmodel_class.new_deserialize_context
204
165
 
205
166
  nc = pv.replace_associated(:children,
206
167
  [{ '_type' => 'Child', 'name' => 'new_child' }],
207
168
  deserialize_context: context)
208
169
 
209
170
  expected_edit_checks = [pv.to_reference,
210
- *old_children.map { |x| ViewModel::Reference.new(ChildView, x.id) },
171
+ *old_children.map { |x| ViewModel::Reference.new(child_viewmodel_class, x.id) },
211
172
  *nc.map(&:to_reference)]
212
173
 
213
174
  assert_contains_exactly(expected_edit_checks,
@@ -216,16 +177,16 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
216
177
  assert_equal(1, nc.size)
217
178
  assert_equal('new_child', nc[0].name)
218
179
 
219
- @parent1.reload
220
- assert_equal(['new_child'], @parent1.children.map(&:name))
221
- assert_equal([], Child.where(id: old_children.map(&:id)))
180
+ @model1.reload
181
+ assert_equal(['new_child'], @model1.children.map(&:name))
182
+ assert_equal([], child_model_class.where(id: old_children.map(&:id)))
222
183
  end
223
184
 
224
185
  def test_replace_associated_has_many_functional
225
- old_children = @parent1.children
186
+ old_children = @model1.children
226
187
 
227
- pv = ParentView.new(@parent1)
228
- context = ParentView.new_deserialize_context
188
+ pv = viewmodel_class.new(@model1)
189
+ context = viewmodel_class.new_deserialize_context
229
190
 
230
191
  update = build_fupdate do
231
192
  append([{ '_type' => 'Child', 'name' => 'new_child' }])
@@ -237,9 +198,9 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
237
198
  new_child = nc.detect { |c| c.name == 'new_child' }
238
199
 
239
200
  expected_edit_checks = [pv.to_reference,
240
- ViewModel::Reference.new(ChildView, new_child.id),
241
- ViewModel::Reference.new(ChildView, old_children.first.id),
242
- ViewModel::Reference.new(ChildView, old_children.last.id)]
201
+ ViewModel::Reference.new(child_viewmodel_class, new_child.id),
202
+ ViewModel::Reference.new(child_viewmodel_class, old_children.first.id),
203
+ ViewModel::Reference.new(child_viewmodel_class, old_children.last.id)]
243
204
 
244
205
  assert_contains_exactly(expected_edit_checks,
245
206
  context.valid_edit_refs)
@@ -247,187 +208,187 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
247
208
  assert_equal(3, nc.size)
248
209
  assert_equal('renamed p1c1', nc[0].name)
249
210
 
250
- @parent1.reload
251
- assert_equal(['renamed p1c1', 'p1c2', 'new_child'], @parent1.children.order(:position).map(&:name))
252
- assert_equal([], Child.where(id: old_children.last.id))
211
+ @model1.reload
212
+ assert_equal(['renamed p1c1', 'p1c2', 'new_child'], @model1.children.order(:position).map(&:name))
213
+ assert_equal([], child_model_class.where(id: old_children.last.id))
253
214
  end
254
215
 
255
216
  def test_remove_has_many
256
- old_children = @parent1.children
257
- _, context = alter_by_view!(ParentView, @parent1) do |view, refs|
217
+ old_children = @model1.children
218
+ _, context = alter_by_view!(viewmodel_class, @model1) do |view, refs|
258
219
  view['children'] = []
259
220
  end
260
221
 
261
- expected_edit_checks = [ViewModel::Reference.new(ParentView, @parent1.id)] +
262
- old_children.map { |x| ViewModel::Reference.new(ChildView, x.id) }
222
+ expected_edit_checks = [ViewModel::Reference.new(viewmodel_class, @model1.id)] +
223
+ old_children.map { |x| ViewModel::Reference.new(child_viewmodel_class, x.id) }
263
224
 
264
225
  assert_equal(Set.new(expected_edit_checks),
265
226
  context.valid_edit_refs.to_set)
266
227
 
267
- assert_equal([], @parent1.children, 'no children associated with parent1')
268
- assert(Child.where(id: old_children.map(&:id)).blank?, 'all children deleted')
228
+ assert_equal([], @model1.children, 'no children associated with parent1')
229
+ assert(child_model_class.where(id: old_children.map(&:id)).blank?, 'all children deleted')
269
230
  end
270
231
 
271
232
  def test_delete_associated_has_many
272
- c1, c2, c3 = @parent1.children.order(:position).to_a
233
+ c1, c2, c3 = @model1.children.order(:position).to_a
273
234
 
274
- pv = ParentView.new(@parent1)
275
- context = ParentView.new_deserialize_context
235
+ pv = viewmodel_class.new(@model1)
236
+ context = viewmodel_class.new_deserialize_context
276
237
 
277
238
  pv.delete_associated(:children, c1.id,
278
239
  deserialize_context: context)
279
240
 
280
- expected_edit_checks = [ViewModel::Reference.new(ParentView, @parent1.id),
281
- ViewModel::Reference.new(ChildView, c1.id)].to_set
241
+ expected_edit_checks = [ViewModel::Reference.new(viewmodel_class, @model1.id),
242
+ ViewModel::Reference.new(child_viewmodel_class, c1.id)].to_set
282
243
 
283
244
  assert_equal(expected_edit_checks,
284
245
  context.valid_edit_refs.to_set)
285
246
 
286
- @parent1.reload
287
- assert_equal([c2, c3], @parent1.children.order(:position))
288
- assert(Child.where(id: c1.id).blank?, 'old child deleted')
247
+ @model1.reload
248
+ assert_equal([c2, c3], @model1.children.order(:position))
249
+ assert(child_model_class.where(id: c1.id).blank?, 'old child deleted')
289
250
  end
290
251
 
291
252
  def test_edit_has_many
292
- c1, c2, c3 = @parent1.children.order(:position).to_a
253
+ c1, c2, c3 = @model1.children.order(:position).to_a
293
254
 
294
- pv, context = alter_by_view!(ParentView, @parent1) do |view, _refs|
255
+ pv, context = alter_by_view!(viewmodel_class, @model1) do |view, _refs|
295
256
  view['children'].shift
296
257
  view['children'] << { '_type' => 'Child', 'name' => 'new_c' }
297
258
  end
298
259
  nc = pv.children.detect { |c| c.name == 'new_c' }
299
260
 
300
261
  assert_contains_exactly(
301
- [ViewModel::Reference.new(ParentView, @parent1.id),
302
- ViewModel::Reference.new(ChildView, c1.id), # deleted child
303
- ViewModel::Reference.new(ChildView, nc.id)], # created child
262
+ [ViewModel::Reference.new(viewmodel_class, @model1.id),
263
+ ViewModel::Reference.new(child_viewmodel_class, c1.id), # deleted child
264
+ ViewModel::Reference.new(child_viewmodel_class, nc.id)], # created child
304
265
  context.valid_edit_refs)
305
266
 
306
- assert_equal([c2, c3, Child.find_by_name('new_c')],
307
- @parent1.children.order(:position))
308
- assert(Child.where(id: c1.id).blank?)
267
+ assert_equal([c2, c3, child_model_class.find_by_name('new_c')],
268
+ @model1.children.order(:position))
269
+ assert(child_model_class.where(id: c1.id).blank?)
309
270
  end
310
271
 
311
272
  def test_append_associated_move_has_many
312
- c1, c2, c3 = @parent1.children.order(:position).to_a
313
- pv = ParentView.new(@parent1)
273
+ c1, c2, c3 = @model1.children.order(:position).to_a
274
+ pv = viewmodel_class.new(@model1)
314
275
 
315
276
  # insert before
316
277
  pv.append_associated(:children,
317
278
  { '_type' => 'Child', 'id' => c3.id },
318
- before: ViewModel::Reference.new(ChildView, c1.id),
319
- deserialize_context: (context = ParentView.new_deserialize_context))
279
+ before: ViewModel::Reference.new(child_viewmodel_class, c1.id),
280
+ deserialize_context: (context = viewmodel_class.new_deserialize_context))
320
281
 
321
282
  expected_edit_checks = [pv.to_reference]
322
283
  assert_contains_exactly(expected_edit_checks, context.valid_edit_refs)
323
284
 
324
285
  assert_equal([c3, c1, c2],
325
- @parent1.children.order(:position))
286
+ @model1.children.order(:position))
326
287
 
327
288
  # insert after
328
289
  pv.append_associated(:children,
329
290
  { '_type' => 'Child', 'id' => c3.id },
330
- after: ViewModel::Reference.new(ChildView, c1.id),
331
- deserialize_context: (context = ParentView.new_deserialize_context))
291
+ after: ViewModel::Reference.new(child_viewmodel_class, c1.id),
292
+ deserialize_context: (context = viewmodel_class.new_deserialize_context))
332
293
 
333
294
  assert_contains_exactly(expected_edit_checks, context.valid_edit_refs)
334
295
 
335
296
  assert_equal([c1, c3, c2],
336
- @parent1.children.order(:position))
297
+ @model1.children.order(:position))
337
298
 
338
299
  # append
339
300
  pv.append_associated(:children,
340
301
  { '_type' => 'Child', 'id' => c3.id },
341
- deserialize_context: (context = ParentView.new_deserialize_context))
302
+ deserialize_context: (context = viewmodel_class.new_deserialize_context))
342
303
 
343
304
  assert_contains_exactly(expected_edit_checks, context.valid_edit_refs)
344
305
 
345
306
  assert_equal([c1, c2, c3],
346
- @parent1.children.order(:position))
307
+ @model1.children.order(:position))
347
308
 
348
309
  # move from another parent
349
- p2c1 = @parent2.children.order(:position).first
310
+ p2c1 = @model2.children.order(:position).first
350
311
 
351
312
  pv.append_associated(:children,
352
313
  { '_type' => 'Child', 'id' => p2c1.id },
353
- deserialize_context: (context = ParentView.new_deserialize_context))
314
+ deserialize_context: (context = viewmodel_class.new_deserialize_context))
354
315
 
355
- expected_edit_checks = [ViewModel::Reference.new(ParentView, @parent1.id),
356
- ViewModel::Reference.new(ParentView, @parent2.id)]
316
+ expected_edit_checks = [ViewModel::Reference.new(viewmodel_class, @model1.id),
317
+ ViewModel::Reference.new(viewmodel_class, @model2.id)]
357
318
 
358
319
  assert_contains_exactly(expected_edit_checks, context.valid_edit_refs)
359
320
 
360
321
  assert_equal([c1, c2, c3, p2c1],
361
- @parent1.children.order(:position))
322
+ @model1.children.order(:position))
362
323
  end
363
324
 
364
325
  def test_append_associated_insert_has_many
365
- c1, c2, c3 = @parent1.children.order(:position).to_a
366
- pv = ParentView.new(@parent1)
326
+ c1, c2, c3 = @model1.children.order(:position).to_a
327
+ pv = viewmodel_class.new(@model1)
367
328
 
368
329
  # insert before
369
330
  pv.append_associated(:children,
370
331
  { '_type' => 'Child', 'name' => 'new1' },
371
- before: ViewModel::Reference.new(ChildView, c2.id),
372
- deserialize_context: (context = ParentView.new_deserialize_context))
332
+ before: ViewModel::Reference.new(child_viewmodel_class, c2.id),
333
+ deserialize_context: (context = viewmodel_class.new_deserialize_context))
373
334
 
374
- n1 = Child.find_by_name('new1')
335
+ n1 = child_model_class.find_by_name('new1')
375
336
 
376
- expected_edit_checks = [ViewModel::Reference.new(ParentView, @parent1.id),
377
- ViewModel::Reference.new(ChildView, n1.id)]
337
+ expected_edit_checks = [ViewModel::Reference.new(viewmodel_class, @model1.id),
338
+ ViewModel::Reference.new(child_viewmodel_class, n1.id)]
378
339
 
379
340
  assert_contains_exactly(expected_edit_checks, context.valid_edit_refs)
380
341
 
381
342
  assert_equal([c1, n1, c2, c3],
382
- @parent1.children.order(:position))
343
+ @model1.children.order(:position))
383
344
 
384
345
  # insert after
385
346
  pv.append_associated(:children,
386
347
  { '_type' => 'Child', 'name' => 'new2' },
387
- after: ViewModel::Reference.new(ChildView, c2.id),
388
- deserialize_context: (context = ParentView.new_deserialize_context))
348
+ after: ViewModel::Reference.new(child_viewmodel_class, c2.id),
349
+ deserialize_context: (context = viewmodel_class.new_deserialize_context))
389
350
 
390
- n2 = Child.find_by_name('new2')
351
+ n2 = child_model_class.find_by_name('new2')
391
352
 
392
- expected_edit_checks = [ViewModel::Reference.new(ParentView, @parent1.id),
393
- ViewModel::Reference.new(ChildView, n2.id)]
353
+ expected_edit_checks = [ViewModel::Reference.new(viewmodel_class, @model1.id),
354
+ ViewModel::Reference.new(child_viewmodel_class, n2.id)]
394
355
 
395
356
  assert_contains_exactly(expected_edit_checks, context.valid_edit_refs)
396
357
 
397
358
  assert_equal([c1, n1, c2, n2, c3],
398
- @parent1.children.order(:position))
359
+ @model1.children.order(:position))
399
360
 
400
361
  # append
401
362
  pv.append_associated(:children,
402
363
  { '_type' => 'Child', 'name' => 'new3' },
403
- deserialize_context: (context = ParentView.new_deserialize_context))
364
+ deserialize_context: (context = viewmodel_class.new_deserialize_context))
404
365
 
405
- n3 = Child.find_by_name('new3')
366
+ n3 = child_model_class.find_by_name('new3')
406
367
 
407
- expected_edit_checks = [ViewModel::Reference.new(ParentView, @parent1.id),
408
- ViewModel::Reference.new(ChildView, n3.id)]
368
+ expected_edit_checks = [ViewModel::Reference.new(viewmodel_class, @model1.id),
369
+ ViewModel::Reference.new(child_viewmodel_class, n3.id)]
409
370
 
410
371
  assert_contains_exactly(expected_edit_checks, context.valid_edit_refs)
411
372
 
412
373
  assert_equal([c1, n1, c2, n2, c3, n3],
413
- @parent1.children.order(:position))
374
+ @model1.children.order(:position))
414
375
  end
415
376
 
416
377
  def test_edit_implicit_list_position
417
- c1, c2, c3 = @parent1.children.order(:position).to_a
378
+ c1, c2, c3 = @model1.children.order(:position).to_a
418
379
 
419
- alter_by_view!(ParentView, @parent1) do |view, refs|
380
+ alter_by_view!(viewmodel_class, @model1) do |view, refs|
420
381
  view['children'].reverse!
421
382
  view['children'].insert(1, { '_type' => 'Child', 'name' => 'new_c' })
422
383
  end
423
384
 
424
- assert_equal([c3, Child.find_by_name('new_c'), c2, c1],
425
- @parent1.children.order(:position))
385
+ assert_equal([c3, child_model_class.find_by_name('new_c'), c2, c1],
386
+ @model1.children.order(:position))
426
387
  end
427
388
 
428
389
  def test_edit_missing_child
429
390
  view = {
430
- "_type" => "Parent",
391
+ "_type" => "Model",
431
392
  "children" => [{
432
393
  "_type" => "Child",
433
394
  "id" => 9999
@@ -435,37 +396,37 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
435
396
  }
436
397
 
437
398
  ex = assert_raises(ViewModel::DeserializationError::NotFound) do
438
- ParentView.deserialize_from_view(view)
399
+ viewmodel_class.deserialize_from_view(view)
439
400
  end
440
401
 
441
- assert_equal(ex.nodes, [ViewModel::Reference.new(ChildView, 9999)])
402
+ assert_equal(ex.nodes, [ViewModel::Reference.new(child_viewmodel_class, 9999)])
442
403
  end
443
404
 
444
405
  def test_move_child_to_new
445
- old_children = @parent1.children.order(:position)
406
+ old_children = @model1.children.order(:position)
446
407
  moved_child = old_children[1]
447
408
 
448
- moved_child_ref = update_hash_for(ChildView, moved_child)
409
+ moved_child_ref = update_hash_for(child_viewmodel_class, moved_child)
449
410
 
450
- view = { '_type' => 'Parent',
411
+ view = { '_type' => 'Model',
451
412
  'name' => 'new_p',
452
413
  'children' => [moved_child_ref,
453
414
  { '_type' => 'Child', 'name' => 'new' }] }
454
415
 
455
416
  retained_children = old_children - [moved_child]
456
- release_view = { '_type' => 'Parent',
457
- 'id' => @parent1.id,
458
- 'children' => retained_children.map { |c| update_hash_for(ChildView, c) } }
417
+ release_view = { '_type' => 'Model',
418
+ 'id' => @model1.id,
419
+ 'children' => retained_children.map { |c| update_hash_for(child_viewmodel_class, c) } }
459
420
 
460
- pv = ParentView.deserialize_from_view([view, release_view])
421
+ pv = viewmodel_class.deserialize_from_view([view, release_view])
461
422
 
462
423
  new_parent = pv.first.model
463
424
  new_parent.reload
464
425
 
465
426
  # child should be removed from old parent
466
- @parent1.reload
427
+ @model1.reload
467
428
  assert_equal(retained_children,
468
- @parent1.children.order(:position))
429
+ @model1.children.order(:position))
469
430
 
470
431
  # child should be added to new parent
471
432
  new_children = new_parent.children.order(:position)
@@ -474,18 +435,18 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
474
435
  end
475
436
 
476
437
  def test_has_many_cannot_take_from_outside_tree
477
- old_children = @parent1.children.order(:position)
438
+ old_children = @model1.children.order(:position)
478
439
 
479
440
  assert_raises(ViewModel::DeserializationError::ParentNotFound) do
480
- alter_by_view!(ParentView, @parent2) do |p2, _refs|
481
- p2['children'] = old_children.map { |x| update_hash_for(ChildView, x) }
441
+ alter_by_view!(viewmodel_class, @model2) do |p2, _refs|
442
+ p2['children'] = old_children.map { |x| update_hash_for(child_viewmodel_class, x) }
482
443
  end
483
444
  end
484
445
  end
485
446
 
486
447
  def test_has_many_cannot_duplicate_unreleased_children
487
448
  assert_raises(ViewModel::DeserializationError::DuplicateNodes) do
488
- alter_by_view!(ParentView, [@parent1, @parent2]) do |(p1, p2), _refs|
449
+ alter_by_view!(viewmodel_class, [@model1, @model2]) do |(p1, p2), _refs|
489
450
  p2['children'] = p1['children'].deep_dup
490
451
  end
491
452
  end
@@ -493,7 +454,7 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
493
454
 
494
455
  def test_has_many_cannot_duplicate_implicitly_unreleased_children
495
456
  assert_raises(ViewModel::DeserializationError::ParentNotFound) do
496
- alter_by_view!(ParentView, [@parent1, @parent2]) do |(p1, p2), _refs|
457
+ alter_by_view!(viewmodel_class, [@model1, @model2]) do |(p1, p2), _refs|
497
458
  p2['children'] = p1['children']
498
459
  p1.delete('children')
499
460
  end
@@ -501,74 +462,73 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
501
462
  end
502
463
 
503
464
  def test_move_child_to_existing
504
- old_children = @parent1.children.order(:position)
465
+ old_children = @model1.children.order(:position)
505
466
  moved_child = old_children[1]
506
467
 
507
- view = ParentView.new(@parent2).to_hash
508
- view['children'] << ChildView.new(moved_child).to_hash
468
+ view = viewmodel_class.new(@model2).to_hash
469
+ view['children'] << child_viewmodel_class.new(moved_child).to_hash
509
470
 
510
471
  retained_children = old_children - [moved_child]
511
- release_view = { '_type' => 'Parent', 'id' => @parent1.id,
512
- 'children' => retained_children.map { |c| update_hash_for(ChildView, c) }}
472
+ release_view = { '_type' => 'Model', 'id' => @model1.id,
473
+ 'children' => retained_children.map { |c| update_hash_for(child_viewmodel_class, c) }}
513
474
 
514
- ParentView.deserialize_from_view([view, release_view])
475
+ viewmodel_class.deserialize_from_view([view, release_view])
515
476
 
516
- @parent1.reload
517
- @parent2.reload
477
+ @model1.reload
478
+ @model2.reload
518
479
 
519
480
  # child should be removed from old parent and positions updated
520
- assert_equal(retained_children, @parent1.children.order(:position))
481
+ assert_equal(retained_children, @model1.children.order(:position))
521
482
 
522
483
  # child should be added to new parent with valid position
523
- new_children = @parent2.children.order(:position)
484
+ new_children = @model2.children.order(:position)
524
485
  assert_equal(%w(p2c1 p2c2 p1c2), new_children.map(&:name))
525
486
  assert_equal(moved_child, new_children.last)
526
487
  end
527
488
 
528
489
  def test_has_many_append_child
529
- ParentView.new(@parent1).append_associated(:children, { "_type" => "Child", "name" => "new" })
490
+ viewmodel_class.new(@model1).append_associated(:children, { "_type" => "Child", "name" => "new" })
530
491
 
531
- @parent1.reload
492
+ @model1.reload
532
493
 
533
- assert_equal(4, @parent1.children.size)
534
- lc = @parent1.children.order(:position).last
494
+ assert_equal(4, @model1.children.size)
495
+ lc = @model1.children.order(:position).last
535
496
  assert_equal("new", lc.name)
536
497
  end
537
498
 
538
499
  def test_has_many_append_and_update_existing_association
539
- child = @parent1.children[1]
500
+ child = @model1.children[1]
540
501
 
541
- cv = ChildView.new(child).to_hash
502
+ cv = child_viewmodel_class.new(child).to_hash
542
503
  cv["name"] = "newname"
543
504
 
544
- ParentView.new(@parent1).append_associated(:children, cv)
505
+ viewmodel_class.new(@model1).append_associated(:children, cv)
545
506
 
546
- @parent1.reload
507
+ @model1.reload
547
508
 
548
509
  # Child should have been moved to the end (and edited)
549
- assert_equal(3, @parent1.children.size)
550
- c1, c2, c3 = @parent1.children.order(:position)
510
+ assert_equal(3, @model1.children.size)
511
+ c1, c2, c3 = @model1.children.order(:position)
551
512
  assert_equal("p1c1", c1.name)
552
513
  assert_equal("p1c3", c2.name)
553
514
  assert_equal(child, c3)
554
515
  assert_equal("newname", c3.name)
555
-
556
516
  end
557
517
 
558
518
  def test_has_many_move_existing_association
559
- p1c2 = @parent1.children[1]
519
+ p1c2 = @model1.children[1]
560
520
  assert_equal(2, p1c2.position)
561
521
 
562
- ParentView.new(@parent2).append_associated("children", { "_type" => "Child", "id" => p1c2.id })
522
+ viewmodel_class.new(@model2).append_associated("children", { "_type" => "Child", "id" => p1c2.id })
563
523
 
564
- @parent1.reload
565
- @parent2.reload
524
+ @model1.reload
525
+ @model2.reload
566
526
 
567
- p1c = @parent1.children.order(:position)
527
+ p1c = @model1.children.order(:position)
568
528
  assert_equal(2, p1c.size)
569
529
  assert_equal(["p1c1", "p1c3"], p1c.map(&:name))
570
530
 
571
- p2c = @parent2.children.order(:position)
531
+ p2c = @model2.children.order(:position)
572
532
  assert_equal(3, p2c.size)
573
533
  assert_equal(["p2c1", "p2c2", "p1c2"], p2c.map(&:name))
574
534
  assert_equal(p1c2, p2c[2])
@@ -576,44 +536,44 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
576
536
  end
577
537
 
578
538
  def test_has_many_remove_existing_association
579
- child = @parent1.children[1]
539
+ child = @model1.children[1]
580
540
 
581
- ParentView.new(@parent1).delete_associated(:children, child.id)
541
+ viewmodel_class.new(@model1).delete_associated(:children, child.id)
582
542
 
583
- @parent1.reload
543
+ @model1.reload
584
544
 
585
545
  # Child should have been removed
586
- assert_equal(2, @parent1.children.size)
587
- c1, c2 = @parent1.children.order(:position)
546
+ assert_equal(2, @model1.children.size)
547
+ c1, c2 = @model1.children.order(:position)
588
548
  assert_equal("p1c1", c1.name)
589
549
  assert_equal("p1c3", c2.name)
590
550
 
591
- assert_equal(0, Child.where(id: child.id).size)
551
+ assert_equal(0, child_model_class.where(id: child.id).size)
592
552
  end
593
553
 
594
554
  def test_move_and_edit_child_to_new
595
- child = @parent1.children[1]
555
+ child = @model1.children[1]
596
556
 
597
- child_view = ChildView.new(child).to_hash
557
+ child_view = child_viewmodel_class.new(child).to_hash
598
558
  child_view["name"] = "changed"
599
559
 
600
- view = { "_type" => "Parent",
560
+ view = { "_type" => "Model",
601
561
  "name" => "new_p",
602
562
  "children" => [child_view, { "_type" => "Child", "name" => "new" }]}
603
563
 
604
564
  # TODO this is as awkward here as it is in the application
605
- release_view = { "_type" => "Parent",
606
- "id" => @parent1.id,
607
- "children" => [{ "_type" => "Child", "id" => @parent1.children[0].id },
608
- { "_type" => "Child", "id" => @parent1.children[2].id }]}
565
+ release_view = { "_type" => "Model",
566
+ "id" => @model1.id,
567
+ "children" => [{ "_type" => "Child", "id" => @model1.children[0].id },
568
+ { "_type" => "Child", "id" => @model1.children[2].id }]}
609
569
 
610
- pv = ParentView.deserialize_from_view([view, release_view])
570
+ pv = viewmodel_class.deserialize_from_view([view, release_view])
611
571
  new_parent = pv.first.model
612
572
 
613
573
  # child should be removed from old parent and positions updated
614
- @parent1.reload
615
- assert_equal(2, @parent1.children.size, "database has 2 children")
616
- oc1, oc2 = @parent1.children.order(:position)
574
+ @model1.reload
575
+ assert_equal(2, @model1.children.size, "database has 2 children")
576
+ oc1, oc2 = @model1.children.order(:position)
617
577
  assert_equal("p1c1", oc1.name, "database c1 unchanged")
618
578
  assert_equal("p1c3", oc2.name, "database c2 unchanged")
619
579
 
@@ -626,32 +586,32 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
626
586
  end
627
587
 
628
588
  def test_move_and_edit_child_to_existing
629
- old_child = @parent1.children[1]
589
+ old_child = @model1.children[1]
630
590
 
631
- old_child_view = ChildView.new(old_child).to_hash
591
+ old_child_view = child_viewmodel_class.new(old_child).to_hash
632
592
  old_child_view["name"] = "changed"
633
- view = ParentView.new(@parent2).to_hash
593
+ view = viewmodel_class.new(@model2).to_hash
634
594
  view["children"] << old_child_view
635
595
 
636
- release_view = {"_type" => "Parent", "id" => @parent1.id,
637
- "children" => [{"_type" => "Child", "id" => @parent1.children[0].id},
638
- {"_type" => "Child", "id" => @parent1.children[2].id}]}
596
+ release_view = {"_type" => "Model", "id" => @model1.id,
597
+ "children" => [{"_type" => "Child", "id" => @model1.children[0].id},
598
+ {"_type" => "Child", "id" => @model1.children[2].id}]}
639
599
 
640
- ParentView.deserialize_from_view([view, release_view])
600
+ viewmodel_class.deserialize_from_view([view, release_view])
641
601
 
642
- @parent1.reload
643
- @parent2.reload
602
+ @model1.reload
603
+ @model2.reload
644
604
 
645
605
  # child should be removed from old parent and positions updated
646
- assert_equal(2, @parent1.children.size)
647
- oc1, oc2 = @parent1.children.order(:position)
606
+ assert_equal(2, @model1.children.size)
607
+ oc1, oc2 = @model1.children.order(:position)
648
608
 
649
609
  assert_equal("p1c1", oc1.name)
650
610
  assert_equal("p1c3", oc2.name)
651
611
 
652
612
  # child should be added to new parent with valid position
653
- assert_equal(3, @parent2.children.size)
654
- nc1, _, nc3 = @parent2.children.order(:position)
613
+ assert_equal(3, @model2.children.size)
614
+ nc1, _, nc3 = @model2.children.order(:position)
655
615
  assert_equal("p2c1", nc1.name)
656
616
 
657
617
  assert_equal("p2c1", nc1.name)
@@ -661,27 +621,27 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
661
621
  end
662
622
 
663
623
  def test_functional_update_append
664
- children_before = @parent1.children.order(:position).pluck(:id)
624
+ children_before = @model1.children.order(:position).pluck(:id)
665
625
  fupdate = build_fupdate do
666
626
  append([{ '_type' => 'Child' },
667
627
  { '_type' => 'Child' }])
668
628
  end
669
629
 
670
- append_view = { '_type' => 'Parent',
671
- 'id' => @parent1.id,
630
+ append_view = { '_type' => 'Model',
631
+ 'id' => @model1.id,
672
632
  'children' => fupdate }
673
633
 
674
- result = ParentView.deserialize_from_view(append_view)
675
- @parent1.reload
634
+ result = viewmodel_class.deserialize_from_view(append_view)
635
+ @model1.reload
676
636
 
677
637
  created_children = result.children[-2,2].map(&:id)
678
638
 
679
639
  assert_equal(children_before + created_children,
680
- @parent1.children.order(:position).pluck(:id))
640
+ @model1.children.order(:position).pluck(:id))
681
641
  end
682
642
 
683
643
  def test_functional_update_append_before_mid
684
- c1, c2, c3 = @parent1.children.order(:position)
644
+ c1, c2, c3 = @model1.children.order(:position)
685
645
 
686
646
  fupdate = build_fupdate do
687
647
  append([{ '_type' => 'Child', 'name' => 'new c1' },
@@ -689,36 +649,36 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
689
649
  before: { '_type' => 'Child', 'id' => c2.id })
690
650
  end
691
651
 
692
- append_view = { '_type' => 'Parent',
693
- 'id' => @parent1.id,
652
+ append_view = { '_type' => 'Model',
653
+ 'id' => @model1.id,
694
654
  'children' => fupdate }
695
- ParentView.deserialize_from_view(append_view)
696
- @parent1.reload
655
+ viewmodel_class.deserialize_from_view(append_view)
656
+ @model1.reload
697
657
 
698
658
  assert_equal([c1.name, 'new c1', 'new c2', c2.name, c3.name],
699
- @parent1.children.order(:position).pluck(:name))
659
+ @model1.children.order(:position).pluck(:name))
700
660
  end
701
661
 
702
662
  def test_functional_update_append_before_reorder
703
- c1, c2, c3 = @parent1.children.order(:position)
663
+ c1, c2, c3 = @model1.children.order(:position)
704
664
 
705
665
  fupdate = build_fupdate do
706
666
  append([{ '_type' => 'Child', 'id' => c3.id }],
707
667
  before: { '_type' => 'Child', 'id' => c2.id })
708
668
  end
709
669
 
710
- append_view = { '_type' => 'Parent',
711
- 'id' => @parent1.id,
670
+ append_view = { '_type' => 'Model',
671
+ 'id' => @model1.id,
712
672
  'children' => fupdate }
713
- ParentView.deserialize_from_view(append_view)
714
- @parent1.reload
673
+ viewmodel_class.deserialize_from_view(append_view)
674
+ @model1.reload
715
675
 
716
676
  assert_equal([c1.name, c3.name, c2.name],
717
- @parent1.children.order(:position).pluck(:name))
677
+ @model1.children.order(:position).pluck(:name))
718
678
  end
719
679
 
720
680
  def test_functional_update_append_before_beginning
721
- c1, c2, c3 = @parent1.children.order(:position)
681
+ c1, c2, c3 = @model1.children.order(:position)
722
682
 
723
683
  fupdate = build_fupdate do
724
684
  append([{ '_type' => 'Child', 'name' => 'new c1' },
@@ -726,18 +686,18 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
726
686
  before: { '_type' => 'Child', 'id' => c1.id })
727
687
  end
728
688
 
729
- append_view = { '_type' => 'Parent',
730
- 'id' => @parent1.id,
689
+ append_view = { '_type' => 'Model',
690
+ 'id' => @model1.id,
731
691
  'children' => fupdate }
732
- ParentView.deserialize_from_view(append_view)
733
- @parent1.reload
692
+ viewmodel_class.deserialize_from_view(append_view)
693
+ @model1.reload
734
694
 
735
695
  assert_equal(['new c1', 'new c2', c1.name, c2.name, c3.name],
736
- @parent1.children.order(:position).pluck(:name))
696
+ @model1.children.order(:position).pluck(:name))
737
697
  end
738
698
 
739
699
  def test_functional_update_append_before_corpse
740
- _, c2, _ = @parent1.children.order(:position)
700
+ _, c2, _ = @model1.children.order(:position)
741
701
  c2.destroy
742
702
 
743
703
  fupdate = build_fupdate do
@@ -746,16 +706,16 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
746
706
  before: { '_type' => 'Child', 'id' => c2.id })
747
707
  end
748
708
 
749
- append_view = { '_type' => 'Parent',
750
- 'id' => @parent1.id,
709
+ append_view = { '_type' => 'Model',
710
+ 'id' => @model1.id,
751
711
  'children' => fupdate }
752
712
  assert_raises(ViewModel::DeserializationError::AssociatedNotFound) do
753
- ParentView.deserialize_from_view(append_view)
713
+ viewmodel_class.deserialize_from_view(append_view)
754
714
  end
755
715
  end
756
716
 
757
717
  def test_functional_update_append_after_mid
758
- c1, c2, c3 = @parent1.children.order(:position)
718
+ c1, c2, c3 = @model1.children.order(:position)
759
719
 
760
720
  fupdate = build_fupdate do
761
721
  append([{ '_type' => 'Child', 'name' => 'new c1' },
@@ -763,18 +723,18 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
763
723
  after: { '_type' => 'Child', 'id' => c2.id })
764
724
  end
765
725
 
766
- append_view = { '_type' => 'Parent',
767
- 'id' => @parent1.id,
726
+ append_view = { '_type' => 'Model',
727
+ 'id' => @model1.id,
768
728
  'children' => fupdate }
769
- ParentView.deserialize_from_view(append_view)
770
- @parent1.reload
729
+ viewmodel_class.deserialize_from_view(append_view)
730
+ @model1.reload
771
731
 
772
732
  assert_equal([c1.name, c2.name, 'new c1', 'new c2', c3.name],
773
- @parent1.children.order(:position).pluck(:name))
733
+ @model1.children.order(:position).pluck(:name))
774
734
  end
775
735
 
776
736
  def test_functional_update_append_after_end
777
- c1, c2, c3 = @parent1.children.order(:position)
737
+ c1, c2, c3 = @model1.children.order(:position)
778
738
 
779
739
  fupdate = build_fupdate do
780
740
  append([{ '_type' => 'Child', 'name' => 'new c1' },
@@ -782,18 +742,18 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
782
742
  after: { '_type' => 'Child', 'id' => c3.id, })
783
743
  end
784
744
 
785
- append_view = { '_type' => 'Parent',
786
- 'id' => @parent1.id,
745
+ append_view = { '_type' => 'Model',
746
+ 'id' => @model1.id,
787
747
  'children' => fupdate }
788
- ParentView.deserialize_from_view(append_view)
789
- @parent1.reload
748
+ viewmodel_class.deserialize_from_view(append_view)
749
+ @model1.reload
790
750
 
791
751
  assert_equal([c1.name, c2.name, c3.name, 'new c1', 'new c2'],
792
- @parent1.children.order(:position).pluck(:name))
752
+ @model1.children.order(:position).pluck(:name))
793
753
  end
794
754
 
795
755
  def test_functional_update_append_after_corpse
796
- _, c2, _ = @parent1.children.order(:position)
756
+ _, c2, _ = @model1.children.order(:position)
797
757
  c2.destroy
798
758
 
799
759
  fupdate = build_fupdate do
@@ -803,32 +763,32 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
803
763
  )
804
764
  end
805
765
 
806
- append_view = { '_type' => 'Parent',
807
- 'id' => @parent1.id,
766
+ append_view = { '_type' => 'Model',
767
+ 'id' => @model1.id,
808
768
  'children' => fupdate }
809
769
  assert_raises(ViewModel::DeserializationError::AssociatedNotFound) do
810
- ParentView.deserialize_from_view(append_view)
770
+ viewmodel_class.deserialize_from_view(append_view)
811
771
  end
812
772
  end
813
773
 
814
774
  def test_functional_update_remove_success
815
- c1_id, c2_id, c3_id = @parent1.children.pluck(:id)
775
+ c1_id, c2_id, c3_id = @model1.children.pluck(:id)
816
776
 
817
777
  fupdate = build_fupdate do
818
778
  remove([{ '_type' => 'Child', 'id' => c2_id }])
819
779
  end
820
780
 
821
- remove_view = { '_type' => 'Parent',
822
- 'id' => @parent1.id,
781
+ remove_view = { '_type' => 'Model',
782
+ 'id' => @model1.id,
823
783
  'children' => fupdate }
824
- ParentView.deserialize_from_view(remove_view)
825
- @parent1.reload
784
+ viewmodel_class.deserialize_from_view(remove_view)
785
+ @model1.reload
826
786
 
827
- assert_equal([c1_id, c3_id], @parent1.children.pluck(:id))
787
+ assert_equal([c1_id, c3_id], @model1.children.pluck(:id))
828
788
  end
829
789
 
830
790
  def test_functional_update_remove_failure
831
- c_id = @parent1.children.pluck(:id).first
791
+ c_id = @model1.children.pluck(:id).first
832
792
 
833
793
  fupdate = build_fupdate do
834
794
  remove([{ '_type' => 'Child',
@@ -836,19 +796,19 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
836
796
  'name' => 'remove and update disallowed' }])
837
797
  end
838
798
 
839
- remove_view = { '_type' => 'Parent',
840
- 'id' => @parent1.id,
799
+ remove_view = { '_type' => 'Model',
800
+ 'id' => @model1.id,
841
801
  'children' => fupdate }
842
802
 
843
803
  ex = assert_raises(ViewModel::DeserializationError::InvalidSyntax) do
844
- ParentView.deserialize_from_view(remove_view)
804
+ viewmodel_class.deserialize_from_view(remove_view)
845
805
  end
846
806
 
847
807
  assert_match(/Removed entities must have only _type and id fields/, ex.message)
848
808
  end
849
809
 
850
810
  def test_functional_update_update_success
851
- c1_id, c2_id, c3_id = @parent1.children.pluck(:id)
811
+ c1_id, c2_id, c3_id = @model1.children.pluck(:id)
852
812
 
853
813
  fupdate = build_fupdate do
854
814
  update([{ '_type' => 'Child',
@@ -856,34 +816,36 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
856
816
  'name' => 'Functionally Updated Child' }])
857
817
  end
858
818
 
859
- update_view = { '_type' => 'Parent',
860
- 'id' => @parent1.id,
819
+ update_view = { '_type' => 'Model',
820
+ 'id' => @model1.id,
861
821
  'children' => fupdate }
862
- ParentView.deserialize_from_view(update_view)
863
- @parent1.reload
822
+ viewmodel_class.deserialize_from_view(update_view)
823
+ @model1.reload
864
824
 
865
- assert_equal([c1_id, c2_id, c3_id], @parent1.children.pluck(:id))
866
- assert_equal('Functionally Updated Child', Child.find(c2_id).name)
825
+ assert_equal([c1_id, c2_id, c3_id], @model1.children.pluck(:id))
826
+ assert_equal('Functionally Updated Child', child_model_class.find(c2_id).name)
867
827
  end
868
828
 
869
829
  def test_functional_update_update_failure
870
- cnew = Child.create(parent: Parent.create).id
830
+ cnew = child_model_class.create(model: model_class.create, position: 0).id
871
831
 
872
832
  fupdate = build_fupdate do
873
833
  update([{ '_type' => 'Child', 'id' => cnew }])
874
834
  end
875
835
 
876
- update_view = { '_type' => 'Parent',
877
- 'id' => @parent1.id,
878
- 'children' => fupdate }
836
+ update_view = {
837
+ '_type' => 'Model',
838
+ 'id' => @model1.id,
839
+ 'children' => fupdate,
840
+ }
879
841
 
880
842
  assert_raises(ViewModel::DeserializationError::AssociatedNotFound) do
881
- ParentView.deserialize_from_view(update_view)
843
+ viewmodel_class.deserialize_from_view(update_view)
882
844
  end
883
845
  end
884
846
 
885
847
  def test_functional_update_duplicate_refs
886
- child_id = @parent1.children.pluck(:id).first
848
+ child_id = @model1.children.pluck(:id).first
887
849
 
888
850
  fupdate = build_fupdate do
889
851
  # remove and append the same child
@@ -891,67 +853,329 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
891
853
  append([{ '_type' => 'Child', 'id' => child_id }])
892
854
  end
893
855
 
894
- update_view = { '_type' => 'Parent',
895
- 'id' => @parent1.id,
856
+ update_view = { '_type' => 'Model',
857
+ 'id' => @model1.id,
896
858
  'children' => fupdate }
897
859
 
898
860
  ex = assert_raises(ViewModel::DeserializationError::InvalidStructure) do
899
- ParentView.deserialize_from_view(update_view)
861
+ viewmodel_class.deserialize_from_view(update_view)
900
862
  end
901
863
 
902
864
  assert_match(/Duplicate functional update targets\b.*\bChild\b/, ex.message)
903
865
  end
904
866
 
867
+ describe 'owned reference children' do
868
+ def child_attributes
869
+ super.merge(viewmodel: ->(v) { root! })
870
+ end
871
+
872
+ def new_model
873
+ new_children = (1 .. 2).map { |n| child_model_class.new(name: "c#{n}", position: n) }
874
+ model_class.new(name: 'm1', children: new_children)
875
+ end
905
876
 
906
- class RenamedTest < ActiveSupport::TestCase
907
- include ARVMTestUtilities
877
+ it 'makes a reference association' do
878
+ assert(subject_association.referenced?)
879
+ end
908
880
 
909
- def before_all
910
- super
881
+ it 'makes an owned association' do
882
+ assert(subject_association.owned?)
883
+ end
884
+
885
+ it 'loads and batches' do
886
+ create_model!
911
887
 
912
- build_viewmodel(:Parent) do
913
- define_schema do |t|
914
- t.string :name
888
+ log_queries do
889
+ serialize(ModelView.load)
890
+ end
891
+
892
+ assert_equal(['Model Load', 'Child Load'], logged_load_queries)
893
+ end
894
+
895
+ it 'serializes' do
896
+ model = create_model!
897
+ view, refs = serialize_with_references(ModelView.new(model))
898
+
899
+ children = model.children.sort_by(&:position)
900
+ assert_equal(children.size, view['children'].size)
901
+
902
+ child_refs = view['children'].map { |c| c['_ref'] }
903
+ child_views = child_refs.map { |r| refs[r] }
904
+
905
+ children.zip(child_views).each do |child, child_view|
906
+ assert_equal(child_view,
907
+ { '_type' => 'Child',
908
+ '_version' => 1,
909
+ 'id' => child.id,
910
+ 'name' => child.name })
911
+ end
912
+
913
+ assert_equal({ '_type' => 'Model',
914
+ '_version' => 1,
915
+ 'id' => model.id,
916
+ 'name' => model.name,
917
+ 'children' => view['children'] },
918
+ view)
919
+ end
920
+
921
+ it 'creates from view' do
922
+ view = {
923
+ '_type' => 'Model',
924
+ 'name' => 'p',
925
+ 'children' => [{ '_ref' => 'r1' }],
926
+ }
927
+
928
+ refs = {
929
+ 'r1' => { '_type' => 'Child', 'name' => 'newkid' },
930
+ }
931
+
932
+ pv = ModelView.deserialize_from_view(view, references: refs)
933
+ p = pv.model
934
+
935
+ assert(!p.changed?)
936
+ assert(!p.new_record?)
937
+
938
+ assert_equal('p', p.name)
939
+
940
+ assert(p.children.present?)
941
+ assert_equal('newkid', p.children[0].name)
942
+ end
943
+
944
+ it 'updates with adding a child' do
945
+ model = create_model!
946
+
947
+ alter_by_view!(ModelView, model) do |view, refs|
948
+ view['children'] << { '_ref' => 'ref1' }
949
+ refs['ref1'] = {
950
+ '_type' => 'Child',
951
+ 'name' => 'newchildname',
952
+ }
953
+ end
954
+
955
+ assert_equal(3, model.children.size)
956
+ assert_equal('newchildname', model.children.last.name)
957
+ end
958
+
959
+ it 'updates with adding a child functionally' do
960
+ model = create_model!
961
+
962
+ alter_by_view!(ModelView, model) do |view, refs|
963
+ refs.clear
964
+
965
+ view['children'] = build_fupdate do
966
+ append([{ '_ref' => 'ref1' }])
915
967
  end
916
968
 
917
- define_model do
918
- has_many :children, dependent: :destroy, inverse_of: :parent
969
+ refs['ref1'] = {
970
+ '_type' => 'Child',
971
+ 'name' => 'newchildname',
972
+ }
973
+ end
974
+
975
+ assert_equal(3, model.children.size)
976
+ assert_equal('newchildname', model.children.last.name)
977
+ end
978
+
979
+ it 'updates with removing a child' do
980
+ model = create_model!
981
+ old_child = model.children.last
982
+
983
+ alter_by_view!(ModelView, model) do |view, refs|
984
+ removed = view['children'].pop['_ref']
985
+ refs.delete(removed)
986
+ end
987
+
988
+ assert_equal(1, model.children.size)
989
+ assert_equal('c1', model.children.first.name)
990
+ assert_empty(child_model_class.where(id: old_child.id))
991
+ end
992
+
993
+ it 'updates with removing a child functionally' do
994
+ model = create_model!
995
+ old_child = model.children.last
996
+
997
+ alter_by_view!(ModelView, model) do |view, refs|
998
+ removed_ref = view['children'].pop['_ref']
999
+ removed_id = refs[removed_ref]['id']
1000
+ refs.clear
1001
+
1002
+ view['children'] = build_fupdate do
1003
+ remove([{ '_type' => 'Child', 'id' => removed_id }])
1004
+ end
1005
+ end
1006
+
1007
+ assert_equal(1, model.children.size)
1008
+ assert_equal('c1', model.children.first.name)
1009
+ assert_empty(child_model_class.where(id: old_child.id))
1010
+ end
1011
+
1012
+ it 'updates with replacing a child' do
1013
+ model = create_model!
1014
+ old_child = model.children.last
1015
+
1016
+ alter_by_view!(ModelView, model) do |view, refs|
1017
+ exchange_ref = view['children'].last['_ref']
1018
+ refs[exchange_ref] = {
1019
+ '_type' => 'Child',
1020
+ 'name' => 'newchildname',
1021
+ }
1022
+ end
1023
+
1024
+ children = model.children.sort_by(&:position)
1025
+ assert_equal(2, children.size)
1026
+ refute_equal(old_child.id, children.last.id)
1027
+ assert_equal('newchildname', children.last.name)
1028
+ assert_empty(child_model_class.where(id: old_child.id))
1029
+ end
1030
+
1031
+ it 'updates with replacing a child functionally' do
1032
+ model = create_model!
1033
+ old_child = model.children.first
1034
+
1035
+ alter_by_view!(ModelView, model) do |view, refs|
1036
+ removed_ref = view['children'].shift['_ref']
1037
+ removed_id = refs[removed_ref]['id']
1038
+ refs.clear
1039
+
1040
+ view['children'] = build_fupdate do
1041
+ append([{ '_ref' => 'repl_ref' }],
1042
+ after: { '_type' => 'Child', 'id' => removed_id })
1043
+ remove([{ '_type' => 'Child', 'id' => removed_id }])
919
1044
  end
920
1045
 
921
- define_viewmodel do
922
- attributes :name
923
- association :children, as: :something_else
1046
+ refs['repl_ref'] = {
1047
+ '_type' => 'Child',
1048
+ 'name' => 'newchildname',
1049
+ }
1050
+ end
1051
+
1052
+ children = model.children.sort_by(&:position)
1053
+ assert_equal(2, children.size)
1054
+ refute_equal(old_child.id, children.first.id)
1055
+ assert_equal('newchildname', children.first.name)
1056
+ assert_empty(child_model_class.where(id: old_child.id))
1057
+ end
1058
+
1059
+ it 'updates with editing a child' do
1060
+ model = create_model!
1061
+
1062
+ alter_by_view!(ModelView, model) do |view, refs|
1063
+ c1ref = view['children'].first['_ref']
1064
+ refs[c1ref]['name'] = 'renamed'
1065
+ end
1066
+
1067
+ assert_equal(2, model.children.size)
1068
+ assert_equal('renamed', model.children.first.name)
1069
+ end
1070
+
1071
+ it 'updates with editing a child functionally' do
1072
+ model = create_model!
1073
+
1074
+ alter_by_view!(ModelView, model) do |view, refs|
1075
+ edit_ref = view['children'].shift['_ref']
1076
+ refs.slice!(edit_ref)
1077
+
1078
+ view['children'] = build_fupdate do
1079
+ update([{ '_ref' => edit_ref }])
924
1080
  end
1081
+
1082
+ refs[edit_ref]['name'] = 'renamed'
1083
+ end
1084
+
1085
+ assert_equal(2, model.children.size)
1086
+ assert_equal('renamed', model.children.first.name)
1087
+ end
1088
+
1089
+ describe 'with association manipulation' do
1090
+ it 'appends a child' do
1091
+ view = create_viewmodel!
1092
+
1093
+ view.append_associated(:children, { '_type' => 'Child', 'name' => 'newchildname' })
1094
+
1095
+ view.model.reload
1096
+ assert_equal(3, view.children.size)
1097
+ assert_equal('newchildname', view.children.last.name)
1098
+ end
1099
+
1100
+ it 'inserts a child' do
1101
+ view = create_viewmodel!
1102
+ c1 = view.children.first
1103
+
1104
+ view.append_associated(:children,
1105
+ { '_type' => 'Child', 'name' => 'newchildname' },
1106
+ after: c1.to_reference)
1107
+ view.model.reload
1108
+
1109
+ assert_equal(3, view.children.size)
1110
+ assert_equal('newchildname', view.children[1].name)
1111
+ end
1112
+
1113
+ it 'moves a child' do
1114
+ view = create_viewmodel!
1115
+ c1, c2 = view.children
1116
+
1117
+ view.append_associated(:children,
1118
+ { '_type' => 'Child', 'id' => c2.id },
1119
+ before: c1.to_reference)
1120
+ view.model.reload
1121
+
1122
+ assert_equal(2, view.children.size)
1123
+ assert_equal(['c2', 'c1'], view.children.map(&:name))
1124
+ end
1125
+
1126
+ it 'replaces children' do
1127
+ view = create_viewmodel!
1128
+ view.replace_associated(:children,
1129
+ [{ '_type' => 'Child', 'name' => 'newchildname' }])
1130
+
1131
+ view.model.reload
1132
+
1133
+ assert_equal(1, view.children.size)
1134
+ assert_equal('newchildname', view.children[0].name)
925
1135
  end
926
1136
 
927
- ViewModel::ActiveRecord::HasManyTest.build_child(self)
1137
+ it 'deletes a child' do
1138
+ view = create_viewmodel!
1139
+ view.delete_associated(:children, view.children.first.id)
1140
+
1141
+ view.model.reload
1142
+
1143
+ assert_equal(1, view.children.size)
1144
+ assert_equal('c2', view.children[0].name)
1145
+ end
1146
+ end
1147
+ end
1148
+
1149
+ describe 'renaming associations' do
1150
+ def subject_association_features
1151
+ { as: :something_else }
928
1152
  end
929
1153
 
930
1154
  def setup
931
1155
  super
932
1156
 
933
- @parent = Parent.create(name: 'p1', children: [Child.new(name: 'c1')])
1157
+ @model = model_class.create(name: 'p1', children: [child_model_class.new(name: 'c1', position: 0)])
934
1158
 
935
1159
  enable_logging!
936
1160
  end
937
1161
 
938
1162
  def test_dependencies
939
- root_updates, _ref_updates = ViewModel::ActiveRecord::UpdateData.parse_hashes([{ '_type' => 'Parent', 'something_else' => [] }])
1163
+ root_updates, _ref_updates = ViewModel::ActiveRecord::UpdateData.parse_hashes([{ '_type' => 'Model', 'something_else' => [] }])
940
1164
  assert_equal(DeepPreloader::Spec.new('children' => DeepPreloader::Spec.new), root_updates.first.preload_dependencies)
941
- assert_equal({ 'something_else' => {} }, root_updates.first.updated_associations)
942
1165
  end
943
1166
 
944
1167
  def test_renamed_roundtrip
945
- alter_by_view!(ParentView, @parent) do |view, _refs|
946
- assert_equal([{ 'id' => @parent.children.first.id,
1168
+ alter_by_view!(viewmodel_class, @model) do |view, _refs|
1169
+ assert_equal([{ 'id' => @model.children.first.id,
947
1170
  '_type' => 'Child',
948
1171
  '_version' => 1,
949
1172
  'name' => 'c1' }],
950
1173
  view['something_else'])
1174
+
951
1175
  view['something_else'][0]['name'] = 'new c1 name'
952
1176
  end
953
1177
 
954
- assert_equal('new c1 name', @parent.children.first.name)
1178
+ assert_equal('new c1 name', @model.children.first.name)
955
1179
  end
956
1180
  end
957
1181
  end