iknow_view_models 3.7.7 → 3.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/iknow_view_models/version.rb +1 -1
- data/lib/view_model/active_record/update_context.rb +9 -1
- data/lib/view_model/active_record/update_data.rb +1 -3
- data/lib/view_model/active_record/update_operation.rb +20 -0
- data/lib/view_model/deserialization_error.rb +17 -0
- data/lib/view_model/schemas.rb +1 -1
- data/lib/view_model.rb +50 -3
- data/test/unit/view_model/active_record/belongs_to_test.rb +103 -1
- data/test/unit/view_model/active_record/has_many_test.rb +59 -1
- data/test/unit/view_model/active_record/has_one_test.rb +98 -4
- data/test/unit/view_model/active_record_test.rb +24 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 10ac2bba9965a5eb683cdbb972d1a7c8b31a6fd86304b1e52816cdd5649818d9
|
4
|
+
data.tar.gz: dcd13cba67c3b27beb4491b23d6a487ff30a3a3b930a8d2188a5503f77298c89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a0faccad196615ad236d93016b3268fe50c3eac86bfc52750445a53655fc24d24ff068442071d4ea0a6d1d19e86cdaba8ae24ad9cba5d4feedfbb007333217e9
|
7
|
+
data.tar.gz: 253dea949f2c1a79360969fbd662a93d072ef93ff43554b5b08bc65344e2cc96c58b0eea2a71ff734cd8d3507c07b2569638be4d483c116d7b498ea98aca5fd5
|
@@ -134,7 +134,15 @@ class ViewModel::ActiveRecord
|
|
134
134
|
|
135
135
|
updates.each do |ref, update_data|
|
136
136
|
viewmodel =
|
137
|
-
if update_data.
|
137
|
+
if update_data.auto_child_update?
|
138
|
+
raise ViewModel::DeserializationError::InvalidStructure.new(
|
139
|
+
'Cannot make an automatic child update to a root node',
|
140
|
+
ViewModel::Reference.new(update_data.viewmodel_class, nil))
|
141
|
+
elsif update_data.child_update?
|
142
|
+
raise ViewModel::DeserializationError::InvalidStructure.new(
|
143
|
+
'Cannot update an existing root node without a specified id',
|
144
|
+
ViewModel::Reference.new(update_data.viewmodel_class, nil))
|
145
|
+
elsif update_data.new?
|
138
146
|
viewmodel_class.for_new_model(id: update_data.id)
|
139
147
|
else
|
140
148
|
viewmodel_class.new(existing_models[update_data.id])
|
@@ -520,9 +520,7 @@ class ViewModel::ActiveRecord
|
|
520
520
|
end
|
521
521
|
end
|
522
522
|
|
523
|
-
|
524
|
-
id.nil? || metadata.new?
|
525
|
-
end
|
523
|
+
delegate :new?, :child_update?, :auto_child_update?, to: :metadata
|
526
524
|
|
527
525
|
def self.parse_hashes(root_subtree_hashes, referenced_subtree_hashes = {})
|
528
526
|
valid_reference_keys = referenced_subtree_hashes.keys.to_set
|
@@ -300,6 +300,26 @@ class ViewModel::ActiveRecord
|
|
300
300
|
case
|
301
301
|
when update_data.new?
|
302
302
|
child_viewmodel_class.for_new_model(id: update_data.id)
|
303
|
+
when update_data.child_update?
|
304
|
+
if association_data.collection?
|
305
|
+
raise ViewModel::DeserializationError::InvalidStructure.new(
|
306
|
+
'Cannot update existing children of a collection association without specified ids',
|
307
|
+
ViewModel::Reference.new(update_data.viewmodel_class, nil))
|
308
|
+
end
|
309
|
+
|
310
|
+
child = previous_child_viewmodels[0]
|
311
|
+
|
312
|
+
if child.nil?
|
313
|
+
unless update_data.auto_child_update?
|
314
|
+
raise ViewModel::DeserializationError::PreviousChildNotFound.new(
|
315
|
+
association_data.association_name.to_s,
|
316
|
+
self.blame_reference)
|
317
|
+
end
|
318
|
+
|
319
|
+
child = child_viewmodel_class.for_new_model
|
320
|
+
end
|
321
|
+
|
322
|
+
child
|
303
323
|
when existing_child = previous_by_key[key]
|
304
324
|
existing_child
|
305
325
|
when taken_child = update_context.try_take_released_viewmodel(key)
|
@@ -211,6 +211,23 @@ class ViewModel
|
|
211
211
|
end
|
212
212
|
end
|
213
213
|
|
214
|
+
class PreviousChildNotFound < NotFound
|
215
|
+
attr_reader :association
|
216
|
+
|
217
|
+
def initialize(association, blame_nodes)
|
218
|
+
@association = association
|
219
|
+
super(blame_nodes)
|
220
|
+
end
|
221
|
+
|
222
|
+
def detail
|
223
|
+
"No child currently exists to update for association '#{association}'"
|
224
|
+
end
|
225
|
+
|
226
|
+
def meta
|
227
|
+
super.merge(association: association)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
214
231
|
class DuplicateNodes < InvalidRequest
|
215
232
|
attr_reader :type
|
216
233
|
|
data/lib/view_model/schemas.rb
CHANGED
@@ -21,7 +21,7 @@ class ViewModel::Schemas
|
|
21
21
|
'description' => 'viewmodel update',
|
22
22
|
'properties' => { ViewModel::TYPE_ATTRIBUTE => { 'type' => 'string' },
|
23
23
|
ViewModel::ID_ATTRIBUTE => ID_SCHEMA,
|
24
|
-
ViewModel::NEW_ATTRIBUTE => { 'type' => 'boolean' },
|
24
|
+
ViewModel::NEW_ATTRIBUTE => { 'oneOf' => [{ 'type' => 'boolean' }, { 'type' => 'string', 'enum' => ['auto'] }] },
|
25
25
|
ViewModel::VERSION_ATTRIBUTE => { 'type' => 'integer' } },
|
26
26
|
'required' => [ViewModel::TYPE_ATTRIBUTE],
|
27
27
|
}.freeze
|
data/lib/view_model.rb
CHANGED
@@ -11,6 +11,22 @@ class ViewModel
|
|
11
11
|
ID_ATTRIBUTE = 'id'
|
12
12
|
TYPE_ATTRIBUTE = '_type'
|
13
13
|
VERSION_ATTRIBUTE = '_version'
|
14
|
+
|
15
|
+
# The optional _new attribute specifies explicitly whether a deserialization
|
16
|
+
# is to an new or existing model. The behaviour of _new is as follows:
|
17
|
+
# * true - Always create a new model, using the id if provided, error if
|
18
|
+
# a model with that id already exists
|
19
|
+
# * false - Never create a new model. If id isn’t provided, it's an error
|
20
|
+
# unless the viewmodel is being deserialized in the context of a
|
21
|
+
# singular association, in which case it's implicitly referring
|
22
|
+
# to the current child of that association, and is an error if
|
23
|
+
# that child doesn't exist.
|
24
|
+
# * nil - Create a new model if `id` is not specified, otherwise update the
|
25
|
+
# existing record with `id`, and error if it doesn't exist.
|
26
|
+
# * 'auto' - Can only be used when the viewmodel is being deserialized in the
|
27
|
+
# context of a singular association. `id` must not be specified. If
|
28
|
+
# the association currently has a child, update it, otherwise
|
29
|
+
# create a new one.
|
14
30
|
NEW_ATTRIBUTE = '_new'
|
15
31
|
|
16
32
|
BULK_UPDATE_TYPE = '_bulk_update'
|
@@ -20,10 +36,36 @@ class ViewModel
|
|
20
36
|
# Migrations leave a metadata attribute _migrated on any views that they
|
21
37
|
# alter. This attribute is accessible as metadata when deserializing migrated
|
22
38
|
# input, and is included in the output serialization sent to clients.
|
23
|
-
MIGRATED_ATTRIBUTE
|
39
|
+
MIGRATED_ATTRIBUTE = '_migrated'
|
24
40
|
|
25
41
|
Metadata = Struct.new(:id, :view_name, :schema_version, :new, :migrated) do
|
26
|
-
|
42
|
+
# Does this metadata describe deserialization to a new model, either by
|
43
|
+
# {new: true} or {new: nil, id: nil}
|
44
|
+
def new?
|
45
|
+
if new.nil?
|
46
|
+
id.nil?
|
47
|
+
else
|
48
|
+
new == true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Does this metadata describe an change to the anonymous child of a singular
|
53
|
+
# association
|
54
|
+
def child_update?
|
55
|
+
explicit_child_update? || auto_child_update?
|
56
|
+
end
|
57
|
+
|
58
|
+
# Does this metadata describe an update to the already-existing anonymous
|
59
|
+
# child of a singular association
|
60
|
+
def explicit_child_update?
|
61
|
+
new == false && id.nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
# Does this child metadata describe an create-or-update to the anonymous
|
65
|
+
# child of a singular association
|
66
|
+
def auto_child_update?
|
67
|
+
new == 'auto'
|
68
|
+
end
|
27
69
|
end
|
28
70
|
|
29
71
|
class << self
|
@@ -119,9 +161,14 @@ class ViewModel
|
|
119
161
|
id = hash.delete(ViewModel::ID_ATTRIBUTE)
|
120
162
|
type_name = hash.delete(ViewModel::TYPE_ATTRIBUTE)
|
121
163
|
schema_version = hash.delete(ViewModel::VERSION_ATTRIBUTE)
|
122
|
-
new = hash.delete(ViewModel::NEW_ATTRIBUTE)
|
164
|
+
new = hash.delete(ViewModel::NEW_ATTRIBUTE)
|
123
165
|
migrated = hash.delete(ViewModel::MIGRATED_ATTRIBUTE) { false }
|
124
166
|
|
167
|
+
if id && new == 'auto'
|
168
|
+
raise ViewModel::DeserializationError::InvalidSyntax.new(
|
169
|
+
"An explicit id must not be specified with an 'auto' child update")
|
170
|
+
end
|
171
|
+
|
125
172
|
Metadata.new(id, type_name, schema_version, new, migrated)
|
126
173
|
end
|
127
174
|
|
@@ -85,7 +85,7 @@ class ViewModel::ActiveRecord::BelongsToTest < ActiveSupport::TestCase
|
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
|
-
def
|
88
|
+
def test_belongs_to_create_implicit_new
|
89
89
|
@model1.update(child: nil)
|
90
90
|
|
91
91
|
alter_by_view!(ModelView, @model1) do |view, _refs|
|
@@ -95,6 +95,108 @@ class ViewModel::ActiveRecord::BelongsToTest < ActiveSupport::TestCase
|
|
95
95
|
assert_equal('cheese', @model1.child.name)
|
96
96
|
end
|
97
97
|
|
98
|
+
def test_belongs_to_create
|
99
|
+
@model1.update(child: nil)
|
100
|
+
|
101
|
+
alter_by_view!(ModelView, @model1) do |view, _refs|
|
102
|
+
view['child'] = { '_type' => 'Child', '_new' => true, 'name' => 'cheese' }
|
103
|
+
end
|
104
|
+
|
105
|
+
assert_equal('cheese', @model1.child.name)
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_belongs_to_create_with_id
|
109
|
+
@model1.update(child: nil)
|
110
|
+
|
111
|
+
alter_by_view!(ModelView, @model1) do |view, _refs|
|
112
|
+
view['child'] = { '_type' => 'Child', 'id' => 9999, '_new' => true, 'name' => 'cheese' }
|
113
|
+
end
|
114
|
+
|
115
|
+
assert_equal('cheese', @model1.child.name)
|
116
|
+
assert_equal(9999, @model1.child.id)
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_belongs_to_create_with_colliding_id
|
120
|
+
child_id = @model1.child_id
|
121
|
+
@model1.update(child: nil)
|
122
|
+
|
123
|
+
ex = assert_raises(ViewModel::DeserializationError::UniqueViolation) do
|
124
|
+
alter_by_view!(ModelView, @model1) do |view, _refs|
|
125
|
+
view['child'] = { '_type' => 'Child', 'id' => child_id, '_new' => true, 'name' => 'cheese' }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
assert_equal(['id'], ex.columns)
|
130
|
+
assert_equal([ViewModel::Reference.new(ChildView, child_id)], ex.nodes)
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_belongs_to_create_child_with_auto
|
134
|
+
@model1.update(child: nil)
|
135
|
+
|
136
|
+
alter_by_view!(ModelView, @model1) do |view, _refs|
|
137
|
+
view['child'] = { '_type' => 'Child', '_new' => 'auto', 'name' => 'cheese' }
|
138
|
+
end
|
139
|
+
|
140
|
+
assert_equal('cheese', @model1.child.name)
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_belongs_to_edit_implicit_new
|
144
|
+
child_id = @model1.child_id
|
145
|
+
|
146
|
+
alter_by_view!(ModelView, @model1) do |view, _refs|
|
147
|
+
view['child'] = { '_type' => 'Child', 'id' => child_id, 'name' => 'cheese' }
|
148
|
+
end
|
149
|
+
|
150
|
+
assert_equal('cheese', @model1.child.name)
|
151
|
+
assert_equal(child_id, @model1.child.id)
|
152
|
+
end
|
153
|
+
|
154
|
+
def test_belongs_to_edit
|
155
|
+
child_id = @model1.child_id
|
156
|
+
|
157
|
+
alter_by_view!(ModelView, @model1) do |view, _refs|
|
158
|
+
view['child'] = { '_type' => 'Child', 'id' => child_id, '_new' => false, 'name' => 'cheese' }
|
159
|
+
end
|
160
|
+
|
161
|
+
assert_equal('cheese', @model1.child.name)
|
162
|
+
assert_equal(child_id, @model1.child.id)
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_belongs_to_edit_child_without_id
|
166
|
+
child_id = @model1.child_id
|
167
|
+
|
168
|
+
alter_by_view!(ModelView, @model1) do |view, _refs|
|
169
|
+
view['child'] = { '_type' => 'Child', '_new' => false, 'name' => 'cheese' }
|
170
|
+
end
|
171
|
+
|
172
|
+
assert_equal('cheese', @model1.child.name)
|
173
|
+
assert_equal(child_id, @model1.child.id)
|
174
|
+
end
|
175
|
+
|
176
|
+
def test_belongs_to_one_edit_without_id_no_child
|
177
|
+
@model1.update(child: nil)
|
178
|
+
|
179
|
+
ex = assert_raises(ViewModel::DeserializationError::PreviousChildNotFound) do
|
180
|
+
alter_by_view!(ModelView, @model1) do |view, _refs|
|
181
|
+
view['child'] = { '_type' => 'Child', '_new' => false, 'name' => 'cheese' }
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
assert_equal('child', ex.association)
|
186
|
+
assert_equal([ViewModel::Reference.new(ModelView, @model1.id)], ex.nodes)
|
187
|
+
end
|
188
|
+
|
189
|
+
def test_belongs_to_edit_child_with_auto
|
190
|
+
child_id = @model1.child_id
|
191
|
+
|
192
|
+
alter_by_view!(ModelView, @model1) do |view, _refs|
|
193
|
+
view['child'] = { '_type' => 'Child', '_new' => 'auto', 'name' => 'cheese' }
|
194
|
+
end
|
195
|
+
|
196
|
+
assert_equal('cheese', @model1.child.name)
|
197
|
+
assert_equal(child_id, @model1.child.id)
|
198
|
+
end
|
199
|
+
|
98
200
|
def test_belongs_to_replace
|
99
201
|
old_child = @model1.child
|
100
202
|
|
@@ -107,7 +107,7 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
|
|
107
107
|
assert(pv.model.children.blank?)
|
108
108
|
end
|
109
109
|
|
110
|
-
def
|
110
|
+
def test_create_has_many_implicit_new
|
111
111
|
view = { '_type' => 'Model',
|
112
112
|
'name' => 'p',
|
113
113
|
'children' => [{ '_type' => 'Child', 'name' => 'c1' },
|
@@ -123,6 +123,64 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
|
|
123
123
|
assert_equal(%w[c1 c2], pv.model.children.map(&:name))
|
124
124
|
end
|
125
125
|
|
126
|
+
def test_create_has_many
|
127
|
+
view = { '_type' => 'Model',
|
128
|
+
'name' => 'p',
|
129
|
+
'children' => [{ '_type' => 'Child', '_new' => true, 'name' => 'c1' },
|
130
|
+
{ '_type' => 'Child', '_new' => true, 'name' => 'c2' },] }
|
131
|
+
|
132
|
+
context = viewmodel_class.new_deserialize_context
|
133
|
+
pv = viewmodel_class.deserialize_from_view(view, deserialize_context: context)
|
134
|
+
|
135
|
+
assert_contains_exactly(
|
136
|
+
[pv.to_reference, pv.children[0].to_reference, pv.children[1].to_reference],
|
137
|
+
context.valid_edit_refs)
|
138
|
+
|
139
|
+
assert_equal(%w[c1 c2], pv.model.children.map(&:name))
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_create_has_many_with_id
|
143
|
+
view = { '_type' => 'Model',
|
144
|
+
'name' => 'p',
|
145
|
+
'children' => [{ '_type' => 'Child', 'id' => 9998, '_new' => true, 'name' => 'c1' },
|
146
|
+
{ '_type' => 'Child', 'id' => 9999, '_new' => true, 'name' => 'c2' },] }
|
147
|
+
|
148
|
+
context = viewmodel_class.new_deserialize_context
|
149
|
+
pv = viewmodel_class.deserialize_from_view(view, deserialize_context: context)
|
150
|
+
|
151
|
+
assert_contains_exactly(
|
152
|
+
[pv.to_reference, pv.children[0].to_reference, pv.children[1].to_reference],
|
153
|
+
context.valid_edit_refs)
|
154
|
+
|
155
|
+
assert_equal(%w[c1 c2], pv.model.children.map(&:name))
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_create_has_many_auto
|
159
|
+
view = { '_type' => 'Model',
|
160
|
+
'name' => 'p',
|
161
|
+
'children' => [{ '_type' => 'Child', '_new' => 'auto', 'name' => 'c1' }] }
|
162
|
+
|
163
|
+
context = viewmodel_class.new_deserialize_context
|
164
|
+
ex = assert_raises(ViewModel::DeserializationError::InvalidStructure) do
|
165
|
+
viewmodel_class.deserialize_from_view(view, deserialize_context: context)
|
166
|
+
end
|
167
|
+
assert_match(/existing children of a collection association without specified ids/, ex.message)
|
168
|
+
assert_equal([ViewModel::Reference.new(ChildView, nil)], ex.nodes)
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_update_has_many_without_id
|
172
|
+
view = { '_type' => 'Model',
|
173
|
+
'name' => 'p',
|
174
|
+
'children' => [{ '_type' => 'Child', '_new' => false, 'name' => 'c1' }] }
|
175
|
+
|
176
|
+
context = viewmodel_class.new_deserialize_context
|
177
|
+
ex = assert_raises(ViewModel::DeserializationError::InvalidStructure) do
|
178
|
+
viewmodel_class.deserialize_from_view(view, deserialize_context: context)
|
179
|
+
end
|
180
|
+
assert_match(/existing children of a collection association without specified ids/, ex.message)
|
181
|
+
assert_equal([ViewModel::Reference.new(ChildView, nil)], ex.nodes)
|
182
|
+
end
|
183
|
+
|
126
184
|
def test_nil_multiple_association
|
127
185
|
view = {
|
128
186
|
'_type' => 'Model',
|
@@ -98,7 +98,7 @@ class ViewModel::ActiveRecord::HasOneTest < ActiveSupport::TestCase
|
|
98
98
|
assert_nil(pv.model.child)
|
99
99
|
end
|
100
100
|
|
101
|
-
def
|
101
|
+
def test_has_one_create_implicit_new
|
102
102
|
@model1.update(child: nil)
|
103
103
|
|
104
104
|
alter_by_view!(ModelView, @model1) do |view, _refs|
|
@@ -108,12 +108,106 @@ class ViewModel::ActiveRecord::HasOneTest < ActiveSupport::TestCase
|
|
108
108
|
assert_equal('t', @model1.child.name)
|
109
109
|
end
|
110
110
|
|
111
|
-
def
|
111
|
+
def test_has_one_create
|
112
|
+
@model1.update(child: nil)
|
113
|
+
|
114
|
+
alter_by_view!(ModelView, @model1) do |view, _refs|
|
115
|
+
view['child'] = { '_type' => 'Child', '_new' => true, 'name' => 't' }
|
116
|
+
end
|
117
|
+
|
118
|
+
assert_equal('t', @model1.child.name)
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_has_one_create_with_id
|
122
|
+
@model1.update(child: nil)
|
123
|
+
|
124
|
+
alter_by_view!(ModelView, @model1) do |view, _refs|
|
125
|
+
view['child'] = { '_type' => 'Child', 'id' => 9999, '_new' => true, 'name' => 't' }
|
126
|
+
end
|
127
|
+
|
128
|
+
assert_equal('t', @model1.child.name)
|
129
|
+
assert_equal(9999, @model1.child.id)
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_has_one_create_with_colliding_id
|
133
|
+
child_id = @model2.child.id
|
134
|
+
@model1.update(child: nil)
|
135
|
+
|
136
|
+
ex = assert_raises(ViewModel::DeserializationError::UniqueViolation) do
|
137
|
+
alter_by_view!(ModelView, @model1) do |view, _refs|
|
138
|
+
view['child'] = { '_type' => 'Child', 'id' => child_id, '_new' => true, 'name' => 'cheese' }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
assert_equal(['id'], ex.columns)
|
143
|
+
assert_equal([ViewModel::Reference.new(ChildView, child_id)], ex.nodes)
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_has_one_create_child_with_auto
|
147
|
+
@model1.update(child: nil)
|
148
|
+
|
149
|
+
alter_by_view!(ModelView, @model1) do |view, _refs|
|
150
|
+
view['child'] = { '_type' => 'Child', '_new' => 'auto', 'name' => 'cheese' }
|
151
|
+
end
|
152
|
+
|
153
|
+
assert_equal('cheese', @model1.child.name)
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_has_one_edit_implicit_new
|
157
|
+
child_id = @model1.child.id
|
158
|
+
|
159
|
+
alter_by_view!(ModelView, @model1) do |view, _refs|
|
160
|
+
view['child'] = { '_type' => 'Child', 'id' => child_id, 'name' => 'cheese' }
|
161
|
+
end
|
162
|
+
|
163
|
+
assert_equal('cheese', @model1.child.name)
|
164
|
+
assert_equal(child_id, @model1.child.id)
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_has_one_edit
|
168
|
+
child_id = @model1.child.id
|
169
|
+
|
170
|
+
alter_by_view!(ModelView, @model1) do |view, _refs|
|
171
|
+
view['child'] = { '_type' => 'Child', 'id' => child_id, '_new' => false, 'name' => 'cheese' }
|
172
|
+
end
|
173
|
+
|
174
|
+
assert_equal('cheese', @model1.child.name)
|
175
|
+
assert_equal(child_id, @model1.child.id)
|
176
|
+
end
|
177
|
+
|
178
|
+
def test_has_one_edit_without_id
|
179
|
+
child_id = @model1.child.id
|
180
|
+
|
181
|
+
alter_by_view!(ModelView, @model1) do |view, _refs|
|
182
|
+
view['child'] = { '_type' => 'Child', '_new' => false, 'name' => 'cheese' }
|
183
|
+
end
|
184
|
+
|
185
|
+
assert_equal('cheese', @model1.child.name)
|
186
|
+
assert_equal(child_id, @model1.child.id)
|
187
|
+
end
|
188
|
+
|
189
|
+
def test_has_one_edit_without_id_no_child
|
190
|
+
@model1.update(child: nil)
|
191
|
+
|
192
|
+
ex = assert_raises(ViewModel::DeserializationError::PreviousChildNotFound) do
|
193
|
+
alter_by_view!(ModelView, @model1) do |view, _refs|
|
194
|
+
view['child'] = { '_type' => 'Child', '_new' => false, 'name' => 'cheese' }
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
assert_equal('child', ex.association)
|
199
|
+
assert_equal([ViewModel::Reference.new(ModelView, @model1.id)], ex.nodes)
|
200
|
+
end
|
201
|
+
|
202
|
+
def test_belongs_to_edit_child_with_auto
|
203
|
+
child_id = @model1.child.id
|
204
|
+
|
112
205
|
alter_by_view!(ModelView, @model1) do |view, _refs|
|
113
|
-
view['child']
|
206
|
+
view['child'] = { '_type' => 'Child', '_new' => 'auto', 'name' => 'cheese' }
|
114
207
|
end
|
115
208
|
|
116
|
-
assert_equal('
|
209
|
+
assert_equal('cheese', @model1.child.name)
|
210
|
+
assert_equal(child_id, @model1.child.id)
|
117
211
|
end
|
118
212
|
|
119
213
|
def test_has_one_destroy
|
@@ -127,6 +127,30 @@ class ViewModel::ActiveRecordTest < ActiveSupport::TestCase
|
|
127
127
|
assert_equal([ViewModel::Reference.new(ParentView, 9999)], ex.nodes)
|
128
128
|
end
|
129
129
|
|
130
|
+
def test_create_implicit_id_raises
|
131
|
+
view = {
|
132
|
+
'_type' => 'Parent',
|
133
|
+
'_new' => false,
|
134
|
+
}
|
135
|
+
ex = assert_raises(ViewModel::DeserializationError::InvalidStructure) do
|
136
|
+
ParentView.deserialize_from_view(view)
|
137
|
+
end
|
138
|
+
assert_match(/existing root node without a specified id/, ex.message)
|
139
|
+
assert_equal([ViewModel::Reference.new(ParentView, nil)], ex.nodes)
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_create_auto_raises
|
143
|
+
view = {
|
144
|
+
'_type' => 'Parent',
|
145
|
+
'_new' => 'auto',
|
146
|
+
}
|
147
|
+
ex = assert_raises(ViewModel::DeserializationError::InvalidStructure) do
|
148
|
+
ParentView.deserialize_from_view(view)
|
149
|
+
end
|
150
|
+
assert_match(/automatic child update to a root node/, ex.message)
|
151
|
+
assert_equal([ViewModel::Reference.new(ParentView, nil)], ex.nodes)
|
152
|
+
end
|
153
|
+
|
130
154
|
def test_read_only_raises_with_id
|
131
155
|
view = {
|
132
156
|
'_type' => 'Parent',
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iknow_view_models
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- iKnow Team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-01-
|
11
|
+
date: 2024-01-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|