iknow_view_models 3.4.2 → 3.5.1
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.
- checksums.yaml +4 -4
- data/iknow_view_models.gemspec +2 -2
- data/lib/iknow_view_models/version.rb +1 -1
- data/lib/view_model/active_record/association_data.rb +3 -1
- data/lib/view_model/active_record/association_manipulation.rb +186 -49
- data/lib/view_model/active_record/cloner.rb +13 -12
- data/lib/view_model/active_record/collection_nested_controller.rb +5 -2
- data/lib/view_model/active_record/controller_base.rb +45 -6
- data/lib/view_model/active_record/nested_controller_base.rb +125 -13
- data/lib/view_model/active_record/singular_nested_controller.rb +5 -2
- data/lib/view_model/active_record.rb +46 -5
- data/lib/view_model/callbacks.rb +1 -1
- data/lib/view_model/controller.rb +24 -2
- data/lib/view_model/migrator.rb +26 -0
- data/lib/view_model/record.rb +1 -1
- data/lib/view_model/schemas.rb +44 -0
- data/lib/view_model.rb +12 -0
- data/test/helpers/arvm_test_utilities.rb +65 -0
- data/test/helpers/controller_test_helpers.rb +65 -34
- data/test/unit/view_model/active_record/controller_nested_test.rb +599 -0
- data/test/unit/view_model/active_record/controller_test.rb +6 -362
- data/test/unit/view_model/active_record/has_many_test.rb +24 -7
- data/test/unit/view_model/active_record/has_many_through_test.rb +28 -12
- data/test/unit/view_model/active_record/migration_test.rb +29 -0
- data/test/unit/view_model/traversal_context_test.rb +15 -1
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b538a2d547114ecfbb76bfc790daea11b2185abdbad54d5da82723bb7fa1bef
|
4
|
+
data.tar.gz: 97b7e6d1d66cae14d56fe53931d06a4079e5c9b4b61e60bf56027d539fd41224
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f70185a7ba12a222eee6a1549f724c08754f7213a1c1594d5b2ea0060baac04fce839cb480615e02741adcc82cd2d910e8d82e519127da15efd6e14dbdf3b0b
|
7
|
+
data.tar.gz: 82a343d49bf61a6e351653c8a3e3855344b5dd017acc7722cc526e41af8915d6aedb028995f859354df23de82f55b2c342b2fa1a572af76b1690345fb120ae02
|
data/iknow_view_models.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.name = 'iknow_view_models'
|
9
9
|
spec.version = IknowViewModels::VERSION
|
10
10
|
spec.authors = ['iKnow Team']
|
11
|
-
spec.email = ['
|
11
|
+
spec.email = ['systems@iknow.jp']
|
12
12
|
spec.summary = 'ViewModels provide a means of encapsulating a collection of related data and specifying its JSON serialization.'
|
13
13
|
spec.description = ''
|
14
14
|
spec.homepage = 'https://github.com/iknow/cerego_view_models'
|
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.add_dependency 'activesupport', '>= 5.0'
|
26
26
|
|
27
27
|
spec.add_dependency 'acts_as_manual_list'
|
28
|
-
spec.add_dependency 'deep_preloader', '>= 1.0.
|
28
|
+
spec.add_dependency 'deep_preloader', '>= 1.0.2'
|
29
29
|
spec.add_dependency 'iknow_cache'
|
30
30
|
spec.add_dependency 'iknow_params', '~> 2.2.0'
|
31
31
|
spec.add_dependency 'keyword_builder'
|
@@ -127,7 +127,9 @@ class ViewModel::ActiveRecord::AssociationData
|
|
127
127
|
end
|
128
128
|
|
129
129
|
def polymorphic?
|
130
|
-
|
130
|
+
# STI polymorphism isn't shown on the association reflection, so in that
|
131
|
+
# case we have to infer it by having multiple target viewmodel types.
|
132
|
+
target_reflection.polymorphic? || viewmodel_classes.size > 1
|
131
133
|
end
|
132
134
|
|
133
135
|
# The side of the immediate association that holds the pointer.
|
@@ -3,6 +3,8 @@
|
|
3
3
|
# Mix-in for VM::ActiveRecord providing direct manipulation of
|
4
4
|
# directly-associated entities. Avoids loading entire collections.
|
5
5
|
module ViewModel::ActiveRecord::AssociationManipulation
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
6
8
|
def load_associated(association_name, scope: nil, eager_include: true, serialize_context: self.class.new_serialize_context)
|
7
9
|
association_data = self.class._association_data(association_name)
|
8
10
|
direct_reflection = association_data.direct_reflection
|
@@ -16,7 +18,7 @@ module ViewModel::ActiveRecord::AssociationManipulation
|
|
16
18
|
associated_viewmodel = association_data.viewmodel_class
|
17
19
|
direct_viewmodel = association_data.direct_viewmodel
|
18
20
|
else
|
19
|
-
raise ArgumentError.new('Polymorphic STI relationships not supported yet') if association_data.
|
21
|
+
raise ArgumentError.new('Polymorphic STI relationships not supported yet') if association_data.polymorphic?
|
20
22
|
|
21
23
|
associated_viewmodel = association.klass.try { |k| association_data.viewmodel_class_for_model!(k) }
|
22
24
|
direct_viewmodel = associated_viewmodel
|
@@ -49,49 +51,63 @@ module ViewModel::ActiveRecord::AssociationManipulation
|
|
49
51
|
end
|
50
52
|
end
|
51
53
|
|
52
|
-
# Replace the current member(s) of an association with the provided
|
54
|
+
# Replace the current member(s) of an association with the provided
|
55
|
+
# hash(es). Only mentioned member(s) will be returned.
|
56
|
+
#
|
57
|
+
# This interface deals with associations directly where reasonable,
|
58
|
+
# with the notable exception of referenced+shared associations. That
|
59
|
+
# is to say, that owned associations should be presented in the form
|
60
|
+
# of direct update hashes, regardless of their
|
61
|
+
# referencing. Reference and shared associations are excluded to
|
62
|
+
# ensure that the update hash for a shared entity is unique, and
|
63
|
+
# that edits may only be specified once.
|
53
64
|
def replace_associated(association_name, update_hash, references: {}, deserialize_context: self.class.new_deserialize_context)
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
action_type_name = action[ViewModel::ActiveRecord::TYPE_ATTRIBUTE]
|
65
|
-
if action_type_name == ViewModel::ActiveRecord::FunctionalUpdate::Remove::NAME
|
66
|
-
# Remove actions are always type/id refs; others need to be translated to proper refs
|
67
|
-
next
|
68
|
-
end
|
65
|
+
_updated_parent, changed_children =
|
66
|
+
self.class.replace_associated_bulk(
|
67
|
+
association_name,
|
68
|
+
{ self.id => update_hash },
|
69
|
+
references: references,
|
70
|
+
deserialize_context: deserialize_context
|
71
|
+
).first
|
72
|
+
|
73
|
+
changed_children
|
74
|
+
end
|
69
75
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
76
|
+
class_methods do
|
77
|
+
# Replace the current member(s) of an association with the provided
|
78
|
+
# hash(es) for many viewmodels. Only mentioned members will be returned.
|
79
|
+
#
|
80
|
+
# This is an interim implementation that requires loading the contents of
|
81
|
+
# all collections into memory and filtering for the mentioned entities,
|
82
|
+
# even for functional updates. This is in contrast to append_associated,
|
83
|
+
# which only operates on the new entities.
|
84
|
+
def replace_associated_bulk(association_name, updates_by_parent_id, references:, deserialize_context: self.class.new_deserialize_context)
|
85
|
+
association_data = _association_data(association_name)
|
86
|
+
|
87
|
+
touched_ids = updates_by_parent_id.each_with_object({}) do |(parent_id, update_hash), acc|
|
88
|
+
acc[parent_id] =
|
89
|
+
mentioned_children(
|
90
|
+
update_hash,
|
91
|
+
references: references,
|
92
|
+
association_data: association_data,
|
93
|
+
).to_set
|
83
94
|
end
|
84
|
-
end
|
85
95
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
96
|
+
root_update_hashes = updates_by_parent_id.map do |parent_id, update_hash|
|
97
|
+
{
|
98
|
+
ViewModel::ID_ATTRIBUTE => parent_id,
|
99
|
+
ViewModel::TYPE_ATTRIBUTE => view_name,
|
100
|
+
association_name.to_s => update_hash,
|
101
|
+
}
|
102
|
+
end
|
91
103
|
|
92
|
-
|
104
|
+
root_update_viewmodels = deserialize_from_view(
|
105
|
+
root_update_hashes, references: references, deserialize_context: deserialize_context)
|
93
106
|
|
94
|
-
|
107
|
+
root_update_viewmodels.each_with_object({}) do |updated, acc|
|
108
|
+
acc[updated] = updated._read_association_touched(association_name, touched_ids: touched_ids.fetch(updated.id))
|
109
|
+
end
|
110
|
+
end
|
95
111
|
end
|
96
112
|
|
97
113
|
# Create or update members of a associated collection. For an ordered
|
@@ -118,7 +134,7 @@ module ViewModel::ActiveRecord::AssociationManipulation
|
|
118
134
|
direct_viewmodel_class = association_data.direct_viewmodel
|
119
135
|
root_update_data, referenced_update_data = construct_indirect_append_updates(association_data, subtree_hashes, references)
|
120
136
|
else
|
121
|
-
raise ArgumentError.new('Polymorphic STI relationships not supported yet') if association_data.
|
137
|
+
raise ArgumentError.new('Polymorphic STI relationships not supported yet') if association_data.polymorphic?
|
122
138
|
|
123
139
|
direct_viewmodel_class = association_data.viewmodel_class
|
124
140
|
root_update_data, referenced_update_data = construct_direct_append_updates(association_data, subtree_hashes, references)
|
@@ -242,7 +258,7 @@ module ViewModel::ActiveRecord::AssociationManipulation
|
|
242
258
|
direct_viewmodel = association_data.direct_viewmodel
|
243
259
|
association_scope = association_scope.where(association_data.indirect_reflection.foreign_key => associated_id)
|
244
260
|
else
|
245
|
-
raise ArgumentError.new('Polymorphic STI relationships not supported yet') if association_data.
|
261
|
+
raise ArgumentError.new('Polymorphic STI relationships not supported yet') if association_data.polymorphic?
|
246
262
|
|
247
263
|
# viewmodel type for current association: nil in case of empty polymorphic association
|
248
264
|
direct_viewmodel = association.klass.try { |k| association_data.viewmodel_class_for_model!(k) }
|
@@ -313,7 +329,10 @@ module ViewModel::ActiveRecord::AssociationManipulation
|
|
313
329
|
indirect_update_data, referenced_update_data = ViewModel::ActiveRecord::UpdateData.parse_hashes(subtree_hashes, references)
|
314
330
|
|
315
331
|
# Convert associated update data to references
|
316
|
-
indirect_references =
|
332
|
+
indirect_references =
|
333
|
+
self.class.convert_updates_to_references(
|
334
|
+
indirect_update_data, key: 'indirect_append')
|
335
|
+
|
317
336
|
referenced_update_data.merge!(indirect_references)
|
318
337
|
|
319
338
|
# Find any existing models for the direct association: need to re-use any
|
@@ -352,12 +371,6 @@ module ViewModel::ActiveRecord::AssociationManipulation
|
|
352
371
|
return direct_update_data, referenced_update_data
|
353
372
|
end
|
354
373
|
|
355
|
-
def convert_updates_to_references(indirect_update_data, key:)
|
356
|
-
indirect_update_data.each.with_index.with_object({}) do |(update, i), indirect_references|
|
357
|
-
indirect_references["__#{key}_ref_#{i}"] = update
|
358
|
-
end
|
359
|
-
end
|
360
|
-
|
361
374
|
# TODO: this functionality could reasonably be extracted into `acts_as_manual_list`.
|
362
375
|
def select_append_positions(association_data, position_attr, append_count, before:, after:)
|
363
376
|
direct_reflection = association_data.direct_reflection
|
@@ -395,10 +408,134 @@ module ViewModel::ActiveRecord::AssociationManipulation
|
|
395
408
|
def check_association_type!(association_data, type)
|
396
409
|
if type && !association_data.accepts?(type)
|
397
410
|
raise ViewModel::SerializationError.new(
|
398
|
-
"Type error: association '#{
|
411
|
+
"Type error: association '#{association_data.association_name}' can't refer to viewmodel #{type.view_name}")
|
399
412
|
elsif association_data.polymorphic? && !type
|
400
413
|
raise ViewModel::SerializationError.new(
|
401
|
-
"Need to specify target viewmodel type for polymorphic association '#{
|
414
|
+
"Need to specify target viewmodel type for polymorphic association '#{association_data.association_name}'")
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
class_methods do
|
419
|
+
def convert_updates_to_references(indirect_update_data, key:)
|
420
|
+
indirect_update_data.each.with_index.with_object({}) do |(update, i), indirect_references|
|
421
|
+
indirect_references["__#{key}_ref_#{i}"] = update
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
def add_reference_indirection(update_hash, association_data:, references:, key:)
|
426
|
+
raise ArgumentError.new('Not a referenced association') unless association_data.referenced?
|
427
|
+
|
428
|
+
is_fupdate =
|
429
|
+
association_data.collection? &&
|
430
|
+
update_hash.is_a?(Hash) &&
|
431
|
+
update_hash[ViewModel::ActiveRecord::TYPE_ATTRIBUTE] == ViewModel::ActiveRecord::FUNCTIONAL_UPDATE_TYPE
|
432
|
+
|
433
|
+
if is_fupdate
|
434
|
+
update_hash[ViewModel::ActiveRecord::ACTIONS_ATTRIBUTE].each_with_index do |action, i|
|
435
|
+
action_type_name = action[ViewModel::ActiveRecord::TYPE_ATTRIBUTE]
|
436
|
+
if action_type_name == ViewModel::ActiveRecord::FunctionalUpdate::Remove::NAME
|
437
|
+
# Remove actions are always type/id refs; others need to be translated to proper refs
|
438
|
+
next
|
439
|
+
end
|
440
|
+
|
441
|
+
association_references = convert_updates_to_references(
|
442
|
+
action[ViewModel::ActiveRecord::VALUES_ATTRIBUTE],
|
443
|
+
key: "#{key}_#{action_type_name}_#{i}")
|
444
|
+
references.merge!(association_references)
|
445
|
+
action[ViewModel::ActiveRecord::VALUES_ATTRIBUTE] =
|
446
|
+
association_references.each_key.map { |ref| { ViewModel::REFERENCE_ATTRIBUTE => ref } }
|
447
|
+
end
|
448
|
+
|
449
|
+
update_hash
|
450
|
+
else
|
451
|
+
ViewModel::Utils.wrap_one_or_many(update_hash) do |sh|
|
452
|
+
association_references = convert_updates_to_references(sh, key: "#{key}_replace")
|
453
|
+
references.merge!(association_references)
|
454
|
+
association_references.each_key.map { |ref| { ViewModel::REFERENCE_ATTRIBUTE => ref } }
|
455
|
+
end
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
# Traverses literals and fupdates to return referenced children.
|
460
|
+
#
|
461
|
+
# Runs before the main parser, so must be defensive
|
462
|
+
def each_child_hash(assoc_update, association_data:)
|
463
|
+
return enum_for(__method__, assoc_update, association_data: association_data) unless block_given?
|
464
|
+
|
465
|
+
is_fupdate =
|
466
|
+
association_data.collection? &&
|
467
|
+
assoc_update.is_a?(Hash) &&
|
468
|
+
assoc_update[ViewModel::ActiveRecord::TYPE_ATTRIBUTE] == ViewModel::ActiveRecord::FUNCTIONAL_UPDATE_TYPE
|
469
|
+
|
470
|
+
if is_fupdate
|
471
|
+
assoc_update.fetch(ViewModel::ActiveRecord::ACTIONS_ATTRIBUTE).each do |action|
|
472
|
+
action_type_name = action[ViewModel::ActiveRecord::TYPE_ATTRIBUTE]
|
473
|
+
if action_type_name.nil?
|
474
|
+
raise ViewModel::DeserializationError::InvalidSyntax.new(
|
475
|
+
"Functional update missing '#{ViewModel::ActiveRecord::TYPE_ATTRIBUTE}'"
|
476
|
+
)
|
477
|
+
end
|
478
|
+
|
479
|
+
if action_type_name == ViewModel::ActiveRecord::FunctionalUpdate::Remove::NAME
|
480
|
+
# Remove actions are not considered children of the action.
|
481
|
+
next
|
482
|
+
end
|
483
|
+
|
484
|
+
values = action.fetch(ViewModel::ActiveRecord::VALUES_ATTRIBUTE) {
|
485
|
+
raise ViewModel::DeserializationError::InvalidSyntax.new(
|
486
|
+
"Functional update missing '#{ViewModel::ActiveRecord::VALUES_ATTRIBUTE}'"
|
487
|
+
)
|
488
|
+
}
|
489
|
+
values.each { |x| yield x }
|
490
|
+
end
|
491
|
+
else
|
492
|
+
ViewModel::Utils.wrap_one_or_many(assoc_update) do |assoc_updates|
|
493
|
+
assoc_updates.each { |u| yield u }
|
494
|
+
end
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
# Collects the ids of children that are mentioned in the update data.
|
499
|
+
#
|
500
|
+
# Runs before the main parser, so must be defensive.
|
501
|
+
def mentioned_children(assoc_update, references:, association_data:)
|
502
|
+
return enum_for(__method__, assoc_update, references: references, association_data: association_data) unless block_given?
|
503
|
+
|
504
|
+
each_child_hash(assoc_update, association_data: association_data).each do |child_hash|
|
505
|
+
unless child_hash.is_a?(Hash)
|
506
|
+
raise ViewModel::DeserializationError::InvalidSyntax.new(
|
507
|
+
"Expected update hash, received: #{child_hash}"
|
508
|
+
)
|
509
|
+
end
|
510
|
+
|
511
|
+
if association_data.referenced?
|
512
|
+
ref_handle = child_hash.fetch(ViewModel::REFERENCE_ATTRIBUTE) {
|
513
|
+
raise ViewModel::DeserializationError::InvalidSyntax.new(
|
514
|
+
"Reference hash missing '#{ViewModel::REFERENCE_ATTRIBUTE}'"
|
515
|
+
)
|
516
|
+
}
|
517
|
+
|
518
|
+
ref_update_hash = references.fetch(ref_handle) {
|
519
|
+
raise ViewModel::DeserializationError::InvalidSyntax.new(
|
520
|
+
"Reference '#{ref_handle}' does not exist in references"
|
521
|
+
)
|
522
|
+
}
|
523
|
+
|
524
|
+
unless ref_update_hash.is_a?(Hash)
|
525
|
+
raise ViewModel::DeserializationError::InvalidSyntax.new(
|
526
|
+
"Expected update hash, received: #{child_hash}"
|
527
|
+
)
|
528
|
+
end
|
529
|
+
|
530
|
+
if (id = ref_update_hash[ViewModel::ID_ATTRIBUTE])
|
531
|
+
yield id
|
532
|
+
end
|
533
|
+
else
|
534
|
+
if (id = child_hash[ViewModel::ID_ATTRIBUTE])
|
535
|
+
yield id
|
536
|
+
end
|
537
|
+
end
|
538
|
+
end
|
402
539
|
end
|
403
540
|
end
|
404
541
|
end
|
@@ -52,22 +52,23 @@ class ViewModel::ActiveRecord::Cloner
|
|
52
52
|
new_associated = associated
|
53
53
|
else
|
54
54
|
# Otherwise descend into the child, and attach the result
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
55
|
+
build_vm = ->(model) do
|
56
|
+
vm_class =
|
57
|
+
if association_data.through?
|
58
|
+
# descend into the synthetic join table viewmodel
|
59
|
+
association_data.direct_viewmodel
|
60
|
+
else
|
61
|
+
association_data.viewmodel_class_for_model!(model.class)
|
62
|
+
end
|
63
|
+
|
64
|
+
vm_class.new(model)
|
65
|
+
end
|
65
66
|
|
66
67
|
new_associated =
|
67
68
|
if ViewModel::Utils.array_like?(associated)
|
68
|
-
associated.map { |m| clone(
|
69
|
+
associated.map { |m| clone(build_vm.(m)) }.compact
|
69
70
|
else
|
70
|
-
clone(
|
71
|
+
clone(build_vm.(associated))
|
71
72
|
end
|
72
73
|
end
|
73
74
|
end
|
@@ -3,8 +3,7 @@
|
|
3
3
|
require 'view_model/active_record/nested_controller_base'
|
4
4
|
|
5
5
|
# Controller mixin for accessing a root ViewModel which can be accessed in a
|
6
|
-
# collection by a parent model.
|
7
|
-
# :children` on the viewmodel controller
|
6
|
+
# collection by a parent model.
|
8
7
|
|
9
8
|
# Contributes the following routes:
|
10
9
|
# PUT /parents/:parent_id/children #append -- deserialize (possibly existing) children and append to collection
|
@@ -32,6 +31,10 @@ module ViewModel::ActiveRecord::CollectionNestedController
|
|
32
31
|
write_association(serialize_context: serialize_context, deserialize_context: deserialize_context, lock_owner: lock_owner, &block)
|
33
32
|
end
|
34
33
|
|
34
|
+
def replace_bulk(serialize_context: new_serialize_context, deserialize_context: new_deserialize_context, &block)
|
35
|
+
write_association_bulk(serialize_context: serialize_context, deserialize_context: deserialize_context, &block)
|
36
|
+
end
|
37
|
+
|
35
38
|
def disassociate_all(serialize_context: new_serialize_context, deserialize_context: new_deserialize_context, lock_owner: nil)
|
36
39
|
destroy_association(true, serialize_context: serialize_context, deserialize_context: deserialize_context, lock_owner: lock_owner)
|
37
40
|
end
|
@@ -102,15 +102,35 @@ module ActionDispatch
|
|
102
102
|
def arvm_resources(resource_name, options = {}, &block)
|
103
103
|
except = options.delete(:except) { [] }
|
104
104
|
add_shallow_routes = options.delete(:add_shallow_routes) { true }
|
105
|
+
defaults = options.delete(:defaults) { {} }
|
105
106
|
|
106
107
|
nested = shallow_nesting_depth > 0
|
107
108
|
|
109
|
+
association_name = options.delete(:association_name) { resource_name.to_s }
|
110
|
+
owner_viewmodel = options.delete(:owner_viewmodel) do
|
111
|
+
if nested
|
112
|
+
parent_resource.name.to_s.singularize.camelize
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
defaults = {
|
117
|
+
association_name: association_name,
|
118
|
+
owner_viewmodel: owner_viewmodel,
|
119
|
+
}.merge(defaults)
|
120
|
+
|
108
121
|
only_routes = []
|
109
122
|
only_routes += [:create] unless nested
|
110
123
|
only_routes += [:show, :destroy] if add_shallow_routes
|
111
124
|
only_routes -= except
|
112
125
|
|
113
|
-
|
126
|
+
# Bulk replace
|
127
|
+
if nested && !except.include?(:replace_bulk)
|
128
|
+
collection do
|
129
|
+
post resource_name, controller: resource_name, action: :replace_bulk, as: nil, defaults: defaults
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
resources resource_name, shallow: true, only: only_routes, defaults: defaults, **options do
|
114
134
|
instance_eval(&block) if block_given?
|
115
135
|
|
116
136
|
if nested
|
@@ -149,16 +169,35 @@ module ActionDispatch
|
|
149
169
|
def arvm_resource(resource_name, options = {}, &block)
|
150
170
|
except = options.delete(:except) { [] }
|
151
171
|
add_shallow_routes = options.delete(:add_shallow_routes) { true }
|
172
|
+
defaults = options.delete(:defaults) { {} }
|
152
173
|
|
153
174
|
only_routes = []
|
154
|
-
|
155
|
-
|
156
|
-
|
175
|
+
nested = shallow_nesting_depth > 0
|
176
|
+
|
177
|
+
association_name = options.delete(:association_name) { resource_name.to_s }
|
178
|
+
owner_viewmodel = options.delete(:owner_viewmodel) do
|
179
|
+
if nested
|
180
|
+
parent_resource.name.to_s.singularize.camelize
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
defaults = {
|
185
|
+
association_name: association_name,
|
186
|
+
owner_viewmodel: owner_viewmodel,
|
187
|
+
}.merge(defaults)
|
188
|
+
|
189
|
+
if nested && !except.include?(:create_associated_bulk)
|
190
|
+
collection do
|
191
|
+
post resource_name, controller: resource_name, action: :create_associated_bulk, as: nil, defaults: defaults
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
resource resource_name, shallow: true, only: only_routes, defaults: defaults, **options do
|
157
196
|
instance_eval(&block) if block_given?
|
158
197
|
|
159
198
|
name_route = { as: '' } # Only one route may take the name
|
160
199
|
|
161
|
-
if
|
200
|
+
if nested
|
162
201
|
post('', action: :create_associated, **name_route.extract!(:as)) unless except.include?(:create)
|
163
202
|
get('', action: :show_associated, **name_route.extract!(:as)) unless except.include?(:show)
|
164
203
|
delete('', action: :destroy_associated, **name_route.extract!(:as)) unless except.include?(:destroy)
|
@@ -170,7 +209,7 @@ module ActionDispatch
|
|
170
209
|
end
|
171
210
|
|
172
211
|
# singularly nested resources provide collection accessors at the top level
|
173
|
-
if
|
212
|
+
if nested && add_shallow_routes
|
174
213
|
resources resource_name.to_s.pluralize, shallow: true, only: [:show, :destroy] - except do
|
175
214
|
shallow_scope do
|
176
215
|
collection do
|