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
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_to_hash, #to_hash, #to_json, #to_reference, #view_name
Constructor Details
#initialize(model) ⇒ Record
Returns a new instance of Record.
173
174
175
176
177
178
179
180
181
182
183
184
185
186
|
# File 'lib/view_model/record.rb', line 173
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.
171
172
173
|
# File 'lib/view_model/record.rb', line 171
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.
171
172
173
|
# File 'lib/view_model/record.rb', line 171
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
|
# 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, 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
end
|
.deserialize_members_from_view(viewmodel, view_hash, references:, deserialize_context:) ⇒ Object
107
108
109
110
111
112
113
114
115
116
117
|
# File 'lib/view_model/record.rb', line 107
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
123
124
125
|
# File 'lib/view_model/record.rb', line 123
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
140
141
142
|
# File 'lib/view_model/record.rb', line 140
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.
130
131
132
133
134
135
136
137
138
|
# File 'lib/view_model/record.rb', line 130
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
119
120
121
|
# File 'lib/view_model/record.rb', line 119
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
end
|
Instance Method Details
#==(other) ⇒ Object
Also known as:
eql?
278
279
280
|
# File 'lib/view_model/record.rb', line 278
def ==(other)
self.class == other.class && self.model == other.model
end
|
#attribute_changed!(attr_name) ⇒ Object
242
243
244
|
# File 'lib/view_model/record.rb', line 242
def attribute_changed!(attr_name)
@changed_attributes << attr_name.to_s
end
|
#changed_nested_children? ⇒ Boolean
206
207
208
|
# File 'lib/view_model/record.rb', line 206
def changed_nested_children?
@changed_nested_children
end
|
#changed_referenced_children? ⇒ Boolean
210
211
212
|
# File 'lib/view_model/record.rb', line 210
def changed_referenced_children?
@changed_referenced_children
end
|
#changes ⇒ Object
254
255
256
257
258
259
260
261
|
# File 'lib/view_model/record.rb', line 254
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
263
264
265
266
267
268
269
270
|
# File 'lib/view_model/record.rb', line 263
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.
274
275
276
|
# File 'lib/view_model/record.rb', line 274
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.
190
191
192
193
194
195
196
|
# File 'lib/view_model/record.rb', line 190
def id
if stable_id?
model.id
else
model.object_id
end
end
|
#model_is_new! ⇒ Object
238
239
240
|
# File 'lib/view_model/record.rb', line 238
def model_is_new!
@new_model = true
end
|
#nested_children_changed! ⇒ Object
246
247
248
|
# File 'lib/view_model/record.rb', line 246
def nested_children_changed!
@changed_nested_children = true
end
|
#new_model? ⇒ Boolean
202
203
204
|
# File 'lib/view_model/record.rb', line 202
def new_model?
@new_model
end
|
#referenced_children_changed! ⇒ Object
250
251
252
|
# File 'lib/view_model/record.rb', line 250
def referenced_children_changed!
@changed_referenced_children = true
end
|
#serialize_members(json, serialize_context:) ⇒ Object
222
223
224
225
226
|
# File 'lib/view_model/record.rb', line 222
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
214
215
216
217
218
219
220
|
# File 'lib/view_model/record.rb', line 214
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
198
199
200
|
# File 'lib/view_model/record.rb', line 198
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.