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