iknow_view_models 2.10.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +119 -0
  3. data/.travis.yml +31 -0
  4. data/Appraisals +6 -16
  5. data/gemfiles/{rails_7_0.gemfile → rails_6_0_beta.gemfile} +2 -2
  6. data/iknow_view_models.gemspec +3 -5
  7. data/lib/iknow_view_models/version.rb +1 -1
  8. data/lib/view_model/active_record/association_data.rb +206 -92
  9. data/lib/view_model/active_record/association_manipulation.rb +22 -12
  10. data/lib/view_model/active_record/cache/cacheable_view.rb +3 -13
  11. data/lib/view_model/active_record/cache.rb +2 -2
  12. data/lib/view_model/active_record/cloner.rb +11 -11
  13. data/lib/view_model/active_record/controller.rb +0 -2
  14. data/lib/view_model/active_record/update_context.rb +21 -3
  15. data/lib/view_model/active_record/update_data.rb +43 -45
  16. data/lib/view_model/active_record/update_operation.rb +265 -153
  17. data/lib/view_model/active_record/visitor.rb +9 -6
  18. data/lib/view_model/active_record.rb +94 -74
  19. data/lib/view_model/after_transaction_runner.rb +3 -18
  20. data/lib/view_model/callbacks.rb +2 -2
  21. data/lib/view_model/changes.rb +24 -16
  22. data/lib/view_model/config.rb +6 -2
  23. data/lib/view_model/deserialization_error.rb +31 -0
  24. data/lib/view_model/deserialize_context.rb +2 -6
  25. data/lib/view_model/error_view.rb +6 -5
  26. data/lib/view_model/record/attribute_data.rb +11 -6
  27. data/lib/view_model/record.rb +44 -24
  28. data/lib/view_model/serialize_context.rb +2 -63
  29. data/lib/view_model/test_helpers/arvm_builder.rb +2 -4
  30. data/lib/view_model/traversal_context.rb +2 -2
  31. data/lib/view_model.rb +21 -13
  32. data/shell.nix +1 -1
  33. data/test/helpers/arvm_test_models.rb +4 -12
  34. data/test/helpers/arvm_test_utilities.rb +6 -0
  35. data/test/helpers/controller_test_helpers.rb +6 -6
  36. data/test/helpers/viewmodel_spec_helpers.rb +63 -52
  37. data/test/unit/view_model/access_control_test.rb +88 -37
  38. data/test/unit/view_model/active_record/belongs_to_test.rb +110 -178
  39. data/test/unit/view_model/active_record/cache_test.rb +11 -5
  40. data/test/unit/view_model/active_record/cloner_test.rb +1 -1
  41. data/test/unit/view_model/active_record/controller_test.rb +12 -20
  42. data/test/unit/view_model/active_record/has_many_test.rb +540 -316
  43. data/test/unit/view_model/active_record/has_many_through_poly_test.rb +12 -15
  44. data/test/unit/view_model/active_record/has_many_through_test.rb +15 -58
  45. data/test/unit/view_model/active_record/has_one_test.rb +288 -135
  46. data/test/unit/view_model/active_record/poly_test.rb +0 -1
  47. data/test/unit/view_model/active_record/shared_test.rb +21 -39
  48. data/test/unit/view_model/active_record/version_test.rb +3 -2
  49. data/test/unit/view_model/active_record_test.rb +5 -63
  50. data/test/unit/view_model/callbacks_test.rb +1 -0
  51. data/test/unit/view_model/record_test.rb +0 -32
  52. data/test/unit/view_model/traversal_context_test.rb +13 -12
  53. metadata +15 -25
  54. data/.github/workflows/gem-push.yml +0 -31
  55. data/.github/workflows/test.yml +0 -65
  56. data/gemfiles/rails_6_0.gemfile +0 -9
  57. data/gemfiles/rails_6_1.gemfile +0 -9
  58. 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