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
@@ -63,42 +63,58 @@ class ViewModel::ActiveRecord < ViewModel::Record
|
|
63
63
|
_list_attribute_name.present?
|
64
64
|
end
|
65
65
|
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
66
|
+
# Adds an association from the model to this viewmodel. The associated model
|
67
|
+
# will be recursively (de)serialized by its own viewmodel type, which will
|
68
|
+
# be inferred from the model name, or may be explicitly specified.
|
69
|
+
#
|
70
|
+
# An association to a root viewmodel type will be serialized with an
|
71
|
+
# indirect reference, while a child viewmodel type will be directly nested.
|
72
|
+
#
|
73
|
+
# - +as+ sets the name of the association in the viewmodel
|
74
|
+
#
|
75
|
+
# - +viewmodel+, +viewmodels+ specifies the viewmodel(s) to use for the
|
76
|
+
# association
|
77
|
+
#
|
78
|
+
# - +external+ indicates an association external to the view. Externalized
|
79
|
+
# associations are not included in (de)serializations of the parent, and
|
80
|
+
# must be independently manipulated using `AssociationManipulation`.
|
81
|
+
# External associations may only be made to root viewmodels.
|
82
|
+
#
|
70
83
|
# - +through+ names an ActiveRecord association that will be used like an
|
71
84
|
# ActiveRecord +has_many:through:+.
|
85
|
+
#
|
72
86
|
# - +through_order_attr+ the through model is ordered by the given attribute
|
73
87
|
# (only applies to when +through+ is set).
|
74
88
|
def association(association_name,
|
89
|
+
as: nil,
|
75
90
|
viewmodel: nil,
|
76
91
|
viewmodels: nil,
|
77
|
-
|
78
|
-
|
92
|
+
external: false,
|
93
|
+
read_only: false,
|
79
94
|
through: nil,
|
80
|
-
through_order_attr: nil
|
81
|
-
as: nil)
|
82
|
-
|
83
|
-
if through
|
84
|
-
model_association_name = through
|
85
|
-
through_to = association_name
|
86
|
-
else
|
87
|
-
model_association_name = association_name
|
88
|
-
through_to = nil
|
89
|
-
end
|
95
|
+
through_order_attr: nil)
|
90
96
|
|
91
97
|
vm_association_name = (as || association_name).to_s
|
92
98
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
99
|
+
if through
|
100
|
+
direct_association_name = through
|
101
|
+
indirect_association_name = association_name
|
102
|
+
else
|
103
|
+
direct_association_name = association_name
|
104
|
+
indirect_association_name = nil
|
97
105
|
end
|
98
106
|
|
99
|
-
|
107
|
+
target_viewmodels = Array.wrap(viewmodel || viewmodels)
|
100
108
|
|
101
|
-
association_data = AssociationData.new(
|
109
|
+
association_data = AssociationData.new(
|
110
|
+
owner: self,
|
111
|
+
association_name: vm_association_name,
|
112
|
+
direct_association_name: direct_association_name,
|
113
|
+
indirect_association_name: indirect_association_name,
|
114
|
+
target_viewmodels: target_viewmodels,
|
115
|
+
external: external,
|
116
|
+
read_only: read_only,
|
117
|
+
through_order_attr: through_order_attr)
|
102
118
|
|
103
119
|
_members[vm_association_name] = association_data
|
104
120
|
|
@@ -108,21 +124,7 @@ class ViewModel::ActiveRecord < ViewModel::Record
|
|
108
124
|
end
|
109
125
|
|
110
126
|
define_method :"serialize_#{vm_association_name}" do |json, serialize_context: self.class.new_serialize_context|
|
111
|
-
|
112
|
-
json.set! vm_association_name do
|
113
|
-
case
|
114
|
-
when associated.nil?
|
115
|
-
json.null!
|
116
|
-
when association_data.through?
|
117
|
-
json.array!(associated) do |through_target|
|
118
|
-
self.class.serialize_as_reference(through_target, json, serialize_context: serialize_context)
|
119
|
-
end
|
120
|
-
when shared
|
121
|
-
self.class.serialize_as_reference(associated, json, serialize_context: serialize_context)
|
122
|
-
else
|
123
|
-
self.class.serialize(associated, json, serialize_context: serialize_context)
|
124
|
-
end
|
125
|
-
end
|
127
|
+
_serialize_association(vm_association_name, json, serialize_context: serialize_context)
|
126
128
|
end
|
127
129
|
end
|
128
130
|
end
|
@@ -171,11 +173,6 @@ class ViewModel::ActiveRecord < ViewModel::Record
|
|
171
173
|
ViewModel::Utils.wrap_one_or_many(subtree_hash_or_hashes) do |subtree_hashes|
|
172
174
|
root_update_data, referenced_update_data = UpdateData.parse_hashes(subtree_hashes, references)
|
173
175
|
|
174
|
-
# Provide information about what was updated
|
175
|
-
deserialize_context.updated_associations = root_update_data
|
176
|
-
.map { |upd| upd.updated_associations }
|
177
|
-
.inject({}) { |acc, assocs| acc.deep_merge(assocs) }
|
178
|
-
|
179
176
|
_updated_viewmodels =
|
180
177
|
UpdateContext
|
181
178
|
.build!(root_update_data, referenced_update_data, root_type: self)
|
@@ -184,27 +181,18 @@ class ViewModel::ActiveRecord < ViewModel::Record
|
|
184
181
|
end
|
185
182
|
end
|
186
183
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
# specified in the serialize_context.
|
191
|
-
|
192
|
-
# when deserializing, we start with intrinsic non-shared associations. We
|
193
|
-
# then traverse the structure of the tree to deserialize to map out which
|
194
|
-
# optional or shared associations are used from each type. We then explore
|
195
|
-
# from the root type to build an preload specification that will include
|
196
|
-
# them all. (We can subsequently use this same structure to build a
|
197
|
-
# serialization context featuring the same associations.)
|
198
|
-
|
184
|
+
# Constructs a preload specification of the required models for
|
185
|
+
# serializing/deserializing this view.
|
186
|
+
def eager_includes(serialize_context: new_serialize_context, include_referenced: true)
|
199
187
|
association_specs = {}
|
200
188
|
_members.each do |assoc_name, association_data|
|
201
189
|
next unless association_data.is_a?(AssociationData)
|
202
|
-
next
|
190
|
+
next if association_data.external?
|
203
191
|
|
204
192
|
child_context =
|
205
193
|
if self.synthetic
|
206
194
|
serialize_context
|
207
|
-
elsif association_data.
|
195
|
+
elsif association_data.referenced?
|
208
196
|
serialize_context.for_references
|
209
197
|
else
|
210
198
|
serialize_context.for_child(nil, association_name: assoc_name)
|
@@ -213,22 +201,22 @@ class ViewModel::ActiveRecord < ViewModel::Record
|
|
213
201
|
case
|
214
202
|
when association_data.through?
|
215
203
|
viewmodel = association_data.direct_viewmodel
|
216
|
-
children = viewmodel.eager_includes(serialize_context: child_context,
|
204
|
+
children = viewmodel.eager_includes(serialize_context: child_context, include_referenced: include_referenced)
|
217
205
|
|
218
|
-
when !
|
219
|
-
children = nil # Load up to the
|
206
|
+
when !include_referenced && association_data.referenced?
|
207
|
+
children = nil # Load up to the root viewmodel, but no further
|
220
208
|
|
221
209
|
when association_data.polymorphic?
|
222
210
|
children_by_klass = {}
|
223
211
|
association_data.viewmodel_classes.each do |vm_class|
|
224
212
|
klass = vm_class.model_class.name
|
225
|
-
children_by_klass[klass] = vm_class.eager_includes(serialize_context: child_context,
|
213
|
+
children_by_klass[klass] = vm_class.eager_includes(serialize_context: child_context, include_referenced: include_referenced)
|
226
214
|
end
|
227
215
|
children = DeepPreloader::PolymorphicSpec.new(children_by_klass)
|
228
216
|
|
229
217
|
else
|
230
218
|
viewmodel = association_data.viewmodel_class
|
231
|
-
children = viewmodel.eager_includes(serialize_context: child_context,
|
219
|
+
children = viewmodel.eager_includes(serialize_context: child_context, include_referenced: include_referenced)
|
232
220
|
end
|
233
221
|
|
234
222
|
association_specs[association_data.direct_reflection.name.to_s] = children
|
@@ -236,26 +224,26 @@ class ViewModel::ActiveRecord < ViewModel::Record
|
|
236
224
|
DeepPreloader::Spec.new(association_specs)
|
237
225
|
end
|
238
226
|
|
239
|
-
def dependent_viewmodels(seen = Set.new,
|
227
|
+
def dependent_viewmodels(seen = Set.new, include_referenced: true)
|
240
228
|
return if seen.include?(self)
|
241
229
|
|
242
230
|
seen << self
|
243
231
|
|
244
232
|
_members.each_value do |data|
|
245
233
|
next unless data.is_a?(AssociationData)
|
246
|
-
next unless
|
234
|
+
next unless include_referenced || !data.referenced?
|
247
235
|
data.viewmodel_classes.each do |vm|
|
248
|
-
vm.dependent_viewmodels(seen,
|
236
|
+
vm.dependent_viewmodels(seen, include_referenced: include_referenced)
|
249
237
|
end
|
250
238
|
end
|
251
239
|
|
252
240
|
seen
|
253
241
|
end
|
254
242
|
|
255
|
-
def deep_schema_version(
|
256
|
-
(@deep_schema_version ||= {})[
|
243
|
+
def deep_schema_version(include_referenced: true)
|
244
|
+
(@deep_schema_version ||= {})[include_referenced] ||=
|
257
245
|
begin
|
258
|
-
dependent_viewmodels(
|
246
|
+
dependent_viewmodels(include_referenced: include_referenced).each_with_object({}) do |view, h|
|
259
247
|
h[view.view_name] = view.schema_version
|
260
248
|
end
|
261
249
|
end
|
@@ -270,6 +258,7 @@ class ViewModel::ActiveRecord < ViewModel::Record
|
|
270
258
|
def _association_data(association_name)
|
271
259
|
association_data = self._members[association_name.to_s]
|
272
260
|
raise ArgumentError.new("Invalid association '#{association_name}'") unless association_data.is_a?(AssociationData)
|
261
|
+
|
273
262
|
association_data
|
274
263
|
end
|
275
264
|
end
|
@@ -282,7 +271,7 @@ class ViewModel::ActiveRecord < ViewModel::Record
|
|
282
271
|
|
283
272
|
def serialize_members(json, serialize_context: self.class.new_serialize_context)
|
284
273
|
self.class._members.each do |member_name, member_data|
|
285
|
-
next
|
274
|
+
next if member_data.association? && member_data.external?
|
286
275
|
|
287
276
|
member_context =
|
288
277
|
case member_data
|
@@ -309,6 +298,13 @@ class ViewModel::ActiveRecord < ViewModel::Record
|
|
309
298
|
|
310
299
|
def association_changed!(association_name)
|
311
300
|
association_name = association_name.to_s
|
301
|
+
|
302
|
+
association_data = self.class._association_data(association_name)
|
303
|
+
|
304
|
+
if association_data.read_only?
|
305
|
+
raise ViewModel::DeserializationError::ReadOnlyAssociation.new(association_name, blame_reference)
|
306
|
+
end
|
307
|
+
|
312
308
|
unless @changed_associations.include?(association_name)
|
313
309
|
@changed_associations << association_name
|
314
310
|
end
|
@@ -321,10 +317,12 @@ class ViewModel::ActiveRecord < ViewModel::Record
|
|
321
317
|
# Additionally pass `changed_associations` while constructing changes.
|
322
318
|
def changes
|
323
319
|
ViewModel::Changes.new(
|
324
|
-
new:
|
325
|
-
changed_attributes:
|
326
|
-
changed_associations:
|
327
|
-
|
320
|
+
new: new_model?,
|
321
|
+
changed_attributes: changed_attributes,
|
322
|
+
changed_associations: changed_associations,
|
323
|
+
changed_nested_children: changed_nested_children?,
|
324
|
+
changed_referenced_children: changed_referenced_children?,
|
325
|
+
)
|
328
326
|
end
|
329
327
|
|
330
328
|
def clear_changes!
|
@@ -365,14 +363,36 @@ class ViewModel::ActiveRecord < ViewModel::Record
|
|
365
363
|
end
|
366
364
|
end
|
367
365
|
|
366
|
+
def _serialize_association(association_name, json, serialize_context:)
|
367
|
+
associated = self.public_send(association_name)
|
368
|
+
association_data = self.class._association_data(association_name)
|
369
|
+
|
370
|
+
json.set! association_name do
|
371
|
+
case
|
372
|
+
when associated.nil?
|
373
|
+
json.null!
|
374
|
+
when association_data.referenced?
|
375
|
+
if association_data.collection?
|
376
|
+
json.array!(associated) do |target|
|
377
|
+
self.class.serialize_as_reference(target, json, serialize_context: serialize_context)
|
378
|
+
end
|
379
|
+
else
|
380
|
+
self.class.serialize_as_reference(associated, json, serialize_context: serialize_context)
|
381
|
+
end
|
382
|
+
else
|
383
|
+
self.class.serialize(associated, json, serialize_context: serialize_context)
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
368
388
|
def context_for_child(member_name, context:)
|
369
389
|
# Synthetic viewmodels don't exist as far as the traversal context is
|
370
390
|
# concerned: pass through the child context received from the parent
|
371
391
|
return context if self.class.synthetic
|
372
392
|
|
373
|
-
#
|
393
|
+
# associations to roots start a new tree
|
374
394
|
member_data = self.class._members[member_name.to_s]
|
375
|
-
if member_data.
|
395
|
+
if member_data.association? && member_data.referenced?
|
376
396
|
return context.for_references
|
377
397
|
end
|
378
398
|
|
@@ -4,31 +4,16 @@
|
|
4
4
|
# `add_to_transaction`, the abstract method `after_transaction` will be invoked
|
5
5
|
# by AR's callbacks.
|
6
6
|
module ViewModel::AfterTransactionRunner
|
7
|
-
|
8
|
-
def committed!(*)
|
9
|
-
after_commit
|
10
|
-
end
|
7
|
+
def committed!; end
|
11
8
|
|
12
9
|
def before_committed!
|
13
|
-
|
10
|
+
after_transaction
|
14
11
|
end
|
15
12
|
|
16
13
|
def rolledback!(*)
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
def trigger_transactional_callbacks?
|
21
|
-
true
|
14
|
+
after_transaction
|
22
15
|
end
|
23
16
|
|
24
|
-
# Our simplified API
|
25
|
-
|
26
|
-
def before_commit; end
|
27
|
-
|
28
|
-
def after_commit; end
|
29
|
-
|
30
|
-
def after_rollback; end
|
31
|
-
|
32
17
|
def add_to_transaction
|
33
18
|
if connection.transaction_open?
|
34
19
|
connection.add_transaction_record(self)
|
data/lib/view_model/callbacks.rb
CHANGED
@@ -11,9 +11,9 @@ module ViewModel::Callbacks
|
|
11
11
|
# callbacks instance with additional instance method access to the view,
|
12
12
|
# context and extra context-dependent parameters.
|
13
13
|
module CallbackEnvContext
|
14
|
-
def method_missing(method,
|
14
|
+
def method_missing(method, *args, &block)
|
15
15
|
if _callbacks.respond_to?(method, true)
|
16
|
-
_callbacks.send(method,
|
16
|
+
_callbacks.send(method, *args, &block)
|
17
17
|
else
|
18
18
|
super
|
19
19
|
end
|
data/lib/view_model/changes.rb
CHANGED
@@ -5,18 +5,20 @@ require 'view_model/utils/collections'
|
|
5
5
|
class ViewModel::Changes
|
6
6
|
using ViewModel::Utils::Collections
|
7
7
|
|
8
|
-
attr_reader :new, :changed_attributes, :changed_associations, :
|
8
|
+
attr_reader :new, :changed_attributes, :changed_associations, :changed_nested_children, :changed_referenced_children, :deleted
|
9
9
|
|
10
10
|
alias new? new
|
11
11
|
alias deleted? deleted
|
12
|
-
alias
|
12
|
+
alias changed_nested_children? changed_nested_children
|
13
|
+
alias changed_referenced_children? changed_referenced_children
|
13
14
|
|
14
|
-
def initialize(new: false, changed_attributes: [], changed_associations: [],
|
15
|
-
@new
|
16
|
-
@changed_attributes
|
17
|
-
@changed_associations
|
18
|
-
@
|
19
|
-
@
|
15
|
+
def initialize(new: false, changed_attributes: [], changed_associations: [], changed_nested_children: false, changed_referenced_children: false, deleted: false)
|
16
|
+
@new = new
|
17
|
+
@changed_attributes = changed_attributes.map(&:to_s)
|
18
|
+
@changed_associations = changed_associations.map(&:to_s)
|
19
|
+
@changed_nested_children = changed_nested_children
|
20
|
+
@changed_referenced_children = changed_referenced_children
|
21
|
+
@deleted = deleted
|
20
22
|
end
|
21
23
|
|
22
24
|
def contained_to?(associations: [], attributes: [])
|
@@ -34,17 +36,22 @@ class ViewModel::Changes
|
|
34
36
|
new? || deleted? || changed_attributes.present? || changed_associations.present?
|
35
37
|
end
|
36
38
|
|
37
|
-
def
|
38
|
-
changed? ||
|
39
|
+
def changed_nested_tree?
|
40
|
+
changed? || changed_nested_children?
|
41
|
+
end
|
42
|
+
|
43
|
+
def changed_owned_tree?
|
44
|
+
changed? || changed_nested_children? || changed_referenced_children?
|
39
45
|
end
|
40
46
|
|
41
47
|
def to_h
|
42
48
|
{
|
43
|
-
'changed_attributes'
|
44
|
-
'changed_associations'
|
45
|
-
'new'
|
46
|
-
'
|
47
|
-
'
|
49
|
+
'changed_attributes' => changed_attributes.dup,
|
50
|
+
'changed_associations' => changed_associations.dup,
|
51
|
+
'new' => new?,
|
52
|
+
'changed_nested_children' => changed_nested_children?,
|
53
|
+
'changed_referenced_children' => changed_referenced_children?,
|
54
|
+
'deleted' => deleted?,
|
48
55
|
}
|
49
56
|
end
|
50
57
|
|
@@ -52,7 +59,8 @@ class ViewModel::Changes
|
|
52
59
|
return false unless other.is_a?(ViewModel::Changes)
|
53
60
|
|
54
61
|
self.new? == other.new? &&
|
55
|
-
self.
|
62
|
+
self.changed_nested_children? == other.changed_nested_children? &&
|
63
|
+
self.changed_referenced_children? == other.changed_referenced_children? &&
|
56
64
|
self.deleted? == other.deleted? &&
|
57
65
|
self.changed_attributes.contains_exactly?(other.changed_attributes) &&
|
58
66
|
self.changed_associations.contains_exactly?(other.changed_associations)
|
data/lib/view_model/config.rb
CHANGED
@@ -10,7 +10,7 @@ ViewModel::Config = Value.new(
|
|
10
10
|
|
11
11
|
class ViewModel::Config
|
12
12
|
def self.configure!(&block)
|
13
|
-
if
|
13
|
+
if configured?
|
14
14
|
raise ArgumentError.new('ViewModel library already configured')
|
15
15
|
end
|
16
16
|
|
@@ -18,8 +18,12 @@ class ViewModel::Config
|
|
18
18
|
@instance = builder.build!(&block)
|
19
19
|
end
|
20
20
|
|
21
|
+
def self.configured?
|
22
|
+
instance_variable_defined?(:@instance)
|
23
|
+
end
|
24
|
+
|
21
25
|
def self._option(opt)
|
22
|
-
configure! unless
|
26
|
+
configure! unless configured?
|
23
27
|
@instance[opt]
|
24
28
|
end
|
25
29
|
|
@@ -226,6 +226,19 @@ class ViewModel
|
|
226
226
|
end
|
227
227
|
end
|
228
228
|
|
229
|
+
class DuplicateOwner < InvalidRequest
|
230
|
+
attr_reader :association_name
|
231
|
+
|
232
|
+
def initialize(association_name, parents)
|
233
|
+
@association_name = association_name
|
234
|
+
super(parents)
|
235
|
+
end
|
236
|
+
|
237
|
+
def detail
|
238
|
+
"Multiple parents attempted to claim the same owned '#{association_name}' reference: " + nodes.map(&:to_s).join(", ")
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
229
242
|
class ParentNotFound < NotFound
|
230
243
|
def detail
|
231
244
|
"Could not resolve release from previous parent for the following owned viewmodel(s): " +
|
@@ -251,6 +264,24 @@ class ViewModel
|
|
251
264
|
end
|
252
265
|
end
|
253
266
|
|
267
|
+
class ReadOnlyAssociation < DeserializationError
|
268
|
+
status 400
|
269
|
+
attr_reader :association
|
270
|
+
|
271
|
+
def initialize(association, node)
|
272
|
+
@association = association
|
273
|
+
super([node])
|
274
|
+
end
|
275
|
+
|
276
|
+
def detail
|
277
|
+
"Cannot edit read only association '#{association}'"
|
278
|
+
end
|
279
|
+
|
280
|
+
def meta
|
281
|
+
super.merge(association: association)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
254
285
|
class ReadOnlyType < DeserializationError
|
255
286
|
status 400
|
256
287
|
detail "Deserialization not defined for view type"
|
@@ -1,16 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'view_model/traversal_context'
|
2
4
|
|
3
5
|
class ViewModel::DeserializeContext < ViewModel::TraversalContext
|
4
6
|
class SharedContext < ViewModel::TraversalContext::SharedContext
|
5
|
-
# During deserialization, collects a tree of viewmodel association names that
|
6
|
-
# were updated. Used to ensure that updated associations are always included
|
7
|
-
# in response serialization after deserialization, even if hidden by default.
|
8
|
-
attr_accessor :updated_associations
|
9
7
|
end
|
10
8
|
|
11
9
|
def self.shared_context_class
|
12
10
|
SharedContext
|
13
11
|
end
|
14
|
-
|
15
|
-
delegate :updated_associations, :"updated_associations=", to: :shared_context
|
16
12
|
end
|
@@ -25,10 +25,11 @@ class ViewModel::ErrorView < ViewModel::Record
|
|
25
25
|
|
26
26
|
# Ruby exceptions should never be serialized in production
|
27
27
|
def serialize_exception(json, serialize_context:)
|
28
|
-
if ViewModel::Config.show_cause_in_error_view
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
28
|
+
super if ViewModel::Config.show_cause_in_error_view
|
29
|
+
end
|
30
|
+
|
31
|
+
# Only serialize causes for aggregation errors.
|
32
|
+
def serialize_causes(*)
|
33
|
+
super if model.aggregation?
|
33
34
|
end
|
34
35
|
end
|
@@ -3,23 +3,28 @@
|
|
3
3
|
class ViewModel::Record::AttributeData
|
4
4
|
attr_reader :name, :model_attr_name, :attribute_viewmodel, :attribute_serializer
|
5
5
|
|
6
|
-
def initialize(name
|
6
|
+
def initialize(name:,
|
7
|
+
model_attr_name:,
|
8
|
+
attribute_viewmodel:,
|
9
|
+
attribute_serializer:,
|
10
|
+
array:,
|
11
|
+
read_only:,
|
12
|
+
write_once:)
|
7
13
|
@name = name
|
8
14
|
@model_attr_name = model_attr_name
|
9
15
|
@attribute_viewmodel = attribute_viewmodel
|
10
16
|
@attribute_serializer = attribute_serializer
|
11
17
|
@array = array
|
12
|
-
@optional = optional
|
13
18
|
@read_only = read_only
|
14
19
|
@write_once = write_once
|
15
20
|
end
|
16
21
|
|
17
|
-
def
|
18
|
-
|
22
|
+
def association?
|
23
|
+
false
|
19
24
|
end
|
20
25
|
|
21
|
-
def
|
22
|
-
@
|
26
|
+
def array?
|
27
|
+
@array
|
23
28
|
end
|
24
29
|
|
25
30
|
def read_only?
|