vorpal 0.0.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.
@@ -0,0 +1,759 @@
1
+ require 'integration_spec_helper'
2
+
3
+ require 'vorpal/configuration'
4
+
5
+ require 'virtus'
6
+
7
+ describe 'Aggregate Repository' do
8
+
9
+ # for testing polymorphic associations
10
+ class Bug
11
+ include Virtus.model
12
+
13
+ attribute :id, Integer
14
+ attribute :name, String
15
+ attribute :lives_on, Object
16
+ end
17
+
18
+ class Tree; end
19
+
20
+ class Trunk
21
+ include Virtus.model
22
+
23
+ attribute :id, Integer
24
+ attribute :length, Decimal
25
+ attribute :bugs, Array[Bug]
26
+ attribute :tree, Tree
27
+ end
28
+
29
+ class Branch
30
+ include Virtus.model
31
+
32
+ attribute :id, Integer
33
+ attribute :length, Decimal
34
+ attribute :tree, Tree
35
+ attribute :branches, Array[Branch]
36
+ attribute :bugs, Array[Bug]
37
+ end
38
+
39
+ class Fissure < ActiveRecord::Base; end
40
+ class Swamp < ActiveRecord::Base; end
41
+
42
+ class Tree
43
+ include Virtus.model
44
+
45
+ attribute :id, Integer
46
+ attribute :name, String
47
+ attribute :trunk, Trunk
48
+ attribute :environment, Object
49
+ attribute :fissures, Array[Fissure]
50
+ attribute :branches, Array[Branch]
51
+ end
52
+
53
+ before(:all) do
54
+ define_table('branches', {length: :decimal, tree_id: :integer, branch_id: :integer}, false)
55
+ BranchDB = defineAr('branches')
56
+
57
+ define_table('bugs', {name: :text, lives_on_id: :integer, lives_on_type: :string}, false)
58
+ BugDB = defineAr('bugs')
59
+
60
+ define_table('fissures', {length: :decimal, tree_id: :integer}, false)
61
+
62
+ define_table('trees', {name: :text, trunk_id: :integer, environment_id: :integer, environment_type: :string}, false)
63
+ TreeDB = defineAr('trees')
64
+
65
+ define_table('trunks', {length: :decimal}, false)
66
+ TrunkDB = defineAr('trunks')
67
+
68
+ define_table('swamps', {}, false)
69
+ end
70
+
71
+ describe 'new records' do
72
+ it 'saves attributes' do
73
+ test_repository = configure
74
+
75
+ tree = Tree.new(name: 'backyard tree')
76
+ test_repository.persist(tree)
77
+
78
+ tree_db = TreeDB.first
79
+ expect(tree_db.name).to eq 'backyard tree'
80
+ end
81
+
82
+ it 'sets the id when first saved' do
83
+ test_repository = configure
84
+
85
+ tree = Tree.new()
86
+ test_repository.persist(tree)
87
+
88
+ expect(tree.id).to_not be nil
89
+
90
+ tree_db = TreeDB.first
91
+ expect(tree_db.id).to eq tree.id
92
+ end
93
+
94
+ it 'saves AR::Base objects' do
95
+ test_repository = configure
96
+
97
+ fissure = Fissure.new(length: 21)
98
+ tree = Tree.new(fissures: [fissure])
99
+
100
+ test_repository.persist(tree)
101
+
102
+ expect(Fissure.first.length).to eq 21
103
+ end
104
+ end
105
+
106
+ describe 'on error' do
107
+ it 'nils ids of new objects' do
108
+ test_repository = configure
109
+
110
+ tree_db = TreeDB.create!
111
+
112
+ fissure = Fissure.new
113
+ def fissure.save!
114
+ raise 'something bad happened!'
115
+ end
116
+ tree = Tree.new(id: tree_db.id, fissures: [fissure])
117
+
118
+ expect {
119
+ test_repository.persist(tree)
120
+ }.to raise_error(Exception)
121
+
122
+ expect(fissure.id).to eq nil
123
+ expect(tree.id).to_not eq nil
124
+ end
125
+ end
126
+
127
+ describe 'existing records' do
128
+ it 'updates attributes' do
129
+ test_repository = configure
130
+
131
+ tree = Tree.new(name: 'little tree')
132
+ test_repository.persist(tree)
133
+
134
+ tree.name = 'big tree'
135
+ test_repository.persist(tree)
136
+
137
+ tree_db = TreeDB.first
138
+ expect(tree_db.name).to eq 'big tree'
139
+ end
140
+
141
+ it 'does not change the id on update' do
142
+ test_repository = configure
143
+
144
+ tree = Tree.new()
145
+ test_repository.persist(tree)
146
+
147
+ original_id = tree.id
148
+
149
+ tree.name = 'change it'
150
+ test_repository.persist(tree)
151
+
152
+ expect(tree.id).to eq original_id
153
+ end
154
+
155
+ it 'does not create additional records' do
156
+ test_repository = configure
157
+
158
+ tree = Tree.new()
159
+ test_repository.persist(tree)
160
+
161
+ tree.name = 'change it'
162
+ test_repository.persist(tree)
163
+
164
+ expect(TreeDB.count).to eq 1
165
+ end
166
+
167
+ it 'removes orphans' do
168
+ test_repository = configure
169
+
170
+ tree_db = TreeDB.create!
171
+ BranchDB.create!(tree_id: tree_db.id)
172
+
173
+ tree = Tree.new(id: tree_db.id, branches: [])
174
+
175
+ test_repository.persist(tree)
176
+
177
+ expect(BranchDB.count).to eq 0
178
+ end
179
+
180
+ it 'does not remove orphans from unowned associations' do
181
+ test_repository = configure_unowned
182
+
183
+ tree_db = TreeDB.create!
184
+ BranchDB.create!(tree_id: tree_db.id)
185
+
186
+ tree = Tree.new(id: tree_db.id, branches: [])
187
+
188
+ test_repository.persist(tree)
189
+
190
+ expect(BranchDB.count).to eq 1
191
+ end
192
+ end
193
+
194
+ it 'copies attributes to domain' do
195
+ test_repository = configure
196
+
197
+ tree_db = TreeDB.create! name: 'tree name'
198
+ tree = test_repository.load(tree_db.id, Tree)
199
+
200
+ expect(tree.id).to eq tree_db.id
201
+ expect(tree.name).to eq 'tree name'
202
+ end
203
+
204
+ it 'hydrates ActiveRecord::Base associations' do
205
+ test_repository = configure
206
+
207
+ tree_db = TreeDB.create!
208
+ Fissure.create! length: 21, tree_id: tree_db.id
209
+
210
+ tree = test_repository.load(tree_db.id, Tree)
211
+
212
+ expect(tree.fissures.first.length).to eq 21
213
+ end
214
+
215
+ describe "cycles" do
216
+ it 'persists' do
217
+ test_repository = configure_with_cycle
218
+
219
+ tree = Tree.new
220
+ long_branch = Branch.new(length: 100, tree: tree)
221
+ tree.branches << long_branch
222
+
223
+ test_repository.persist(tree)
224
+
225
+ expect(TreeDB.count).to eq 1
226
+ end
227
+
228
+ it 'hydrates' do
229
+ test_repository = configure_with_cycle
230
+
231
+ tree_db = TreeDB.create!
232
+ BranchDB.create!(length: 50, tree_id: tree_db.id)
233
+
234
+ tree = test_repository.load(tree_db.id, Tree)
235
+
236
+ expect(tree).to be tree.branches.first.tree
237
+ end
238
+ end
239
+
240
+ describe 'recursive associations' do
241
+ it 'persists' do
242
+ test_repository = configure_recursive
243
+
244
+ tree = Tree.new
245
+ long_branch = Branch.new(length: 100, tree: tree)
246
+ tree.branches << long_branch
247
+ short_branch = Branch.new(length: 50, tree: tree)
248
+ long_branch.branches << short_branch
249
+
250
+ test_repository.persist(tree)
251
+
252
+ expect(BranchDB.count).to eq 2
253
+ end
254
+
255
+ it 'hydrates' do
256
+ test_repository = configure_recursive
257
+
258
+ tree_db = TreeDB.create!
259
+ long_branch = BranchDB.create!(length: 100, tree_id: tree_db.id)
260
+ BranchDB.create!(length: 50, branch_id: long_branch.id)
261
+
262
+ tree = test_repository.load(tree_db.id, Tree)
263
+
264
+ expect(tree.branches.first.branches.first.length).to eq 50
265
+ end
266
+ end
267
+
268
+ describe 'belongs_to associations' do
269
+ it 'saves attributes' do
270
+ test_repository = configure
271
+ trunk = Trunk.new(length: 12)
272
+ tree = Tree.new(trunk: trunk)
273
+
274
+ test_repository.persist(tree)
275
+
276
+ trunk_db = TrunkDB.first
277
+ expect(trunk_db.length).to eq 12
278
+ end
279
+
280
+ it 'saves foreign keys' do
281
+ test_repository = configure
282
+ trunk = Trunk.new()
283
+ tree = Tree.new(trunk: trunk)
284
+
285
+ test_repository.persist(tree)
286
+
287
+ tree_db = TreeDB.first
288
+ expect(tree_db.trunk_id).to eq trunk.id
289
+ end
290
+
291
+ it 'updating does not create additional rows' do
292
+ test_repository = configure
293
+ trunk = Trunk.new
294
+ tree = Tree.new(trunk: trunk)
295
+
296
+ test_repository.persist(tree)
297
+
298
+ trunk.length = 21
299
+
300
+ expect{ test_repository.persist(tree) }.to_not change{ TrunkDB.count }
301
+ end
302
+
303
+ it 'only saves entities that are owned' do
304
+ test_repository = configure_unowned
305
+
306
+ trunk = Trunk.new
307
+ tree = Tree.new(trunk: trunk)
308
+
309
+ test_repository.persist(tree)
310
+
311
+ expect(TrunkDB.count).to eq 0
312
+ end
313
+
314
+ it 'hydrates' do
315
+ test_repository = configure
316
+ trunk_db = TrunkDB.create!(length: 21)
317
+ tree_db = TreeDB.create!(trunk_id: trunk_db.id)
318
+
319
+ new_tree = test_repository.load(tree_db.id, Tree)
320
+ expect(new_tree.trunk.length).to eq 21
321
+ end
322
+ end
323
+
324
+ describe 'has_many associations' do
325
+ it 'saves' do
326
+ test_repository = configure
327
+ tree = Tree.new
328
+ tree.branches << Branch.new(length: 100)
329
+ tree.branches << Branch.new(length: 3)
330
+
331
+ test_repository.persist(tree)
332
+
333
+ branches = BranchDB.all
334
+ expect(branches.size).to eq 2
335
+ expect(branches.first.length).to eq 100
336
+ expect(branches.second.length).to eq 3
337
+ end
338
+
339
+ it 'saves foreign keys' do
340
+ test_repository = configure
341
+ tree = Tree.new
342
+ tree.branches << Branch.new(length: 100)
343
+
344
+ test_repository.persist(tree)
345
+
346
+ branches = BranchDB.all
347
+ expect(branches.first.tree_id).to eq tree.id
348
+ end
349
+
350
+ it 'updates' do
351
+ test_repository = configure
352
+ tree = Tree.new
353
+ long_branch = Branch.new(length: 100)
354
+ tree.branches << long_branch
355
+
356
+ test_repository.persist(tree)
357
+
358
+ long_branch.length = 120
359
+
360
+ test_repository.persist(tree)
361
+
362
+ branches = BranchDB.all
363
+ expect(branches.first.length).to eq 120
364
+ end
365
+
366
+ it 'only saves entities that are owned' do
367
+ test_repository = configure_unowned
368
+
369
+ tree = Tree.new
370
+ long_branch = Branch.new(length: 100)
371
+ tree.branches << long_branch
372
+
373
+ test_repository.persist(tree)
374
+
375
+ expect(BranchDB.count).to eq 0
376
+ end
377
+
378
+ it 'hydrates' do
379
+ test_repository = configure
380
+
381
+ tree_db = TreeDB.create!
382
+ BranchDB.create!(length: 50, tree_id: tree_db.id)
383
+
384
+ tree = test_repository.load(tree_db.id, Tree)
385
+
386
+ expect(tree.branches.first.length).to eq 50
387
+ end
388
+ end
389
+
390
+ describe 'has_one associations' do
391
+ it 'saves' do
392
+ test_repository = configure_has_one
393
+ tree = Tree.new(name: 'big tree')
394
+ trunk = Trunk.new(tree: tree)
395
+
396
+ test_repository.persist(trunk)
397
+
398
+ expect(TreeDB.first.name).to eq 'big tree'
399
+ end
400
+
401
+ it 'saves foreign keys' do
402
+ test_repository = configure_has_one
403
+ tree = Tree.new(name: 'big tree')
404
+ trunk = Trunk.new(tree: tree)
405
+
406
+ test_repository.persist(trunk)
407
+
408
+ expect(TreeDB.first.trunk_id).to eq trunk.id
409
+ end
410
+
411
+ it 'only saves entities that are owned' do
412
+ test_repository = configure_unowned_has_one
413
+ tree = Tree.new
414
+ trunk = Trunk.new(tree: tree)
415
+
416
+ test_repository.persist(trunk)
417
+
418
+ expect(TreeDB.count).to eq 0
419
+ end
420
+
421
+ it 'hydrates' do
422
+ test_repository = configure_has_one
423
+
424
+ trunk_db = TrunkDB.create!
425
+ TreeDB.create!(name: 'big tree', trunk_id: trunk_db.id)
426
+
427
+ trunk = test_repository.load(trunk_db.id, Trunk)
428
+
429
+ expect(trunk.tree.name).to eq 'big tree'
430
+ end
431
+ end
432
+
433
+ describe 'polymorphic associations' do
434
+ it 'saves with has_manys' do
435
+ test_repository = configure_polymorphic_has_many
436
+ trunk = Trunk.new
437
+ branch = Branch.new
438
+ tree = Tree.new(trunk: trunk, branches: [branch])
439
+
440
+ trunk_bug = Bug.new
441
+ trunk.bugs << trunk_bug
442
+ branch_bug = Bug.new
443
+ branch.bugs << branch_bug
444
+
445
+ test_repository.persist(tree)
446
+
447
+ expect(BugDB.find(trunk_bug.id).lives_on_type).to eq Trunk.name
448
+ expect(BugDB.find(branch_bug.id).lives_on_type).to eq Branch.name
449
+ end
450
+
451
+ it 'restores with has_manys' do
452
+ test_repository = configure_polymorphic_has_many
453
+
454
+ trunk_db = TrunkDB.create!
455
+ BugDB.create!(name: 'trunk bug', lives_on_id: trunk_db.id, lives_on_type: Trunk.name)
456
+ BugDB.create!(name: 'not a trunk bug!', lives_on_id: trunk_db.id, lives_on_type: 'some other table')
457
+
458
+ trunk = test_repository.load(trunk_db.id, Trunk)
459
+
460
+ expect(trunk.bugs.map(&:name)).to eq ['trunk bug']
461
+ end
462
+
463
+ it 'saves with belongs_tos' do
464
+ test_repository = configure_polymorphic_belongs_to
465
+
466
+ trunk_bug = Bug.new(lives_on: Trunk.new)
467
+ branch_bug = Bug.new(lives_on: Branch.new)
468
+
469
+ test_repository.persist_all([trunk_bug, branch_bug])
470
+
471
+ expect(BugDB.find(trunk_bug.id).lives_on_type).to eq Trunk.name
472
+ expect(BugDB.find(branch_bug.id).lives_on_type).to eq Branch.name
473
+ end
474
+
475
+ it 'saves associations to unowned entities via belongs_to' do
476
+ test_repository = configure_unowned_polymorphic_belongs_to
477
+ trunk = Trunk.new
478
+
479
+ trunk_bug = Bug.new(lives_on: trunk)
480
+
481
+ test_repository.persist(trunk_bug)
482
+
483
+ expect(BugDB.find(trunk_bug.id).lives_on_type).to eq Trunk.name
484
+ end
485
+
486
+ it 'restores with belongs_tos' do
487
+ test_repository = configure_polymorphic_belongs_to
488
+
489
+ trunk_db = TrunkDB.create!(length: 99)
490
+ trunk_bug_db = BugDB.create!(lives_on_id: trunk_db.id, lives_on_type: Trunk.name)
491
+ branch_db = BranchDB.create!(length: 5)
492
+ branch_bug_db = BugDB.create!(lives_on_id: branch_db.id, lives_on_type: Branch.name)
493
+
494
+ trunk_bug, branch_bug = test_repository.load_all([trunk_bug_db.id, branch_bug_db.id], Bug)
495
+
496
+ expect(trunk_bug.lives_on.length).to eq 99
497
+ expect(branch_bug.lives_on.length).to eq 5
498
+ end
499
+
500
+ it 'restores active record objects' do
501
+ test_repository = configure_ar_polymorphic_belongs_to
502
+
503
+ swamp = Swamp.create!
504
+ tree_db = TreeDB.create!(environment_id: swamp.id, environment_type: Swamp.name)
505
+
506
+ tree = test_repository.load(tree_db.id, Tree)
507
+
508
+ expect(tree.environment).to eq swamp
509
+ end
510
+ end
511
+
512
+ describe 'destroy' do
513
+ it 'removes the entity from the database' do
514
+ test_repository = configure
515
+
516
+ tree_db = TreeDB.create!
517
+
518
+ test_repository.destroy_all([Tree.new(id: tree_db.id)])
519
+
520
+ expect(TreeDB.count).to eq 0
521
+ end
522
+
523
+ it 'removes has many children from the database' do
524
+ test_repository = configure
525
+
526
+ tree_db = TreeDB.create!
527
+ BranchDB.create!(tree_id: tree_db.id)
528
+
529
+ test_repository.destroy(Tree.new(id: tree_db.id))
530
+
531
+ expect(BranchDB.count).to eq 0
532
+ end
533
+
534
+ it 'removes belongs to children from the database' do
535
+ test_repository = configure
536
+
537
+ trunk_db = TrunkDB.create!
538
+ tree_db = TreeDB.create!(trunk_id: trunk_db.id)
539
+
540
+ test_repository.destroy(Tree.new(id: tree_db.id))
541
+
542
+ expect(TrunkDB.count).to eq 0
543
+ end
544
+
545
+ it 'removes AR children from the database' do
546
+ test_repository = configure
547
+
548
+ tree_db = TreeDB.create!
549
+ Fissure.create!(tree_id: tree_db.id)
550
+
551
+ test_repository.destroy(Tree.new(id: tree_db.id))
552
+
553
+ expect(Fissure.count).to eq 0
554
+ end
555
+
556
+ it 'leaves unowned belongs to children in the database' do
557
+ test_repository = configure_unowned
558
+
559
+ trunk_db = TrunkDB.create!
560
+ tree_db = TreeDB.create!(trunk_id: trunk_db.id)
561
+
562
+ test_repository.destroy(Tree.new(id: tree_db.id))
563
+
564
+ expect(TrunkDB.count).to eq 1
565
+ end
566
+
567
+ it 'leaves unowned has many children in the database' do
568
+ test_repository = configure_unowned
569
+
570
+ tree_db = TreeDB.create!
571
+ BranchDB.create!(tree_id: tree_db.id)
572
+
573
+ test_repository.destroy(Tree.new(id: tree_db.id))
574
+
575
+ expect(BranchDB.count).to eq 1
576
+ end
577
+ end
578
+
579
+ # when you change a table's columns, set force to true to re-generate the table in the DB
580
+ def define_table(table_name, columns, force)
581
+ if !ActiveRecord::Base.connection.table_exists?(table_name) || force
582
+ ActiveRecord::Base.connection.create_table(table_name, force: true) do |t|
583
+ columns.each do |name, type|
584
+ t.send(type, name)
585
+ end
586
+ end
587
+ end
588
+ end
589
+
590
+ def defineAr(table_name)
591
+ Class.new(ActiveRecord::Base) do
592
+ self.table_name = table_name
593
+ end
594
+ end
595
+
596
+ private
597
+
598
+ def configure_polymorphic_has_many
599
+ Vorpal::Configuration.define do
600
+ map Tree do
601
+ fields :name
602
+ has_many :branches
603
+ belongs_to :trunk
604
+ end
605
+
606
+ map Trunk do
607
+ fields :length
608
+ has_many :bugs, fk: :lives_on_id, fk_type: :lives_on_type
609
+ end
610
+
611
+ map Branch do
612
+ fields :length
613
+ has_many :bugs, fk: :lives_on_id, fk_type: :lives_on_type
614
+ end
615
+
616
+ map Bug do
617
+ fields :name
618
+ end
619
+ end
620
+ end
621
+
622
+ def configure_polymorphic_belongs_to
623
+ Vorpal::Configuration.define do
624
+ map Bug do
625
+ fields :name
626
+ belongs_to :lives_on, fk: :lives_on_id, fk_type: :lives_on_type, child_classes: [Trunk, Branch]
627
+ end
628
+
629
+ map Trunk do
630
+ fields :length
631
+ end
632
+
633
+ map Branch do
634
+ fields :length
635
+ end
636
+ end
637
+ end
638
+
639
+ def configure_ar_polymorphic_belongs_to
640
+ Vorpal::Configuration.define do
641
+ map Tree do
642
+ fields :name
643
+ belongs_to :environment, owned: false, fk: :environment_id, fk_type: :environment_type, child_class: Swamp
644
+ end
645
+
646
+ map Swamp
647
+ end
648
+ end
649
+
650
+ def configure_unowned_polymorphic_belongs_to
651
+ Vorpal::Configuration.define do
652
+ map Bug do
653
+ fields :name
654
+ belongs_to :lives_on, owned: false, fk: :lives_on_id, fk_type: :lives_on_type, child_classes: [Trunk, Branch]
655
+ end
656
+
657
+ map Trunk do
658
+ fields :length
659
+ end
660
+
661
+ map Branch do
662
+ fields :length
663
+ end
664
+ end
665
+ end
666
+
667
+ def configure_unowned
668
+ Vorpal::Configuration.define do
669
+ map Tree do
670
+ fields :name
671
+ has_many :branches, owned: false
672
+ belongs_to :trunk, owned: false
673
+ end
674
+
675
+ map Trunk do
676
+ fields :length
677
+ end
678
+
679
+ map Branch do
680
+ fields :length
681
+ end
682
+ end
683
+ end
684
+
685
+ def configure_recursive
686
+ Vorpal::Configuration.define do
687
+ map Branch do
688
+ fields :length
689
+ has_many :branches
690
+ end
691
+
692
+ map Tree do
693
+ fields :name
694
+ has_many :branches
695
+ end
696
+ end
697
+ end
698
+
699
+ def configure_with_cycle
700
+ Vorpal::Configuration.define do
701
+ map Branch do
702
+ fields :length
703
+ belongs_to :tree
704
+ end
705
+
706
+ map Tree do
707
+ fields :name
708
+ has_many :branches
709
+ end
710
+ end
711
+ end
712
+
713
+ def configure
714
+ Vorpal::Configuration.define do
715
+ map Tree do
716
+ fields :name
717
+ belongs_to :trunk
718
+ has_many :fissures
719
+ has_many :branches
720
+ end
721
+
722
+ map Trunk do
723
+ fields :length
724
+ end
725
+
726
+ map Branch do
727
+ fields :length
728
+ end
729
+
730
+ map Fissure
731
+ end
732
+ end
733
+
734
+ def configure_has_one
735
+ Vorpal::Configuration.define do
736
+ map Trunk do
737
+ fields :length
738
+ has_one :tree
739
+ end
740
+
741
+ map Tree do
742
+ fields :name
743
+ end
744
+ end
745
+ end
746
+
747
+ def configure_unowned_has_one
748
+ Vorpal::Configuration.define do
749
+ map Trunk do
750
+ fields :length
751
+ has_one :tree, owned: false
752
+ end
753
+
754
+ map Tree do
755
+ fields :name
756
+ end
757
+ end
758
+ end
759
+ end