iknow_view_models 2.10.1 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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?
|