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, 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?, 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.
170
171
172
173
174
175
176
177
178
179
180
181
|
# File 'lib/view_model/record.rb', line 170
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
end
|
Class Attribute Details
._members ⇒ Object
Returns the value of attribute _members.
15
16
17
|
# File 'lib/view_model/record.rb', line 15
def _members
@_members
end
|
.abstract_class ⇒ Object
Returns the value of attribute abstract_class.
16
17
18
|
# File 'lib/view_model/record.rb', line 16
def abstract_class
@abstract_class
end
|
.unregistered ⇒ Object
Returns the value of attribute unregistered.
16
17
18
|
# File 'lib/view_model/record.rb', line 16
def unregistered
@unregistered
end
|
Instance Attribute Details
#changed_attributes ⇒ Object
Returns the value of attribute changed_attributes.
168
169
170
|
# File 'lib/view_model/record.rb', line 168
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.
168
169
170
|
# File 'lib/view_model/record.rb', line 168
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
39
40
41
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
|
# File 'lib/view_model/record.rb', line 39
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
# File 'lib/view_model/record.rb', line 80
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
104
105
106
107
108
109
110
111
112
113
114
|
# File 'lib/view_model/record.rb', line 104
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
120
121
122
|
# File 'lib/view_model/record.rb', line 120
def for_new_model(*model_args)
self.new(model_class.new(*model_args)).tap { |v| v.model_is_new! }
end
|
.inherited(subclass) ⇒ Object
18
19
20
21
22
|
# File 'lib/view_model/record.rb', line 18
def inherited(subclass)
super
subclass.initialize_as_viewmodel_record
ViewModel::Registry.register(subclass)
end
|
.initialize_as_viewmodel_record ⇒ Object
24
25
26
27
28
29
30
31
|
# File 'lib/view_model/record.rb', line 24
def initialize_as_viewmodel_record
@_members = {}
@abstract_class = false
@unregistered = false
@generated_accessor_module = Module.new
include @generated_accessor_module
end
|
.member_names ⇒ Object
137
138
139
|
# File 'lib/view_model/record.rb', line 137
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.
127
128
129
130
131
132
133
134
135
|
# File 'lib/view_model/record.rb', line 127
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
116
117
118
|
# File 'lib/view_model/record.rb', line 116
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
34
35
36
|
# File 'lib/view_model/record.rb', line 34
def should_register?
!abstract_class && !unregistered
end
|
Instance Method Details
#==(other) ⇒ Object
Also known as:
eql?
273
274
275
|
# File 'lib/view_model/record.rb', line 273
def ==(other)
self.class == other.class && self.model == other.model
end
|
#attribute_changed!(attr_name) ⇒ Object
237
238
239
|
# File 'lib/view_model/record.rb', line 237
def attribute_changed!(attr_name)
@changed_attributes << attr_name.to_s
end
|
#changed_nested_children? ⇒ Boolean
201
202
203
|
# File 'lib/view_model/record.rb', line 201
def changed_nested_children?
@changed_nested_children
end
|
#changed_referenced_children? ⇒ Boolean
205
206
207
|
# File 'lib/view_model/record.rb', line 205
def changed_referenced_children?
@changed_referenced_children
end
|
#changes ⇒ Object
249
250
251
252
253
254
255
256
|
# File 'lib/view_model/record.rb', line 249
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
258
259
260
261
262
263
264
265
|
# File 'lib/view_model/record.rb', line 258
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.
269
270
271
|
# File 'lib/view_model/record.rb', line 269
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.
185
186
187
188
189
190
191
|
# File 'lib/view_model/record.rb', line 185
def id
if stable_id?
model.id
else
model.object_id
end
end
|
#model_is_new! ⇒ Object
233
234
235
|
# File 'lib/view_model/record.rb', line 233
def model_is_new!
@new_model = true
end
|
#nested_children_changed! ⇒ Object
241
242
243
|
# File 'lib/view_model/record.rb', line 241
def nested_children_changed!
@changed_nested_children = true
end
|
#new_model? ⇒ Boolean
197
198
199
|
# File 'lib/view_model/record.rb', line 197
def new_model?
@new_model
end
|
#referenced_children_changed! ⇒ Object
245
246
247
|
# File 'lib/view_model/record.rb', line 245
def referenced_children_changed!
@changed_referenced_children = true
end
|
#serialize_members(json, serialize_context:) ⇒ Object
217
218
219
220
221
|
# File 'lib/view_model/record.rb', line 217
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
209
210
211
212
213
214
215
|
# File 'lib/view_model/record.rb', line 209
def serialize_view(json, serialize_context: self.class.new_serialize_context)
json.set!(ViewModel::ID_ATTRIBUTE, model.id) if model.respond_to?(: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
193
194
195
|
# File 'lib/view_model/record.rb', line 193
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.