iknow_view_models 2.10.1 → 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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +119 -0
- data/.travis.yml +31 -0
- data/Appraisals +6 -16
- data/gemfiles/{rails_7_0.gemfile → rails_6_0_beta.gemfile} +2 -2
- data/iknow_view_models.gemspec +3 -5
- data/lib/iknow_view_models/version.rb +1 -1
- data/lib/view_model/active_record/association_data.rb +206 -92
- data/lib/view_model/active_record/association_manipulation.rb +22 -12
- data/lib/view_model/active_record/cache/cacheable_view.rb +3 -13
- data/lib/view_model/active_record/cache.rb +2 -2
- data/lib/view_model/active_record/cloner.rb +11 -11
- data/lib/view_model/active_record/controller.rb +0 -2
- data/lib/view_model/active_record/update_context.rb +21 -3
- data/lib/view_model/active_record/update_data.rb +43 -45
- data/lib/view_model/active_record/update_operation.rb +265 -153
- data/lib/view_model/active_record/visitor.rb +9 -6
- data/lib/view_model/active_record.rb +94 -74
- data/lib/view_model/after_transaction_runner.rb +3 -18
- data/lib/view_model/callbacks.rb +2 -2
- data/lib/view_model/changes.rb +24 -16
- data/lib/view_model/config.rb +6 -2
- data/lib/view_model/deserialization_error.rb +31 -0
- data/lib/view_model/deserialize_context.rb +2 -6
- data/lib/view_model/error_view.rb +6 -5
- data/lib/view_model/record/attribute_data.rb +11 -6
- data/lib/view_model/record.rb +44 -24
- data/lib/view_model/serialize_context.rb +2 -63
- data/lib/view_model/test_helpers/arvm_builder.rb +2 -4
- data/lib/view_model/traversal_context.rb +2 -2
- data/lib/view_model.rb +21 -13
- data/shell.nix +1 -1
- data/test/helpers/arvm_test_models.rb +4 -12
- data/test/helpers/arvm_test_utilities.rb +6 -0
- data/test/helpers/controller_test_helpers.rb +6 -6
- data/test/helpers/viewmodel_spec_helpers.rb +63 -52
- data/test/unit/view_model/access_control_test.rb +88 -37
- data/test/unit/view_model/active_record/belongs_to_test.rb +110 -178
- data/test/unit/view_model/active_record/cache_test.rb +11 -5
- data/test/unit/view_model/active_record/cloner_test.rb +1 -1
- data/test/unit/view_model/active_record/controller_test.rb +12 -20
- data/test/unit/view_model/active_record/has_many_test.rb +540 -316
- data/test/unit/view_model/active_record/has_many_through_poly_test.rb +12 -15
- data/test/unit/view_model/active_record/has_many_through_test.rb +15 -58
- data/test/unit/view_model/active_record/has_one_test.rb +288 -135
- data/test/unit/view_model/active_record/poly_test.rb +0 -1
- data/test/unit/view_model/active_record/shared_test.rb +21 -39
- data/test/unit/view_model/active_record/version_test.rb +3 -2
- data/test/unit/view_model/active_record_test.rb +5 -63
- data/test/unit/view_model/callbacks_test.rb +1 -0
- data/test/unit/view_model/record_test.rb +0 -32
- data/test/unit/view_model/traversal_context_test.rb +13 -12
- metadata +15 -25
- data/.github/workflows/gem-push.yml +0 -31
- data/.github/workflows/test.yml +0 -65
- data/gemfiles/rails_6_0.gemfile +0 -9
- data/gemfiles/rails_6_1.gemfile +0 -9
- 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
|
-
|
12
|
-
|
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
|
-
@
|
58
|
-
children: [
|
59
|
-
|
60
|
-
|
61
|
-
@
|
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
|
-
@
|
64
|
-
children: [
|
65
|
-
|
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
|
-
@
|
28
|
+
@model2.save!
|
68
29
|
|
69
30
|
enable_logging!
|
70
31
|
end
|
71
32
|
|
72
33
|
def test_load_associated
|
73
|
-
parentview =
|
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(
|
43
|
+
view, _refs = serialize_with_references(viewmodel_class.new(@model1))
|
83
44
|
|
84
45
|
|
85
|
-
assert_equal({ "_type" => "
|
46
|
+
assert_equal({ "_type" => "Model",
|
86
47
|
"_version" => 1,
|
87
|
-
"id" => @
|
88
|
-
"name" => @
|
89
|
-
"children" => @
|
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(
|
59
|
+
serialize(viewmodel_class.load)
|
99
60
|
end
|
100
|
-
assert_equal(['
|
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" => "
|
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 =
|
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 =
|
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
|
-
|
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
|
-
|
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' => '
|
144
|
-
pv =
|
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' => '
|
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 =
|
155
|
-
pv =
|
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" => "
|
127
|
+
"_type" => "Model",
|
167
128
|
"children" => nil
|
168
129
|
}
|
169
130
|
ex = assert_raises(ViewModel::DeserializationError::InvalidSyntax) do
|
170
|
-
|
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" => "
|
139
|
+
"_type" => "Model",
|
179
140
|
"children" => { '_type' => 'Child', 'name' => 'c1' }
|
180
141
|
}
|
181
142
|
ex = assert_raises(ViewModel::DeserializationError::InvalidSyntax) do
|
182
|
-
|
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 = @
|
150
|
+
old_children = @model1.children
|
190
151
|
|
191
|
-
alter_by_view!(
|
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'], @
|
196
|
-
assert_equal([],
|
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 = @
|
161
|
+
old_children = @model1.children
|
201
162
|
|
202
|
-
pv =
|
203
|
-
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(
|
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
|
-
@
|
220
|
-
assert_equal(['new_child'], @
|
221
|
-
assert_equal([],
|
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 = @
|
186
|
+
old_children = @model1.children
|
226
187
|
|
227
|
-
pv =
|
228
|
-
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(
|
241
|
-
ViewModel::Reference.new(
|
242
|
-
ViewModel::Reference.new(
|
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
|
-
@
|
251
|
-
assert_equal(['renamed p1c1', 'p1c2', 'new_child'], @
|
252
|
-
assert_equal([],
|
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 = @
|
257
|
-
_, context = alter_by_view!(
|
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(
|
262
|
-
old_children.map { |x| ViewModel::Reference.new(
|
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([], @
|
268
|
-
assert(
|
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 = @
|
233
|
+
c1, c2, c3 = @model1.children.order(:position).to_a
|
273
234
|
|
274
|
-
pv =
|
275
|
-
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(
|
281
|
-
ViewModel::Reference.new(
|
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
|
-
@
|
287
|
-
assert_equal([c2, c3], @
|
288
|
-
assert(
|
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 = @
|
253
|
+
c1, c2, c3 = @model1.children.order(:position).to_a
|
293
254
|
|
294
|
-
pv, context = alter_by_view!(
|
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(
|
302
|
-
ViewModel::Reference.new(
|
303
|
-
ViewModel::Reference.new(
|
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,
|
307
|
-
@
|
308
|
-
assert(
|
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 = @
|
313
|
-
pv =
|
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(
|
319
|
-
deserialize_context: (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
|
-
@
|
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(
|
331
|
-
deserialize_context: (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
|
-
@
|
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 =
|
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
|
-
@
|
307
|
+
@model1.children.order(:position))
|
347
308
|
|
348
309
|
# move from another parent
|
349
|
-
p2c1 = @
|
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 =
|
314
|
+
deserialize_context: (context = viewmodel_class.new_deserialize_context))
|
354
315
|
|
355
|
-
expected_edit_checks = [ViewModel::Reference.new(
|
356
|
-
ViewModel::Reference.new(
|
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
|
-
@
|
322
|
+
@model1.children.order(:position))
|
362
323
|
end
|
363
324
|
|
364
325
|
def test_append_associated_insert_has_many
|
365
|
-
c1, c2, c3 = @
|
366
|
-
pv =
|
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(
|
372
|
-
deserialize_context: (context =
|
332
|
+
before: ViewModel::Reference.new(child_viewmodel_class, c2.id),
|
333
|
+
deserialize_context: (context = viewmodel_class.new_deserialize_context))
|
373
334
|
|
374
|
-
n1 =
|
335
|
+
n1 = child_model_class.find_by_name('new1')
|
375
336
|
|
376
|
-
expected_edit_checks = [ViewModel::Reference.new(
|
377
|
-
ViewModel::Reference.new(
|
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
|
-
@
|
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(
|
388
|
-
deserialize_context: (context =
|
348
|
+
after: ViewModel::Reference.new(child_viewmodel_class, c2.id),
|
349
|
+
deserialize_context: (context = viewmodel_class.new_deserialize_context))
|
389
350
|
|
390
|
-
n2 =
|
351
|
+
n2 = child_model_class.find_by_name('new2')
|
391
352
|
|
392
|
-
expected_edit_checks = [ViewModel::Reference.new(
|
393
|
-
ViewModel::Reference.new(
|
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
|
-
@
|
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 =
|
364
|
+
deserialize_context: (context = viewmodel_class.new_deserialize_context))
|
404
365
|
|
405
|
-
n3 =
|
366
|
+
n3 = child_model_class.find_by_name('new3')
|
406
367
|
|
407
|
-
expected_edit_checks = [ViewModel::Reference.new(
|
408
|
-
ViewModel::Reference.new(
|
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
|
-
@
|
374
|
+
@model1.children.order(:position))
|
414
375
|
end
|
415
376
|
|
416
377
|
def test_edit_implicit_list_position
|
417
|
-
c1, c2, c3 = @
|
378
|
+
c1, c2, c3 = @model1.children.order(:position).to_a
|
418
379
|
|
419
|
-
alter_by_view!(
|
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,
|
425
|
-
@
|
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" => "
|
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
|
-
|
399
|
+
viewmodel_class.deserialize_from_view(view)
|
439
400
|
end
|
440
401
|
|
441
|
-
assert_equal(ex.nodes, [ViewModel::Reference.new(
|
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 = @
|
406
|
+
old_children = @model1.children.order(:position)
|
446
407
|
moved_child = old_children[1]
|
447
408
|
|
448
|
-
moved_child_ref = update_hash_for(
|
409
|
+
moved_child_ref = update_hash_for(child_viewmodel_class, moved_child)
|
449
410
|
|
450
|
-
view = { '_type' => '
|
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' => '
|
457
|
-
'id' => @
|
458
|
-
'children' => retained_children.map { |c| update_hash_for(
|
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 =
|
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
|
-
@
|
427
|
+
@model1.reload
|
467
428
|
assert_equal(retained_children,
|
468
|
-
@
|
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 = @
|
438
|
+
old_children = @model1.children.order(:position)
|
478
439
|
|
479
440
|
assert_raises(ViewModel::DeserializationError::ParentNotFound) do
|
480
|
-
alter_by_view!(
|
481
|
-
p2['children'] = old_children.map { |x| update_hash_for(
|
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!(
|
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!(
|
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 = @
|
465
|
+
old_children = @model1.children.order(:position)
|
505
466
|
moved_child = old_children[1]
|
506
467
|
|
507
|
-
view =
|
508
|
-
view['children'] <<
|
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' => '
|
512
|
-
'children' => retained_children.map { |c| update_hash_for(
|
472
|
+
release_view = { '_type' => 'Model', 'id' => @model1.id,
|
473
|
+
'children' => retained_children.map { |c| update_hash_for(child_viewmodel_class, c) }}
|
513
474
|
|
514
|
-
|
475
|
+
viewmodel_class.deserialize_from_view([view, release_view])
|
515
476
|
|
516
|
-
@
|
517
|
-
@
|
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, @
|
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 = @
|
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
|
-
|
490
|
+
viewmodel_class.new(@model1).append_associated(:children, { "_type" => "Child", "name" => "new" })
|
530
491
|
|
531
|
-
@
|
492
|
+
@model1.reload
|
532
493
|
|
533
|
-
assert_equal(4, @
|
534
|
-
lc = @
|
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 = @
|
500
|
+
child = @model1.children[1]
|
540
501
|
|
541
|
-
cv =
|
502
|
+
cv = child_viewmodel_class.new(child).to_hash
|
542
503
|
cv["name"] = "newname"
|
543
504
|
|
544
|
-
|
505
|
+
viewmodel_class.new(@model1).append_associated(:children, cv)
|
545
506
|
|
546
|
-
@
|
507
|
+
@model1.reload
|
547
508
|
|
548
509
|
# Child should have been moved to the end (and edited)
|
549
|
-
assert_equal(3, @
|
550
|
-
c1, c2, c3 = @
|
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 = @
|
519
|
+
p1c2 = @model1.children[1]
|
560
520
|
assert_equal(2, p1c2.position)
|
561
521
|
|
562
|
-
|
522
|
+
viewmodel_class.new(@model2).append_associated("children", { "_type" => "Child", "id" => p1c2.id })
|
563
523
|
|
564
|
-
@
|
565
|
-
@
|
524
|
+
@model1.reload
|
525
|
+
@model2.reload
|
566
526
|
|
567
|
-
p1c = @
|
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 = @
|
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 = @
|
539
|
+
child = @model1.children[1]
|
580
540
|
|
581
|
-
|
541
|
+
viewmodel_class.new(@model1).delete_associated(:children, child.id)
|
582
542
|
|
583
|
-
@
|
543
|
+
@model1.reload
|
584
544
|
|
585
545
|
# Child should have been removed
|
586
|
-
assert_equal(2, @
|
587
|
-
c1, c2 = @
|
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,
|
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 = @
|
555
|
+
child = @model1.children[1]
|
596
556
|
|
597
|
-
child_view =
|
557
|
+
child_view = child_viewmodel_class.new(child).to_hash
|
598
558
|
child_view["name"] = "changed"
|
599
559
|
|
600
|
-
view = { "_type" => "
|
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" => "
|
606
|
-
"id" => @
|
607
|
-
"children" => [{ "_type" => "Child", "id" => @
|
608
|
-
{ "_type" => "Child", "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 =
|
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
|
-
@
|
615
|
-
assert_equal(2, @
|
616
|
-
oc1, oc2 = @
|
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 = @
|
589
|
+
old_child = @model1.children[1]
|
630
590
|
|
631
|
-
old_child_view =
|
591
|
+
old_child_view = child_viewmodel_class.new(old_child).to_hash
|
632
592
|
old_child_view["name"] = "changed"
|
633
|
-
view =
|
593
|
+
view = viewmodel_class.new(@model2).to_hash
|
634
594
|
view["children"] << old_child_view
|
635
595
|
|
636
|
-
release_view = {"_type" => "
|
637
|
-
"children" => [{"_type" => "Child", "id" => @
|
638
|
-
{"_type" => "Child", "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
|
-
|
600
|
+
viewmodel_class.deserialize_from_view([view, release_view])
|
641
601
|
|
642
|
-
@
|
643
|
-
@
|
602
|
+
@model1.reload
|
603
|
+
@model2.reload
|
644
604
|
|
645
605
|
# child should be removed from old parent and positions updated
|
646
|
-
assert_equal(2, @
|
647
|
-
oc1, oc2 = @
|
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, @
|
654
|
-
nc1, _, nc3 = @
|
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 = @
|
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' => '
|
671
|
-
'id' => @
|
630
|
+
append_view = { '_type' => 'Model',
|
631
|
+
'id' => @model1.id,
|
672
632
|
'children' => fupdate }
|
673
633
|
|
674
|
-
result =
|
675
|
-
@
|
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
|
-
@
|
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 = @
|
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' => '
|
693
|
-
'id' => @
|
652
|
+
append_view = { '_type' => 'Model',
|
653
|
+
'id' => @model1.id,
|
694
654
|
'children' => fupdate }
|
695
|
-
|
696
|
-
@
|
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
|
-
@
|
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 = @
|
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' => '
|
711
|
-
'id' => @
|
670
|
+
append_view = { '_type' => 'Model',
|
671
|
+
'id' => @model1.id,
|
712
672
|
'children' => fupdate }
|
713
|
-
|
714
|
-
@
|
673
|
+
viewmodel_class.deserialize_from_view(append_view)
|
674
|
+
@model1.reload
|
715
675
|
|
716
676
|
assert_equal([c1.name, c3.name, c2.name],
|
717
|
-
@
|
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 = @
|
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' => '
|
730
|
-
'id' => @
|
689
|
+
append_view = { '_type' => 'Model',
|
690
|
+
'id' => @model1.id,
|
731
691
|
'children' => fupdate }
|
732
|
-
|
733
|
-
@
|
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
|
-
@
|
696
|
+
@model1.children.order(:position).pluck(:name))
|
737
697
|
end
|
738
698
|
|
739
699
|
def test_functional_update_append_before_corpse
|
740
|
-
_, c2, _ = @
|
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' => '
|
750
|
-
'id' => @
|
709
|
+
append_view = { '_type' => 'Model',
|
710
|
+
'id' => @model1.id,
|
751
711
|
'children' => fupdate }
|
752
712
|
assert_raises(ViewModel::DeserializationError::AssociatedNotFound) do
|
753
|
-
|
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 = @
|
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' => '
|
767
|
-
'id' => @
|
726
|
+
append_view = { '_type' => 'Model',
|
727
|
+
'id' => @model1.id,
|
768
728
|
'children' => fupdate }
|
769
|
-
|
770
|
-
@
|
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
|
-
@
|
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 = @
|
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' => '
|
786
|
-
'id' => @
|
745
|
+
append_view = { '_type' => 'Model',
|
746
|
+
'id' => @model1.id,
|
787
747
|
'children' => fupdate }
|
788
|
-
|
789
|
-
@
|
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
|
-
@
|
752
|
+
@model1.children.order(:position).pluck(:name))
|
793
753
|
end
|
794
754
|
|
795
755
|
def test_functional_update_append_after_corpse
|
796
|
-
_, c2, _ = @
|
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' => '
|
807
|
-
'id' => @
|
766
|
+
append_view = { '_type' => 'Model',
|
767
|
+
'id' => @model1.id,
|
808
768
|
'children' => fupdate }
|
809
769
|
assert_raises(ViewModel::DeserializationError::AssociatedNotFound) do
|
810
|
-
|
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 = @
|
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' => '
|
822
|
-
'id' => @
|
781
|
+
remove_view = { '_type' => 'Model',
|
782
|
+
'id' => @model1.id,
|
823
783
|
'children' => fupdate }
|
824
|
-
|
825
|
-
@
|
784
|
+
viewmodel_class.deserialize_from_view(remove_view)
|
785
|
+
@model1.reload
|
826
786
|
|
827
|
-
assert_equal([c1_id, c3_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 = @
|
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' => '
|
840
|
-
'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
|
-
|
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 = @
|
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' => '
|
860
|
-
'id' => @
|
819
|
+
update_view = { '_type' => 'Model',
|
820
|
+
'id' => @model1.id,
|
861
821
|
'children' => fupdate }
|
862
|
-
|
863
|
-
@
|
822
|
+
viewmodel_class.deserialize_from_view(update_view)
|
823
|
+
@model1.reload
|
864
824
|
|
865
|
-
assert_equal([c1_id, c2_id, c3_id], @
|
866
|
-
assert_equal('Functionally Updated Child',
|
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
|
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
|
877
|
-
|
878
|
-
|
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
|
-
|
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
|
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' => '
|
895
|
-
'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
|
-
|
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
|
-
|
907
|
-
|
877
|
+
it 'makes a reference association' do
|
878
|
+
assert(subject_association.referenced?)
|
879
|
+
end
|
908
880
|
|
909
|
-
|
910
|
-
|
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
|
-
|
913
|
-
|
914
|
-
|
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
|
-
|
918
|
-
|
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
|
-
|
922
|
-
|
923
|
-
|
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
|
-
|
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
|
-
@
|
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' => '
|
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!(
|
946
|
-
assert_equal([{ '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', @
|
1178
|
+
assert_equal('new c1 name', @model.children.first.name)
|
955
1179
|
end
|
956
1180
|
end
|
957
1181
|
end
|