Class: ViewModel::Record
Overview
Abstract ViewModel type for serializing a subset of attributes from a record. A record viewmodel wraps a single underlying model, exposing a fixed set of real or calculated attributes.
Defined Under Namespace
Classes: AttributeData
Constant Summary
Constants inherited
from ViewModel
BULK_UPDATES_ATTRIBUTE, BULK_UPDATE_ATTRIBUTE, BULK_UPDATE_TYPE, ID_ATTRIBUTE, MIGRATED_ATTRIBUTE, NEW_ATTRIBUTE, REFERENCE_ATTRIBUTE, TYPE_ATTRIBUTE, VERSION_ATTRIBUTE
Class Attribute Summary collapse
Instance Attribute Summary collapse
Class Method Summary
collapse
-
.attribute(attr, as: nil, read_only: false, write_once: false, using: nil, format: nil, array: false) ⇒ Object
Specifies an attribute from the model to be serialized in this view.
-
.deserialize_from_view(view_hashes, references: {}, deserialize_context: new_deserialize_context) ⇒ Object
-
.deserialize_members_from_view(viewmodel, view_hash, references:, deserialize_context:) ⇒ Object
-
.for_new_model(*model_args) ⇒ Object
-
.inherited(subclass) ⇒ Object
-
.initialize_as_viewmodel_record ⇒ Object
-
.member_names ⇒ Object
-
.model_class ⇒ Object
Returns the AR model class wrapped by this viewmodel.
-
.resolve_viewmodel(_metadata, _view_hash, deserialize_context:) ⇒ Object
-
.should_register? ⇒ Boolean
Should this class be registered in the viewmodel registry.
Instance Method Summary
collapse
Methods inherited from ViewModel
accepts_schema_version?, add_view_alias, attributes, #blame_reference, #context_for_child, deserialize_context_class, eager_includes, encode_json, extract_reference_metadata, extract_reference_only_metadata, extract_viewmodel_metadata, initialize_as_viewmodel, is_update_hash?, lock_attribute_inheritance, new_deserialize_context, new_serialize_context, preload_for_serialization, #preload_for_serialization, root!, root?, schema_hash, schema_versions, #serialize, serialize, serialize_as_reference, serialize_context_class, serialize_from_cache, serialize_to_hash, #serialize_to_hash, #to_json, #to_reference, #view_name
Constructor Details
#initialize(model) ⇒ Record
Returns a new instance of Record.
187
188
189
190
191
192
193
194
195
196
197
198
199
200
|
# File 'lib/view_model/record.rb', line 187
def initialize(model)
unless model.is_a?(model_class)
raise ArgumentError.new("'#{model.inspect}' is not an instance of #{model_class.name}")
end
self.model = model
@new_model = false
@changed_attributes = []
@changed_nested_children = false
@changed_referenced_children = false
super()
end
|
Class Attribute Details
._members ⇒ Object
Returns the value of attribute _members.
18
19
20
|
# File 'lib/view_model/record.rb', line 18
def _members
@_members
end
|
.abstract_class ⇒ Object
Returns the value of attribute abstract_class.
19
20
21
|
# File 'lib/view_model/record.rb', line 19
def abstract_class
@abstract_class
end
|
.unregistered ⇒ Object
Returns the value of attribute unregistered.
19
20
21
|
# File 'lib/view_model/record.rb', line 19
def unregistered
@unregistered
end
|
Instance Attribute Details
#changed_attributes ⇒ Object
Returns the value of attribute changed_attributes.
185
186
187
|
# File 'lib/view_model/record.rb', line 185
def changed_attributes
@changed_attributes
end
|
#model ⇒ Object
All ViewModel::Records have the same underlying ViewModel attribute: the record model they back on to. We want this to be inherited by subclasses, so we override ViewModel’s :_attributes to close over it.
10
11
12
|
# File 'lib/view_model/record.rb', line 10
def model
@model
end
|
#previous_changes ⇒ Object
Returns the value of attribute previous_changes.
185
186
187
|
# File 'lib/view_model/record.rb', line 185
def previous_changes
@previous_changes
end
|
Class Method Details
.attribute(attr, as: nil, read_only: false, write_once: false, using: nil, format: nil, array: false) ⇒ Object
Specifies an attribute from the model to be serialized in this view
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
# File 'lib/view_model/record.rb', line 42
def attribute(attr, as: nil, read_only: false, write_once: false, using: nil, format: nil, array: false)
model_attribute_name = attr.to_s
vm_attribute_name = (as || attr).to_s
if using && format
raise ArgumentError.new("Only one of ':using' and ':format' may be specified")
end
if using && !(using.is_a?(Class) && using < ViewModel)
raise ArgumentError.new("Invalid 'using:' viewmodel: not a viewmodel class")
end
if using && using.root?
raise ArgumentError.new("Invalid 'using:' viewmodel: is a root")
end
if format && !format.respond_to?(:dump) && !format.respond_to?(:load)
raise ArgumentError.new("Invalid 'format:' serializer: must respond to :dump and :load")
end
attr_data = AttributeData.new(name: vm_attribute_name,
model_attr_name: model_attribute_name,
attribute_viewmodel: using,
attribute_serializer: format,
array: array,
read_only: read_only,
write_once: write_once)
_members[vm_attribute_name] = attr_data
@generated_accessor_module.module_eval do
define_method vm_attribute_name do
_get_attribute(attr_data)
end
define_method "serialize_#{vm_attribute_name}" do |json, serialize_context: self.class.new_serialize_context|
_serialize_attribute(attr_data, json, serialize_context: serialize_context)
end
define_method "deserialize_#{vm_attribute_name}" do |value, references: {}, deserialize_context: self.class.new_deserialize_context|
_deserialize_attribute(attr_data, value, references: references, deserialize_context: deserialize_context)
end
end
end
|
.deserialize_from_view(view_hashes, references: {}, deserialize_context: new_deserialize_context) ⇒ Object
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
# File 'lib/view_model/record.rb', line 83
def deserialize_from_view(view_hashes, references: {}, deserialize_context: new_deserialize_context)
ViewModel::Utils.map_one_or_many(view_hashes) do |view_hash|
view_hash = view_hash.dup
metadata = ViewModel.(view_hash)
unless self.view_name == metadata.view_name || self.view_aliases.include?(metadata.view_name)
raise ViewModel::DeserializationError::InvalidViewType.new(
self.view_name,
ViewModel::Reference.new(self, metadata.id))
end
if metadata.schema_version && !self.accepts_schema_version?(metadata.schema_version)
raise ViewModel::DeserializationError::SchemaVersionMismatch.new(
self, metadata.schema_version, ViewModel::Reference.new(self, metadata.id))
end
viewmodel = resolve_viewmodel(metadata, view_hash, deserialize_context: deserialize_context)
deserialize_members_from_view(viewmodel, view_hash, references: references, deserialize_context: deserialize_context)
viewmodel
end
rescue ViewModel::DeserializationError => e
if (new_error = customize_deserialization_error(e))
raise new_error
else
raise
end
end
|
.deserialize_members_from_view(viewmodel, view_hash, references:, deserialize_context:) ⇒ Object
113
114
115
116
117
118
119
120
121
122
123
|
# File 'lib/view_model/record.rb', line 113
def deserialize_members_from_view(viewmodel, view_hash, references:, deserialize_context:)
super do |hook_control|
final_changes = viewmodel.clear_changes!
if final_changes.changed?
deserialize_context.run_callback(ViewModel::Callbacks::Hook::OnChange, viewmodel, changes: final_changes)
end
hook_control.record_changes(final_changes)
end
end
|
.for_new_model(*model_args) ⇒ Object
129
130
131
|
# File 'lib/view_model/record.rb', line 129
def for_new_model(*model_args)
self.new(model_class.new(*model_args)).tap { |v| v.model_is_new! }
end
|
.inherited(subclass) ⇒ Object
21
22
23
24
25
|
# File 'lib/view_model/record.rb', line 21
def inherited(subclass)
super
subclass.initialize_as_viewmodel_record
ViewModel::Registry.register(subclass)
end
|
.initialize_as_viewmodel_record ⇒ Object
27
28
29
30
31
32
33
34
|
# File 'lib/view_model/record.rb', line 27
def initialize_as_viewmodel_record
@_members = {}
@abstract_class = false
@unregistered = false
@generated_accessor_module = Module.new
include @generated_accessor_module
end
|
.member_names ⇒ Object
146
147
148
|
# File 'lib/view_model/record.rb', line 146
def member_names
self._members.keys
end
|
.model_class ⇒ Object
Returns the AR model class wrapped by this viewmodel. If this has not been set via ‘model_class_name=`, attempt to automatically resolve based on the name of this viewmodel.
136
137
138
139
140
141
142
143
144
|
# File 'lib/view_model/record.rb', line 136
def model_class
unless instance_variable_defined?(:@model_class)
self.model_class_name =
ViewModel::Registry.infer_model_class_name(self.view_name)
end
@model_class
end
|
.resolve_viewmodel(_metadata, _view_hash, deserialize_context:) ⇒ Object
125
126
127
|
# File 'lib/view_model/record.rb', line 125
def resolve_viewmodel(_metadata, _view_hash, deserialize_context:)
self.for_new_model
end
|
.should_register? ⇒ Boolean
Should this class be registered in the viewmodel registry
37
38
39
|
# File 'lib/view_model/record.rb', line 37
def should_register?
!abstract_class && !unregistered && !synthetic
end
|
Instance Method Details
#==(other) ⇒ Object
Also known as:
eql?
292
293
294
|
# File 'lib/view_model/record.rb', line 292
def ==(other)
self.class == other.class && self.model == other.model
end
|
#attribute_changed!(attr_name) ⇒ Object
256
257
258
|
# File 'lib/view_model/record.rb', line 256
def attribute_changed!(attr_name)
@changed_attributes << attr_name.to_s
end
|
#changed_nested_children? ⇒ Boolean
220
221
222
|
# File 'lib/view_model/record.rb', line 220
def changed_nested_children?
@changed_nested_children
end
|
#changed_referenced_children? ⇒ Boolean
224
225
226
|
# File 'lib/view_model/record.rb', line 224
def changed_referenced_children?
@changed_referenced_children
end
|
#changes ⇒ Object
268
269
270
271
272
273
274
275
|
# File 'lib/view_model/record.rb', line 268
def changes
ViewModel::Changes.new(
new: new_model?,
changed_attributes: changed_attributes,
changed_nested_children: changed_nested_children?,
changed_referenced_children: changed_referenced_children?,
)
end
|
#clear_changes! ⇒ Object
277
278
279
280
281
282
283
284
|
# File 'lib/view_model/record.rb', line 277
def clear_changes!
@previous_changes = changes
@new_model = false
@changed_attributes = []
@changed_nested_children = false
@changed_referenced_children = false
previous_changes
end
|
#hash ⇒ Object
Use ActiveRecord style identity for viewmodels. This allows serialization to generate a references section by keying on the viewmodel itself.
288
289
290
|
# File 'lib/view_model/record.rb', line 288
def hash
[self.class, self.model].hash
end
|
#id ⇒ Object
VM::Record identity matches the identity of its model. If the model has a stable identity, use it, otherwise fall back to its object_id.
204
205
206
207
208
209
210
|
# File 'lib/view_model/record.rb', line 204
def id
if stable_id?
model.id
else
model.object_id
end
end
|
#model_is_new! ⇒ Object
252
253
254
|
# File 'lib/view_model/record.rb', line 252
def model_is_new!
@new_model = true
end
|
#nested_children_changed! ⇒ Object
260
261
262
|
# File 'lib/view_model/record.rb', line 260
def nested_children_changed!
@changed_nested_children = true
end
|
#new_model? ⇒ Boolean
216
217
218
|
# File 'lib/view_model/record.rb', line 216
def new_model?
@new_model
end
|
#referenced_children_changed! ⇒ Object
264
265
266
|
# File 'lib/view_model/record.rb', line 264
def referenced_children_changed!
@changed_referenced_children = true
end
|
#serialize_members(json, serialize_context:) ⇒ Object
236
237
238
239
240
|
# File 'lib/view_model/record.rb', line 236
def serialize_members(json, serialize_context:)
self.class._members.each do |member_name, _member_data|
self.public_send("serialize_#{member_name}", json, serialize_context: serialize_context)
end
end
|
#serialize_view(json, serialize_context: self.class.new_serialize_context) ⇒ Object
228
229
230
231
232
233
234
|
# File 'lib/view_model/record.rb', line 228
def serialize_view(json, serialize_context: self.class.new_serialize_context)
json.set!(ViewModel::ID_ATTRIBUTE, self.id) if stable_id?
json.set!(ViewModel::TYPE_ATTRIBUTE, self.view_name)
json.set!(ViewModel::VERSION_ATTRIBUTE, self.class.schema_version)
serialize_members(json, serialize_context: serialize_context)
end
|
#stable_id? ⇒ Boolean
212
213
214
|
# File 'lib/view_model/record.rb', line 212
def stable_id?
model.respond_to?(:id)
end
|
#validate! ⇒ Object
Check that the model backing this view is consistent, for example by calling AR validations. Default implementation handles ActiveModel::Validations, may be overridden by subclasses for other types of validation. Must raise DeserializationError::Validation if invalid.