iknow_view_models 3.4.0 → 3.4.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|