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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e0fb612b3ee4fc28b8bba11d40dfcd2da75775af0c8154a83a913f179824ccd
4
- data.tar.gz: 6027d6f5a46e2654432e8c7b28cfcde8d2cb07f523efa7f87be199241246b83e
3
+ metadata.gz: 87a056bba1e37b5593a5ad90365c5b761976529f1c5ab4074b26dd666060d57e
4
+ data.tar.gz: 5a796e74204b2fb1b55d8f94bf95c347b3b80a65bf3d48036f7b6de06cba8cf2
5
5
  SHA512:
6
- metadata.gz: 7301fdf85df162d86aaaa558e136dd1df2f047aec97abd084ba83369cac83b5d1b7904a8d0930c9cfb34a9c08aa49e835f89d0ea5c83eef007dbec4ff92a4b31
7
- data.tar.gz: 861f4a2b0ab0eb4a48fa6ef1a2116307dd9d8409508e40c2534cb3e07b6dfe92e843f2eeb4904249d97326d83147eccf3b1757c0200b2ce796aa83eeb1eb3c0f
6
+ metadata.gz: 1b7d484cb7ab2d0286bfc99460f029e8a9ccbb5edcc355cee42671e285f7ce2efe45946f2f7130d528b947fdb8e3bd41aa12dc007a049674b35d7a41b6f6b5be
7
+ data.tar.gz: 7ab16b3a6ec5569704da7a5bdfccde6374d43770c53e1e78ceca961e7043400842c8e84e3614528eee3b8f7c81ccf884f2d0ffc24793ef5cc8328d96cee12152
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IknowViewModels
4
- VERSION = '3.4.0'
4
+ VERSION = '3.4.1'
5
5
  end
@@ -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.direct_viewmodel._list_member?
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
- associated_viewmodel_class = association_data.viewmodel_class
354
- associated_viewmodels = associated.map { |x| associated_viewmodel_class.new(x) }
355
- if associated_viewmodel_class._list_member?
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 direct_viewmodel._list_member?
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 direct_viewmodel_class._list_member?
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 child_viewmodel_class._list_member?
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.viewmodel_class._list_member?
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 direct_viewmodel_class._list_member?
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 direct_viewmodel_class._list_member?
870
+ if association_data.ordered?
872
871
  ActsAsManualList.update_positions(target_collection.members)
873
872
  end
874
873
 
@@ -102,11 +102,18 @@ class ViewModel
102
102
  class DownMigrator < Migrator
103
103
  private
104
104
 
105
- def migrate_viewmodel!(view_name, _, view_hash, references)
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
- required_version, _current_version = @versions[view_name]
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::ParentAndBelongsToChild
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 :deleted_child, class_name: Child.name, dependent: :delete
181
- belongs_to :ignored_child, class_name: Child.name
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
- # test belongs_to garbage collection - dependent: delete_all
189
- def test_gc_dependent_delete_all
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
- proc { serialize(vm) }.must_raise(Crash)
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
- proc { serialize(vm) }.must_raise(ViewModel::AccessControlError)
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
- proc { serialize(vm) }.must_raise(Crash)
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.0
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-21 00:00:00.000000000 Z
11
+ date: 2021-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord