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.
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?