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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +119 -0
  3. data/.travis.yml +31 -0
  4. data/Appraisals +6 -16
  5. data/gemfiles/{rails_7_0.gemfile → rails_6_0_beta.gemfile} +2 -2
  6. data/iknow_view_models.gemspec +3 -5
  7. data/lib/iknow_view_models/version.rb +1 -1
  8. data/lib/view_model/active_record/association_data.rb +206 -92
  9. data/lib/view_model/active_record/association_manipulation.rb +22 -12
  10. data/lib/view_model/active_record/cache/cacheable_view.rb +3 -13
  11. data/lib/view_model/active_record/cache.rb +2 -2
  12. data/lib/view_model/active_record/cloner.rb +11 -11
  13. data/lib/view_model/active_record/controller.rb +0 -2
  14. data/lib/view_model/active_record/update_context.rb +21 -3
  15. data/lib/view_model/active_record/update_data.rb +43 -45
  16. data/lib/view_model/active_record/update_operation.rb +265 -153
  17. data/lib/view_model/active_record/visitor.rb +9 -6
  18. data/lib/view_model/active_record.rb +94 -74
  19. data/lib/view_model/after_transaction_runner.rb +3 -18
  20. data/lib/view_model/callbacks.rb +2 -2
  21. data/lib/view_model/changes.rb +24 -16
  22. data/lib/view_model/config.rb +6 -2
  23. data/lib/view_model/deserialization_error.rb +31 -0
  24. data/lib/view_model/deserialize_context.rb +2 -6
  25. data/lib/view_model/error_view.rb +6 -5
  26. data/lib/view_model/record/attribute_data.rb +11 -6
  27. data/lib/view_model/record.rb +44 -24
  28. data/lib/view_model/serialize_context.rb +2 -63
  29. data/lib/view_model/test_helpers/arvm_builder.rb +2 -4
  30. data/lib/view_model/traversal_context.rb +2 -2
  31. data/lib/view_model.rb +21 -13
  32. data/shell.nix +1 -1
  33. data/test/helpers/arvm_test_models.rb +4 -12
  34. data/test/helpers/arvm_test_utilities.rb +6 -0
  35. data/test/helpers/controller_test_helpers.rb +6 -6
  36. data/test/helpers/viewmodel_spec_helpers.rb +63 -52
  37. data/test/unit/view_model/access_control_test.rb +88 -37
  38. data/test/unit/view_model/active_record/belongs_to_test.rb +110 -178
  39. data/test/unit/view_model/active_record/cache_test.rb +11 -5
  40. data/test/unit/view_model/active_record/cloner_test.rb +1 -1
  41. data/test/unit/view_model/active_record/controller_test.rb +12 -20
  42. data/test/unit/view_model/active_record/has_many_test.rb +540 -316
  43. data/test/unit/view_model/active_record/has_many_through_poly_test.rb +12 -15
  44. data/test/unit/view_model/active_record/has_many_through_test.rb +15 -58
  45. data/test/unit/view_model/active_record/has_one_test.rb +288 -135
  46. data/test/unit/view_model/active_record/poly_test.rb +0 -1
  47. data/test/unit/view_model/active_record/shared_test.rb +21 -39
  48. data/test/unit/view_model/active_record/version_test.rb +3 -2
  49. data/test/unit/view_model/active_record_test.rb +5 -63
  50. data/test/unit/view_model/callbacks_test.rb +1 -0
  51. data/test/unit/view_model/record_test.rb +0 -32
  52. data/test/unit/view_model/traversal_context_test.rb +13 -12
  53. metadata +15 -25
  54. data/.github/workflows/gem-push.yml +0 -31
  55. data/.github/workflows/test.yml +0 -65
  56. data/gemfiles/rails_6_0.gemfile +0 -9
  57. data/gemfiles/rails_6_1.gemfile +0 -9
  58. 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
- # Specifies an association from the model to be recursively serialized using
67
- # another viewmodel. If the target viewmodel is not specified, attempt to
68
- # locate a default viewmodel based on the name of the associated model.
69
- # TODO document harder
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
- shared: false,
78
- optional: false,
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
- reflection = model_class.reflect_on_association(model_association_name)
94
-
95
- if reflection.nil?
96
- raise ArgumentError.new("Association #{model_association_name} not found in #{model_class.name} model")
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
- viewmodel_spec = viewmodel || viewmodels
107
+ target_viewmodels = Array.wrap(viewmodel || viewmodels)
100
108
 
101
- association_data = AssociationData.new(vm_association_name, reflection, viewmodel_spec, shared, optional, through_to, through_order_attr)
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
- associated = self.public_send(vm_association_name)
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
- def eager_includes(serialize_context: new_serialize_context, include_shared: true)
188
- # When serializing, we need to (recursively) include all intrinsic
189
- # associations and also those optional (incl. shared) associations
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 unless serialize_context.includes_member?(assoc_name, !association_data.optional?)
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.shared?
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, include_shared: include_shared)
204
+ children = viewmodel.eager_includes(serialize_context: child_context, include_referenced: include_referenced)
217
205
 
218
- when !include_shared && association_data.shared?
219
- children = nil # Load up to the shared model, but no further
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, include_shared: include_shared)
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, include_shared: include_shared)
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, include_shared: true)
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 include_shared || !data.shared?
234
+ next unless include_referenced || !data.referenced?
247
235
  data.viewmodel_classes.each do |vm|
248
- vm.dependent_viewmodels(seen, include_shared: include_shared)
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(include_shared: true)
256
- (@deep_schema_version ||= {})[include_shared] ||=
243
+ def deep_schema_version(include_referenced: true)
244
+ (@deep_schema_version ||= {})[include_referenced] ||=
257
245
  begin
258
- dependent_viewmodels(include_shared: include_shared).each_with_object({}) do |view, h|
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 unless serialize_context.includes_member?(member_name, !member_data.optional?)
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: new_model?,
325
- changed_attributes: changed_attributes,
326
- changed_associations: changed_associations,
327
- changed_children: changed_children?)
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
- # Shared associations start a new tree
393
+ # associations to roots start a new tree
374
394
  member_data = self.class._members[member_name.to_s]
375
- if member_data.is_a?(AssociationData) && member_data.shared?
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
- # Rails' internal API
8
- def committed!(*)
9
- after_commit
10
- end
7
+ def committed!; end
11
8
 
12
9
  def before_committed!
13
- before_commit
10
+ after_transaction
14
11
  end
15
12
 
16
13
  def rolledback!(*)
17
- after_rollback
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)
@@ -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
@@ -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, :changed_children, :deleted
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 changed_children? changed_children
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: [], changed_children: false, deleted: false)
15
- @new = new
16
- @changed_attributes = changed_attributes.map(&:to_s)
17
- @changed_associations = changed_associations.map(&:to_s)
18
- @changed_children = changed_children
19
- @deleted = deleted
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 changed_tree?
38
- changed? || changed_children?
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' => changed_attributes.dup,
44
- 'changed_associations' => changed_associations.dup,
45
- 'new' => new?,
46
- 'changed_children' => changed_children?,
47
- 'deleted' => deleted?,
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.changed_children? == other.changed_children? &&
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)
@@ -10,7 +10,7 @@ ViewModel::Config = Value.new(
10
10
 
11
11
  class ViewModel::Config
12
12
  def self.configure!(&block)
13
- if instance_variable_defined?(:@instance)
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 instance_variable_defined?(:@instance)
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
- super
30
- else
31
- json.exception nil
32
- end
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, model_attr_name, attribute_viewmodel, attribute_serializer, array, optional, read_only, write_once)
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 array?
18
- @array
22
+ def association?
23
+ false
19
24
  end
20
25
 
21
- def optional?
22
- @optional
26
+ def array?
27
+ @array
23
28
  end
24
29
 
25
30
  def read_only?