iknow_view_models 3.7.7 → 3.8.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/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
|