iknow_view_models 3.4.0 → 3.4.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/lib/iknow_view_models/version.rb +1 -1
- data/lib/view_model/active_record.rb +9 -4
- data/lib/view_model/active_record/association_data.rb +15 -0
- data/lib/view_model/active_record/association_manipulation.rb +8 -2
- data/lib/view_model/active_record/update_operation.rb +5 -6
- data/lib/view_model/migrator.rb +9 -2
- data/test/unit/view_model/active_record/belongs_to_test.rb +41 -6
- data/test/unit/view_model/active_record/has_many_test.rb +125 -0
- data/test/unit/view_model/callbacks_test.rb +3 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87a056bba1e37b5593a5ad90365c5b761976529f1c5ab4074b26dd666060d57e
|
4
|
+
data.tar.gz: 5a796e74204b2fb1b55d8f94bf95c347b3b80a65bf3d48036f7b6de06cba8cf2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b7d484cb7ab2d0286bfc99460f029e8a9ccbb5edcc355cee42671e285f7ce2efe45946f2f7130d528b947fdb8e3bd41aa12dc007a049674b35d7a41b6f6b5be
|
7
|
+
data.tar.gz: 7ab16b3a6ec5569704da7a5bdfccde6374d43770c53e1e78ceca961e7043400842c8e84e3614528eee3b8f7c81ccf884f2d0ffc24793ef5cc8328d96cee12152
|
@@ -339,7 +339,7 @@ class ViewModel::ActiveRecord < ViewModel::Record
|
|
339
339
|
# associated here are join-table models; we need to get the far side out
|
340
340
|
join_models = associated
|
341
341
|
|
342
|
-
if association_data.
|
342
|
+
if association_data.ordered?
|
343
343
|
attr = association_data.direct_viewmodel._list_attribute_name
|
344
344
|
join_models = join_models.sort_by { |j| j[attr] }
|
345
345
|
end
|
@@ -350,11 +350,16 @@ class ViewModel::ActiveRecord < ViewModel::Record
|
|
350
350
|
end
|
351
351
|
|
352
352
|
when association_data.collection?
|
353
|
-
|
354
|
-
|
355
|
-
|
353
|
+
associated_viewmodels = associated.map do |x|
|
354
|
+
associated_viewmodel_class = association_data.viewmodel_class_for_model!(x.class)
|
355
|
+
associated_viewmodel_class.new(x)
|
356
|
+
end
|
357
|
+
|
358
|
+
# If any associated type is a list member, they must all be
|
359
|
+
if association_data.ordered?
|
356
360
|
associated_viewmodels.sort_by!(&:_list_attribute)
|
357
361
|
end
|
362
|
+
|
358
363
|
associated_viewmodels
|
359
364
|
|
360
365
|
else
|
@@ -196,6 +196,21 @@ class ViewModel::ActiveRecord::AssociationData
|
|
196
196
|
viewmodel_classes.first
|
197
197
|
end
|
198
198
|
|
199
|
+
def ordered?
|
200
|
+
@ordered ||=
|
201
|
+
if through?
|
202
|
+
direct_viewmodel._list_member?
|
203
|
+
else
|
204
|
+
list_members = viewmodel_classes.map { |c| c._list_member? }.uniq
|
205
|
+
|
206
|
+
if list_members.size > 1
|
207
|
+
raise ArgumentError.new('Inconsistent associated views: mixed list membership')
|
208
|
+
end
|
209
|
+
|
210
|
+
list_members[0]
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
199
214
|
def through?
|
200
215
|
@indirect_association_name.present?
|
201
216
|
end
|
@@ -16,11 +16,13 @@ module ViewModel::ActiveRecord::AssociationManipulation
|
|
16
16
|
associated_viewmodel = association_data.viewmodel_class
|
17
17
|
direct_viewmodel = association_data.direct_viewmodel
|
18
18
|
else
|
19
|
+
raise ArgumentError.new('Polymorphic STI relationships not supported yet') if association_data.viewmodel_classes.size > 1
|
20
|
+
|
19
21
|
associated_viewmodel = association.klass.try { |k| association_data.viewmodel_class_for_model!(k) }
|
20
22
|
direct_viewmodel = associated_viewmodel
|
21
23
|
end
|
22
24
|
|
23
|
-
if
|
25
|
+
if association_data.ordered?
|
24
26
|
association_scope = association_scope.order(direct_viewmodel._list_attribute_name)
|
25
27
|
end
|
26
28
|
|
@@ -116,6 +118,8 @@ module ViewModel::ActiveRecord::AssociationManipulation
|
|
116
118
|
direct_viewmodel_class = association_data.direct_viewmodel
|
117
119
|
root_update_data, referenced_update_data = construct_indirect_append_updates(association_data, subtree_hashes, references)
|
118
120
|
else
|
121
|
+
raise ArgumentError.new('Polymorphic STI relationships not supported yet') if association_data.viewmodel_classes.size > 1
|
122
|
+
|
119
123
|
direct_viewmodel_class = association_data.viewmodel_class
|
120
124
|
root_update_data, referenced_update_data = construct_direct_append_updates(association_data, subtree_hashes, references)
|
121
125
|
end
|
@@ -127,7 +131,7 @@ module ViewModel::ActiveRecord::AssociationManipulation
|
|
127
131
|
update_context.root_updates.each { |update| update.reparent_to = new_parent }
|
128
132
|
|
129
133
|
# Set place in list.
|
130
|
-
if
|
134
|
+
if association_data.ordered?
|
131
135
|
new_positions = select_append_positions(association_data,
|
132
136
|
direct_viewmodel_class._list_attribute_name,
|
133
137
|
update_context.root_updates.count,
|
@@ -238,6 +242,8 @@ module ViewModel::ActiveRecord::AssociationManipulation
|
|
238
242
|
direct_viewmodel = association_data.direct_viewmodel
|
239
243
|
association_scope = association_scope.where(association_data.indirect_reflection.foreign_key => associated_id)
|
240
244
|
else
|
245
|
+
raise ArgumentError.new('Polymorphic STI relationships not supported yet') if association_data.viewmodel_classes.size > 1
|
246
|
+
|
241
247
|
# viewmodel type for current association: nil in case of empty polymorphic association
|
242
248
|
direct_viewmodel = association.klass.try { |k| association_data.viewmodel_class_for_model!(k) }
|
243
249
|
|
@@ -450,14 +450,13 @@ class ViewModel::ActiveRecord
|
|
450
450
|
parent_data = ParentData.new(association_data.direct_reflection.inverse_of, viewmodel)
|
451
451
|
|
452
452
|
# load children already attached to this model
|
453
|
-
child_viewmodel_class = association_data.viewmodel_class
|
454
|
-
|
455
453
|
previous_child_viewmodels =
|
456
454
|
model.public_send(association_data.direct_reflection.name).map do |child_model|
|
455
|
+
child_viewmodel_class = association_data.viewmodel_class_for_model!(child_model.class)
|
457
456
|
child_viewmodel_class.new(child_model)
|
458
457
|
end
|
459
458
|
|
460
|
-
if
|
459
|
+
if association_data.ordered?
|
461
460
|
previous_child_viewmodels.sort_by!(&:_list_attribute)
|
462
461
|
end
|
463
462
|
|
@@ -604,7 +603,7 @@ class ViewModel::ActiveRecord
|
|
604
603
|
# need to be updated anyway since their parent pointer will change.
|
605
604
|
new_positions = Array.new(resolved_children.length)
|
606
605
|
|
607
|
-
if association_data.
|
606
|
+
if association_data.ordered?
|
608
607
|
set_position = ->(index, pos) { new_positions[index] = pos }
|
609
608
|
|
610
609
|
get_previous_position = ->(index) do
|
@@ -802,7 +801,7 @@ class ViewModel::ActiveRecord
|
|
802
801
|
ReferencedCollectionMember.new(indirect_viewmodel_ref, direct_vm)
|
803
802
|
end
|
804
803
|
|
805
|
-
if
|
804
|
+
if association_data.ordered?
|
806
805
|
previous_members.sort_by!(&:position)
|
807
806
|
end
|
808
807
|
|
@@ -868,7 +867,7 @@ class ViewModel::ActiveRecord
|
|
868
867
|
viewmodel.association_changed!(association_data.association_name)
|
869
868
|
end
|
870
869
|
|
871
|
-
if
|
870
|
+
if association_data.ordered?
|
872
871
|
ActsAsManualList.update_positions(target_collection.members)
|
873
872
|
end
|
874
873
|
|
data/lib/view_model/migrator.rb
CHANGED
@@ -102,11 +102,18 @@ class ViewModel
|
|
102
102
|
class DownMigrator < Migrator
|
103
103
|
private
|
104
104
|
|
105
|
-
def migrate_viewmodel!(view_name,
|
105
|
+
def migrate_viewmodel!(view_name, source_version, view_hash, references)
|
106
106
|
path = @paths[view_name]
|
107
107
|
return false unless path
|
108
108
|
|
109
|
-
|
109
|
+
# In a serialized output, the source version should always be the present
|
110
|
+
# and the current version, unless already modified by a parent migration
|
111
|
+
required_version, current_version = @versions[view_name]
|
112
|
+
return false if source_version == required_version
|
113
|
+
|
114
|
+
unless source_version == current_version
|
115
|
+
raise ViewModel::Migration::UnspecifiedVersionError.new(view_name, source_version)
|
116
|
+
end
|
110
117
|
|
111
118
|
path.reverse_each do |migration|
|
112
119
|
migration.down(view_hash, references)
|
@@ -168,26 +168,61 @@ class ViewModel::ActiveRecord::BelongsToTest < ActiveSupport::TestCase
|
|
168
168
|
|
169
169
|
class GCTests < ActiveSupport::TestCase
|
170
170
|
include ARVMTestUtilities
|
171
|
-
include ViewModelSpecHelpers::
|
171
|
+
include ViewModelSpecHelpers::Base
|
172
172
|
|
173
173
|
def model_attributes
|
174
174
|
super.merge(
|
175
175
|
schema: ->(t) do
|
176
|
+
t.integer :destroyed_child_id
|
176
177
|
t.integer :deleted_child_id
|
177
178
|
t.integer :ignored_child_id
|
179
|
+
t.foreign_key :children, column: :destroyed_child_id
|
180
|
+
t.foreign_key :children, column: :deleted_child_id
|
181
|
+
t.foreign_key :children, column: :ignored_child_id
|
178
182
|
end,
|
179
183
|
model: ->(_m) do
|
180
|
-
belongs_to :
|
181
|
-
belongs_to :
|
184
|
+
belongs_to :destroyed_child, class_name: Child.name, inverse_of: :destroyed_model, dependent: :destroy
|
185
|
+
belongs_to :deleted_child, class_name: Child.name, inverse_of: :deleted_model, dependent: :delete
|
186
|
+
belongs_to :ignored_child, class_name: Child.name, inverse_of: :ignored_model
|
182
187
|
end,
|
183
188
|
viewmodel: ->(_v) do
|
184
|
-
associations :deleted_child, :ignored_child
|
189
|
+
associations :destroyed_child, :deleted_child, :ignored_child
|
185
190
|
end)
|
186
191
|
end
|
187
192
|
|
188
|
-
|
189
|
-
|
193
|
+
def child_attributes
|
194
|
+
super.merge(
|
195
|
+
model: ->(_m) do
|
196
|
+
has_one :destroyed_model, class_name: 'Model', inverse_of: :destroyed_child, foreign_key: 'destroyed_child_id'
|
197
|
+
has_one :deleted_model, class_name: 'Model', inverse_of: :deleted_child, foreign_key: 'deleted_child_id'
|
198
|
+
has_one :ignored_model, class_name: 'Model', inverse_of: :ignored_child, foreign_key: 'ignored_child_id'
|
199
|
+
end)
|
200
|
+
end
|
201
|
+
|
202
|
+
def viewmodel_class
|
203
|
+
child_viewmodel_class
|
204
|
+
super
|
205
|
+
end
|
206
|
+
|
207
|
+
# test belongs_to garbage collection - dependent: destroy
|
208
|
+
def test_gc_dependent_destroy
|
209
|
+
model = model_class.create(destroyed_child: Child.new(name: 'one'))
|
210
|
+
|
211
|
+
old_child = model.destroyed_child
|
212
|
+
|
213
|
+
alter_by_view!(ModelView, model) do |ov, _refs|
|
214
|
+
ov['destroyed_child'] = { '_type' => 'Child', 'name' => 'two' }
|
215
|
+
end
|
216
|
+
|
217
|
+
assert_equal('two', model.destroyed_child.name)
|
218
|
+
refute_equal(old_child, model.destroyed_child)
|
219
|
+
assert(Child.where(id: old_child.id).blank?)
|
220
|
+
end
|
221
|
+
|
222
|
+
# test belongs_to garbage collection - dependent: delete
|
223
|
+
def test_gc_dependent_delete
|
190
224
|
model = model_class.create(deleted_child: Child.new(name: 'one'))
|
225
|
+
|
191
226
|
old_child = model.deleted_child
|
192
227
|
|
193
228
|
alter_by_view!(ModelView, model) do |ov, _refs|
|
@@ -898,6 +898,131 @@ class ViewModel::ActiveRecord::HasManyTest < ActiveSupport::TestCase
|
|
898
898
|
assert_match(/Duplicate functional update targets\b.*\bChild\b/, ex.message)
|
899
899
|
end
|
900
900
|
|
901
|
+
describe 'sti polymorphic children' do
|
902
|
+
def setup
|
903
|
+
child_viewmodel_class
|
904
|
+
dog_viewmodel_class
|
905
|
+
cat_viewmodel_class
|
906
|
+
enable_logging!
|
907
|
+
end
|
908
|
+
|
909
|
+
def child_attributes
|
910
|
+
super().merge(schema: ->(t) do
|
911
|
+
t.string :type, null: false
|
912
|
+
t.integer :dog_number
|
913
|
+
t.integer :cat_number
|
914
|
+
end)
|
915
|
+
end
|
916
|
+
|
917
|
+
def subject_association_features
|
918
|
+
{ viewmodels: [:Dog, :Cat] }
|
919
|
+
end
|
920
|
+
|
921
|
+
def dog_viewmodel_class
|
922
|
+
@dog_viewmodel_class ||= define_viewmodel_class(:Dog, namespace: namespace, viewmodel_base: viewmodel_base, model_base: child_model_class) do
|
923
|
+
define_model {}
|
924
|
+
define_viewmodel do
|
925
|
+
attribute :dog_number
|
926
|
+
acts_as_list :position
|
927
|
+
end
|
928
|
+
end
|
929
|
+
end
|
930
|
+
|
931
|
+
def cat_viewmodel_class
|
932
|
+
@cat_viewmodel_class ||= define_viewmodel_class(:Cat, namespace: namespace, viewmodel_base: viewmodel_base, model_base: child_model_class) do
|
933
|
+
define_model {}
|
934
|
+
define_viewmodel do
|
935
|
+
attribute :cat_number
|
936
|
+
acts_as_list :position
|
937
|
+
end
|
938
|
+
end
|
939
|
+
end
|
940
|
+
|
941
|
+
def new_model
|
942
|
+
model_class.new(name: 'p', children: [Dog.new(position: 1, dog_number: 1), Cat.new(position: 2, cat_number: 2)])
|
943
|
+
end
|
944
|
+
|
945
|
+
it 'creates the model structure' do
|
946
|
+
m = create_model!
|
947
|
+
m.reload
|
948
|
+
assert(m.is_a?(Model))
|
949
|
+
children = m.children.order(:position)
|
950
|
+
assert_equal(2, children.size)
|
951
|
+
assert_kind_of(Dog, children[0])
|
952
|
+
assert_kind_of(Cat, children[1])
|
953
|
+
end
|
954
|
+
|
955
|
+
it 'serializes' do
|
956
|
+
model = create_model!
|
957
|
+
view = serialize(ModelView.new(model))
|
958
|
+
expected_view = {
|
959
|
+
'id' => 1, '_type' => 'Model', '_version' => 1, 'name' => 'p',
|
960
|
+
'children' => [
|
961
|
+
{ 'id' => 1, '_type' => 'Dog', '_version' => 1, 'dog_number' => 1 },
|
962
|
+
{ 'id' => 2, '_type' => 'Cat', '_version' => 1, 'cat_number' => 2 },
|
963
|
+
]
|
964
|
+
}
|
965
|
+
assert_equal(expected_view, view)
|
966
|
+
end
|
967
|
+
|
968
|
+
it 'creates from view' do
|
969
|
+
view = {
|
970
|
+
'_type' => 'Model',
|
971
|
+
'name' => 'p',
|
972
|
+
'children' => [
|
973
|
+
{ '_type' => 'Dog', 'dog_number' => 1 },
|
974
|
+
{ '_type' => 'Cat', 'cat_number' => 2 },
|
975
|
+
]
|
976
|
+
}
|
977
|
+
|
978
|
+
pv = ModelView.deserialize_from_view(view)
|
979
|
+
p = pv.model
|
980
|
+
|
981
|
+
assert(!p.changed?)
|
982
|
+
assert(!p.new_record?)
|
983
|
+
|
984
|
+
assert_equal('p', p.name)
|
985
|
+
|
986
|
+
children = p.children.order(:position)
|
987
|
+
|
988
|
+
assert_equal(2, children.size)
|
989
|
+
assert_kind_of(Dog, children[0])
|
990
|
+
assert_equal(1, children[0].dog_number)
|
991
|
+
assert_kind_of(Cat, children[1])
|
992
|
+
assert_equal(2, children[1].cat_number)
|
993
|
+
end
|
994
|
+
|
995
|
+
it 'updates with reordering' do
|
996
|
+
model = create_model!
|
997
|
+
|
998
|
+
alter_by_view!(ModelView, model) do |view, _refs|
|
999
|
+
view['children'].reverse!
|
1000
|
+
end
|
1001
|
+
|
1002
|
+
children = model.children.order(:position)
|
1003
|
+
assert_equal(2, children.size)
|
1004
|
+
assert_kind_of(Cat, children[0])
|
1005
|
+
assert_equal(2, children[0].cat_number)
|
1006
|
+
assert_kind_of(Dog, children[1])
|
1007
|
+
assert_equal(1, children[1].dog_number)
|
1008
|
+
end
|
1009
|
+
|
1010
|
+
it 'functional updates' do
|
1011
|
+
model = create_model!
|
1012
|
+
|
1013
|
+
alter_by_view!(ModelView, model) do |view, refs|
|
1014
|
+
view['children'] = build_fupdate do
|
1015
|
+
append([{ '_type' => 'Cat', 'cat_number' => 100 }])
|
1016
|
+
end
|
1017
|
+
end
|
1018
|
+
|
1019
|
+
assert_equal(3, model.children.size)
|
1020
|
+
new_child = model.children.order(:position).last
|
1021
|
+
assert_kind_of(Cat, new_child)
|
1022
|
+
assert_equal(100, new_child.cat_number)
|
1023
|
+
end
|
1024
|
+
end
|
1025
|
+
|
901
1026
|
describe 'owned reference children' do
|
902
1027
|
def child_attributes
|
903
1028
|
super.merge(viewmodel: ->(_v) { root! })
|
@@ -494,7 +494,7 @@ class ViewModel::CallbacksTest < ActiveSupport::TestCase
|
|
494
494
|
let(:callback) { CallbackCrasher.new }
|
495
495
|
|
496
496
|
it 'raises the callback error' do
|
497
|
-
|
497
|
+
_(-> { serialize(vm) }).must_raise(Crash)
|
498
498
|
end
|
499
499
|
|
500
500
|
describe 'with an access control that rejects' do
|
@@ -503,7 +503,7 @@ class ViewModel::CallbacksTest < ActiveSupport::TestCase
|
|
503
503
|
end
|
504
504
|
|
505
505
|
it 'fails access control first' do
|
506
|
-
|
506
|
+
_(-> { serialize(vm) }).must_raise(ViewModel::AccessControlError)
|
507
507
|
end
|
508
508
|
|
509
509
|
describe 'and a view-mutating callback that crashes' do
|
@@ -514,7 +514,7 @@ class ViewModel::CallbacksTest < ActiveSupport::TestCase
|
|
514
514
|
let(:callback) { MutatingCrasher.new }
|
515
515
|
|
516
516
|
it 'raises the callback error first' do
|
517
|
-
|
517
|
+
_(-> { serialize(vm) }).must_raise(Crash)
|
518
518
|
end
|
519
519
|
end
|
520
520
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iknow_view_models
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.4.
|
4
|
+
version: 3.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- iKnow Team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-06-
|
11
|
+
date: 2021-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|