sequel 3.9.0 → 3.10.0

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.
Files changed (60) hide show
  1. data/CHANGELOG +56 -0
  2. data/README.rdoc +1 -1
  3. data/Rakefile +1 -1
  4. data/doc/advanced_associations.rdoc +7 -10
  5. data/doc/release_notes/3.10.0.txt +286 -0
  6. data/lib/sequel/adapters/do/mysql.rb +4 -0
  7. data/lib/sequel/adapters/jdbc.rb +5 -0
  8. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  9. data/lib/sequel/adapters/jdbc/oracle.rb +30 -0
  10. data/lib/sequel/adapters/shared/mssql.rb +23 -9
  11. data/lib/sequel/adapters/shared/mysql.rb +12 -1
  12. data/lib/sequel/adapters/shared/postgres.rb +7 -18
  13. data/lib/sequel/adapters/shared/sqlite.rb +5 -0
  14. data/lib/sequel/adapters/sqlite.rb +5 -0
  15. data/lib/sequel/connection_pool/single.rb +3 -3
  16. data/lib/sequel/database.rb +3 -2
  17. data/lib/sequel/dataset.rb +6 -5
  18. data/lib/sequel/dataset/convenience.rb +3 -3
  19. data/lib/sequel/dataset/query.rb +13 -0
  20. data/lib/sequel/dataset/sql.rb +31 -1
  21. data/lib/sequel/extensions/schema_dumper.rb +3 -3
  22. data/lib/sequel/model.rb +8 -6
  23. data/lib/sequel/model/associations.rb +144 -102
  24. data/lib/sequel/model/base.rb +21 -1
  25. data/lib/sequel/model/plugins.rb +3 -1
  26. data/lib/sequel/plugins/association_dependencies.rb +14 -7
  27. data/lib/sequel/plugins/caching.rb +4 -0
  28. data/lib/sequel/plugins/composition.rb +138 -0
  29. data/lib/sequel/plugins/identity_map.rb +2 -2
  30. data/lib/sequel/plugins/lazy_attributes.rb +1 -1
  31. data/lib/sequel/plugins/nested_attributes.rb +3 -2
  32. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  33. data/lib/sequel/plugins/typecast_on_load.rb +16 -5
  34. data/lib/sequel/sql.rb +18 -1
  35. data/lib/sequel/version.rb +1 -1
  36. data/spec/adapters/mssql_spec.rb +4 -0
  37. data/spec/adapters/mysql_spec.rb +4 -0
  38. data/spec/adapters/postgres_spec.rb +55 -5
  39. data/spec/core/database_spec.rb +5 -3
  40. data/spec/core/dataset_spec.rb +86 -15
  41. data/spec/core/expression_filters_spec.rb +23 -6
  42. data/spec/extensions/association_dependencies_spec.rb +24 -5
  43. data/spec/extensions/association_proxies_spec.rb +3 -0
  44. data/spec/extensions/composition_spec.rb +194 -0
  45. data/spec/extensions/identity_map_spec.rb +16 -0
  46. data/spec/extensions/nested_attributes_spec.rb +44 -1
  47. data/spec/extensions/rcte_tree_spec.rb +205 -0
  48. data/spec/extensions/schema_dumper_spec.rb +6 -0
  49. data/spec/extensions/spec_helper.rb +6 -0
  50. data/spec/extensions/typecast_on_load_spec.rb +9 -0
  51. data/spec/extensions/validation_helpers_spec.rb +5 -5
  52. data/spec/integration/dataset_test.rb +13 -9
  53. data/spec/integration/eager_loader_test.rb +56 -1
  54. data/spec/integration/model_test.rb +8 -0
  55. data/spec/integration/plugin_test.rb +270 -0
  56. data/spec/integration/schema_test.rb +1 -1
  57. data/spec/model/associations_spec.rb +541 -118
  58. data/spec/model/eager_loading_spec.rb +24 -3
  59. data/spec/model/record_spec.rb +34 -0
  60. metadata +9 -2
@@ -82,6 +82,8 @@ describe "Sequel::Database dump methods" do
82
82
  [[:c1, {:db_type=>'blahblah', :allow_null=>true}]]
83
83
  when :t6
84
84
  [[:c1, {:db_type=>'bigint', :primary_key=>true, :allow_null=>true}]]
85
+ when :t7
86
+ [[:c1, {:db_type=>'somedbspecifictype', :primary_key=>true, :allow_null=>false}]]
85
87
  end
86
88
  end
87
89
  end
@@ -94,6 +96,10 @@ describe "Sequel::Database dump methods" do
94
96
  @d.dump_table_schema(:t6).should == "create_table(:t6) do\n primary_key :c1, :type=>Bignum\nend"
95
97
  end
96
98
 
99
+ it "should dump primary key columns with explicit :type equal to the database type when :same_db option is passed" do
100
+ @d.dump_table_schema(:t7, :same_db => true).should == "create_table(:t7) do\n primary_key :c1, :type=>\"somedbspecifictype\"\nend"
101
+ end
102
+
97
103
  it "should use a composite primary_key calls if there is a composite primary key" do
98
104
  @d.dump_table_schema(:t2).should == "create_table(:t2) do\n Integer :c1, :null=>false\n BigDecimal :c2, :null=>false\n \n primary_key [:c1, :c2]\nend"
99
105
  end
@@ -45,6 +45,12 @@ class MockDatabase < Sequel::Database
45
45
  @sqls ||= []
46
46
  @sqls << sql
47
47
  end
48
+
49
+ def new_sqls
50
+ s = sqls
51
+ reset
52
+ s
53
+ end
48
54
 
49
55
  def reset
50
56
  @sqls = []
@@ -7,6 +7,7 @@ describe Sequel::Model, "TypecastOnLoad plugin" do
7
7
  [[:id, {}], [:y, {:type=>:boolean, :db_type=>'tinyint(1)'}], [:b, {:type=>:integer, :db_type=>'integer'}]]
8
8
  end
9
9
  @c = Class.new(Sequel::Model(@db[:items])) do
10
+ include(Module.new{def _refresh(ds); values[:b] = b.to_s; self; end})
10
11
  attr_accessor :bset
11
12
  def b=(x)
12
13
  self.bset = true
@@ -26,6 +27,14 @@ describe Sequel::Model, "TypecastOnLoad plugin" do
26
27
  o.bset.should == true
27
28
  end
28
29
 
30
+ specify "should call setter method with value when reloading the object, for all given columns" do
31
+ @c.plugin :typecast_on_load, :b
32
+ o = @c.new({:id=>1, :b=>"1", :y=>"0"}, true)
33
+ o.refresh
34
+ o.values.should == {:id=>1, :b=>1, :y=>"0"}
35
+ o.bset.should == true
36
+ end
37
+
29
38
  specify "should allowing setting columns separately via add_typecast_on_load_columns" do
30
39
  @c.plugin :typecast_on_load
31
40
  @c.load(:id=>1, :b=>"1", :y=>"0").values.should == {:id=>1, :b=>"1", :y=>"0"}
@@ -377,7 +377,7 @@ describe "Sequel::Plugins::ValidationHelpers" do
377
377
 
378
378
  @user = @c.load(:id=>1, :username => "0records", :password => "anothertest")
379
379
  @user.should be_valid
380
- MODEL_DB.sqls.last.should == "SELECT COUNT(*) AS count FROM items WHERE (((username = '0records') AND (password = 'anothertest')) AND (id != 1)) LIMIT 1"
380
+ MODEL_DB.sqls.last.should == "SELECT COUNT(*) AS count FROM items WHERE ((username = '0records') AND (password = 'anothertest') AND (id != 1)) LIMIT 1"
381
381
  @user = @c.new(:username => "0records", :password => "anothertest")
382
382
  @user.should be_valid
383
383
  MODEL_DB.sqls.last.should == "SELECT COUNT(*) AS count FROM items WHERE ((username = '0records') AND (password = 'anothertest')) LIMIT 1"
@@ -398,7 +398,7 @@ describe "Sequel::Plugins::ValidationHelpers" do
398
398
  @c.new(:username => "0records", :password => "anothertest").should be_valid
399
399
  @c.load(:id=>3, :username => "0records", :password => "anothertest").should be_valid
400
400
  MODEL_DB.sqls.should == ["SELECT COUNT(*) AS count FROM items WHERE ((username = '0records') AND active) LIMIT 1",
401
- "SELECT COUNT(*) AS count FROM items WHERE (((username = '0records') AND active) AND (id != 3)) LIMIT 1"]
401
+ "SELECT COUNT(*) AS count FROM items WHERE ((username = '0records') AND active AND (id != 3)) LIMIT 1"]
402
402
  end
403
403
 
404
404
  it "should support :only_if_modified option for validates_unique, and not check uniqueness for existing records if values haven't changed" do
@@ -423,16 +423,16 @@ describe "Sequel::Plugins::ValidationHelpers" do
423
423
 
424
424
  m.username = '1'
425
425
  m.should be_valid
426
- MODEL_DB.sqls.should == ["SELECT COUNT(*) AS count FROM items WHERE (((username = '1') AND (password = 'anothertest')) AND (id != 3)) LIMIT 1"]
426
+ MODEL_DB.sqls.should == ["SELECT COUNT(*) AS count FROM items WHERE ((username = '1') AND (password = 'anothertest') AND (id != 3)) LIMIT 1"]
427
427
 
428
428
  m = @c.load(:id=>3, :username => "0records", :password => "anothertest")
429
429
  MODEL_DB.reset
430
430
  m.password = '1'
431
431
  m.should be_valid
432
- MODEL_DB.sqls.should == ["SELECT COUNT(*) AS count FROM items WHERE (((username = '0records') AND (password = '1')) AND (id != 3)) LIMIT 1"]
432
+ MODEL_DB.sqls.should == ["SELECT COUNT(*) AS count FROM items WHERE ((username = '0records') AND (password = '1') AND (id != 3)) LIMIT 1"]
433
433
  MODEL_DB.reset
434
434
  m.username = '2'
435
435
  m.should be_valid
436
- MODEL_DB.sqls.should == ["SELECT COUNT(*) AS count FROM items WHERE (((username = '2') AND (password = '1')) AND (id != 3)) LIMIT 1"]
436
+ MODEL_DB.sqls.should == ["SELECT COUNT(*) AS count FROM items WHERE ((username = '2') AND (password = '1') AND (id != 3)) LIMIT 1"]
437
437
  end
438
438
  end
@@ -266,6 +266,10 @@ describe "Simple Dataset operations in transactions" do
266
266
  @ds.order(:id).all.should == [{:id=>1, :number=>20}]
267
267
  end
268
268
  end
269
+
270
+ specify "should support for_update" do
271
+ INTEGRATION_DB.transaction{@ds.for_update.all.should == []}
272
+ end
269
273
  end
270
274
 
271
275
  describe "Dataset UNION, EXCEPT, and INTERSECT" do
@@ -536,23 +540,23 @@ describe "Sequel::Dataset convenience methods" do
536
540
  end
537
541
 
538
542
  it "#group_and_count should return a grouping by count" do
539
- @ds.group_and_count(:a).all.should == []
543
+ @ds.group_and_count(:a).order(:count).all.should == []
540
544
  @ds.insert(20, 10)
541
- @ds.group_and_count(:a).all.each{|h| h[:count] = h[:count].to_i}.should == [{:a=>20, :count=>1}]
545
+ @ds.group_and_count(:a).order(:count).all.each{|h| h[:count] = h[:count].to_i}.should == [{:a=>20, :count=>1}]
542
546
  @ds.insert(20, 30)
543
- @ds.group_and_count(:a).all.each{|h| h[:count] = h[:count].to_i}.should == [{:a=>20, :count=>2}]
547
+ @ds.group_and_count(:a).order(:count).all.each{|h| h[:count] = h[:count].to_i}.should == [{:a=>20, :count=>2}]
544
548
  @ds.insert(30, 30)
545
- @ds.group_and_count(:a).all.each{|h| h[:count] = h[:count].to_i}.should == [{:a=>30, :count=>1}, {:a=>20, :count=>2}]
549
+ @ds.group_and_count(:a).order(:count).all.each{|h| h[:count] = h[:count].to_i}.should == [{:a=>30, :count=>1}, {:a=>20, :count=>2}]
546
550
  end
547
551
 
548
552
  it "#group_and_count should support column aliases" do
549
- @ds.group_and_count(:a___c).all.should == []
553
+ @ds.group_and_count(:a___c).order(:count).all.should == []
550
554
  @ds.insert(20, 10)
551
- @ds.group_and_count(:a___c).all.each{|h| h[:count] = h[:count].to_i}.should == [{:c=>20, :count=>1}]
555
+ @ds.group_and_count(:a___c).order(:count).all.each{|h| h[:count] = h[:count].to_i}.should == [{:c=>20, :count=>1}]
552
556
  @ds.insert(20, 30)
553
- @ds.group_and_count(:a___c).all.each{|h| h[:count] = h[:count].to_i}.should == [{:c=>20, :count=>2}]
557
+ @ds.group_and_count(:a___c).order(:count).all.each{|h| h[:count] = h[:count].to_i}.should == [{:c=>20, :count=>2}]
554
558
  @ds.insert(30, 30)
555
- @ds.group_and_count(:a___c).all.each{|h| h[:count] = h[:count].to_i}.should == [{:c=>30, :count=>1}, {:c=>20, :count=>2}]
559
+ @ds.group_and_count(:a___c).order(:count).all.each{|h| h[:count] = h[:count].to_i}.should == [{:c=>30, :count=>1}, {:c=>20, :count=>2}]
556
560
  end
557
561
 
558
562
  cspecify "#range should return the range between the maximum and minimum values", :sqlite do
@@ -596,7 +600,7 @@ describe "Sequel::Dataset main SQL methods" do
596
600
  @ds.filter("b < ?", 15).invert.all.should == [{:a=>20, :b=>30}]
597
601
  end
598
602
 
599
- it "#and and #or should work with placeholder strings" do
603
+ it "#and and #or should work correctly" do
600
604
  @ds.insert(20, 30)
601
605
  @ds.filter(:a=>20).and(:b=>30).all.should == [{:a=>20, :b=>30}]
602
606
  @ds.filter(:a=>20).and(:b=>15).all.should == []
@@ -624,7 +624,7 @@ describe "statistics associations" do
624
624
  String :name
625
625
  end
626
626
  class ::Project < Sequel::Model
627
- many_to_one :ticket_hours, :read_only=>true, :key=>:id,
627
+ many_to_one :ticket_hours, :read_only=>true, :key=>:id, :class=>:Ticket,
628
628
  :dataset=>proc{Ticket.filter(:project_id=>id).select{sum(hours).as(hours)}},
629
629
  :eager_loader=>(proc do |kh, projects, a|
630
630
  projects.each{|p| p.associations[:ticket_hours] = nil}
@@ -677,3 +677,58 @@ describe "statistics associations" do
677
677
  p2.ticket_hours.to_i.should == 22
678
678
  end
679
679
  end
680
+
681
+ describe "one to one associations" do
682
+ before do
683
+ INTEGRATION_DB.create_table!(:books) do
684
+ primary_key :id
685
+ end
686
+ class ::Book < Sequel::Model
687
+ one_to_one :first_page, :class=>:Page, :conditions=>{:page_number=>1}
688
+ one_to_one :second_page, :class=>:Page, :conditions=>{:page_number=>2}
689
+ end
690
+
691
+ INTEGRATION_DB.create_table!(:pages) do
692
+ primary_key :id
693
+ foreign_key :book_id, :books
694
+ Integer :page_number
695
+ end
696
+ class ::Page < Sequel::Model
697
+ many_to_one :book
698
+ end
699
+
700
+ @book1 = Book.create
701
+ @book2 = Book.create
702
+ @page1 = Page.create(:book=>@book1, :page_number=>1)
703
+ @page2 = Page.create(:book=>@book1, :page_number=>2)
704
+ @page3 = Page.create(:book=>@book2, :page_number=>1)
705
+ @page4 = Page.create(:book=>@book2, :page_number=>2)
706
+ clear_sqls
707
+ end
708
+
709
+ after do
710
+ INTEGRATION_DB.drop_table :pages, :books
711
+ Object.send(:remove_const, :Book)
712
+ Object.send(:remove_const, :Page)
713
+ end
714
+
715
+ it "should be eager loadable" do
716
+ bk1, bk2 = Book.filter(:books__id=>[1,2]).eager(:first_page).all
717
+ bk1.first_page.should == @page1
718
+ bk2.first_page.should == @page3
719
+ end
720
+
721
+ it "should be eager graphable" do
722
+ bk1, bk2 = Book.filter(:books__id=>[1,2]).eager_graph(:first_page).all
723
+ bk1.first_page.should == @page1
724
+ bk2.first_page.should == @page3
725
+ end
726
+
727
+ it "should be eager graphable two at once" do
728
+ bk1, bk2 = Book.filter(:books__id=>[1,2]).eager_graph(:first_page, :second_page).all
729
+ bk1.first_page.should == @page1
730
+ bk1.second_page.should == @page2
731
+ bk2.first_page.should == @page3
732
+ bk2.second_page.should == @page4
733
+ end
734
+ end
@@ -107,4 +107,12 @@ describe "Sequel::Model basic support" do
107
107
  proc{i2 = Marshal.load(s)}.should_not raise_error
108
108
  i2.should == i
109
109
  end
110
+
111
+ specify "#lock! should lock records" do
112
+ Item.db.transaction do
113
+ i = Item.create(:name=>'J')
114
+ i.lock!
115
+ i.update(:name=>'K')
116
+ end
117
+ end
110
118
  end
@@ -482,3 +482,273 @@ describe "OptimisticLocking plugin" do
482
482
  proc{p1.update(:name=>'Bob')}.should_not raise_error
483
483
  end
484
484
  end
485
+
486
+ describe "Composition plugin" do
487
+ before do
488
+ @db = INTEGRATION_DB
489
+ @db.create_table!(:events) do
490
+ primary_key :id
491
+ Integer :year
492
+ Integer :month
493
+ Integer :day
494
+ end
495
+ class ::Event < Sequel::Model(@db)
496
+ plugin :composition
497
+ composition :date, :composer=>proc{Date.new(year, month, day) if year && month && day}, :decomposer=>(proc do
498
+ if date
499
+ self.year = date.year
500
+ self.month = date.month
501
+ self.day = date.day
502
+ else
503
+ self.year, self.month, self.day = nil
504
+ end
505
+ end)
506
+ composition :date, :mapping=>[:year, :month, :day]
507
+ end
508
+ @e1 = Event.create(:year=>2010, :month=>2, :day=>15)
509
+ @e2 = Event.create({})
510
+ end
511
+ after do
512
+ @db.drop_table(:events)
513
+ Object.send(:remove_const, :Event)
514
+ end
515
+
516
+ specify "should return a composed object if the underlying columns have a value" do
517
+ @e1.date.should == Date.civil(2010, 2, 15)
518
+ @e2.date.should == nil
519
+ end
520
+
521
+ specify "should decompose the object when saving the record" do
522
+ @e1.date = Date.civil(2009, 1, 2)
523
+ @e1.save
524
+ @e1.year.should == 2009
525
+ @e1.month.should == 1
526
+ @e1.day.should == 2
527
+ end
528
+
529
+ specify "should save all columns when saving changes" do
530
+ @e2.date = Date.civil(2009, 10, 2)
531
+ @e2.save_changes
532
+ @e2.reload
533
+ @e2.year.should == 2009
534
+ @e2.month.should == 10
535
+ @e2.day.should == 2
536
+ end
537
+ end
538
+
539
+ if INTEGRATION_DB.dataset.supports_cte?
540
+ describe "RcteTree Plugin" do
541
+ before do
542
+ @db = INTEGRATION_DB
543
+ @db.create_table!(:nodes) do
544
+ primary_key :id
545
+ Integer :parent_id
546
+ String :name
547
+ end
548
+ class ::Node < Sequel::Model(@db)
549
+ plugin :rcte_tree, :order=>:name
550
+ end
551
+
552
+ @a = Node.create(:name=>'a')
553
+ @b = Node.create(:name=>'b')
554
+ @aa = Node.create(:name=>'aa', :parent=>@a)
555
+ @ab = Node.create(:name=>'ab', :parent=>@a)
556
+ @ba = Node.create(:name=>'ba', :parent=>@b)
557
+ @bb = Node.create(:name=>'bb', :parent=>@b)
558
+ @aaa = Node.create(:name=>'aaa', :parent=>@aa)
559
+ @aab = Node.create(:name=>'aab', :parent=>@aa)
560
+ @aba = Node.create(:name=>'aba', :parent=>@ab)
561
+ @abb = Node.create(:name=>'abb', :parent=>@ab)
562
+ @aaaa = Node.create(:name=>'aaaa', :parent=>@aaa)
563
+ @aaab = Node.create(:name=>'aaab', :parent=>@aaa)
564
+ @aaaaa = Node.create(:name=>'aaaaa', :parent=>@aaaa)
565
+ end
566
+ after do
567
+ @db.drop_table :nodes
568
+ Object.send(:remove_const, :Node)
569
+ end
570
+
571
+ specify "should load all standard (not-CTE) methods correctly" do
572
+ @a.children.should == [@aa, @ab]
573
+ @b.children.should == [@ba, @bb]
574
+ @aa.children.should == [@aaa, @aab]
575
+ @ab.children.should == [@aba, @abb]
576
+ @ba.children.should == []
577
+ @bb.children.should == []
578
+ @aaa.children.should == [@aaaa, @aaab]
579
+ @aab.children.should == []
580
+ @aba.children.should == []
581
+ @abb.children.should == []
582
+ @aaaa.children.should == [@aaaaa]
583
+ @aaab.children.should == []
584
+ @aaaaa.children.should == []
585
+
586
+ @a.parent.should == nil
587
+ @b.parent.should == nil
588
+ @aa.parent.should == @a
589
+ @ab.parent.should == @a
590
+ @ba.parent.should == @b
591
+ @bb.parent.should == @b
592
+ @aaa.parent.should == @aa
593
+ @aab.parent.should == @aa
594
+ @aba.parent.should == @ab
595
+ @abb.parent.should == @ab
596
+ @aaaa.parent.should == @aaa
597
+ @aaab.parent.should == @aaa
598
+ @aaaaa.parent.should == @aaaa
599
+ end
600
+
601
+ specify "should load all ancestors and descendants lazily for a given instance" do
602
+ @a.descendants.should == [@aa, @aaa, @aaaa, @aaaaa, @aaab, @aab, @ab, @aba, @abb]
603
+ @b.descendants.should == [@ba, @bb]
604
+ @aa.descendants.should == [@aaa, @aaaa, @aaaaa, @aaab, @aab]
605
+ @ab.descendants.should == [@aba, @abb]
606
+ @ba.descendants.should == []
607
+ @bb.descendants.should == []
608
+ @aaa.descendants.should == [@aaaa, @aaaaa, @aaab]
609
+ @aab.descendants.should == []
610
+ @aba.descendants.should == []
611
+ @abb.descendants.should == []
612
+ @aaaa.descendants.should == [@aaaaa]
613
+ @aaab.descendants.should == []
614
+ @aaaaa.descendants.should == []
615
+
616
+ @a.ancestors.should == []
617
+ @b.ancestors.should == []
618
+ @aa.ancestors.should == [@a]
619
+ @ab.ancestors.should == [@a]
620
+ @ba.ancestors.should == [@b]
621
+ @bb.ancestors.should == [@b]
622
+ @aaa.ancestors.should == [@a, @aa]
623
+ @aab.ancestors.should == [@a, @aa]
624
+ @aba.ancestors.should == [@a, @ab]
625
+ @abb.ancestors.should == [@a, @ab]
626
+ @aaaa.ancestors.should == [@a, @aa, @aaa]
627
+ @aaab.ancestors.should == [@a, @aa, @aaa]
628
+ @aaaaa.ancestors.should == [@a, @aa, @aaa, @aaaa]
629
+ end
630
+
631
+ specify "should eagerly load all ancestors and descendants for a dataset" do
632
+ nodes = Node.filter(:id=>[@a.id, @b.id, @aaa.id]).order(:name).eager(:ancestors, :descendants).all
633
+ nodes.should == [@a, @aaa, @b]
634
+ nodes[0].descendants.should == [@aa, @aaa, @aaaa, @aaaaa, @aaab, @aab, @ab, @aba, @abb]
635
+ nodes[1].descendants.should == [@aaaa, @aaaaa, @aaab]
636
+ nodes[2].descendants.should == [@ba, @bb]
637
+ nodes[0].ancestors.should == []
638
+ nodes[1].ancestors.should == [@a, @aa]
639
+ nodes[2].ancestors.should == []
640
+ end
641
+
642
+ specify "should eagerly load descendants to a given level" do
643
+ nodes = Node.filter(:id=>[@a.id, @b.id, @aaa.id]).order(:name).eager(:descendants=>1).all
644
+ nodes.should == [@a, @aaa, @b]
645
+ nodes[0].descendants.should == [@aa, @ab]
646
+ nodes[1].descendants.should == [@aaaa, @aaab]
647
+ nodes[2].descendants.should == [@ba, @bb]
648
+
649
+ nodes = Node.filter(:id=>[@a.id, @b.id, @aaa.id]).order(:name).eager(:descendants=>2).all
650
+ nodes.should == [@a, @aaa, @b]
651
+ nodes[0].descendants.should == [@aa, @aaa, @aab, @ab, @aba, @abb]
652
+ nodes[1].descendants.should == [@aaaa, @aaaaa, @aaab]
653
+ nodes[2].descendants.should == [@ba, @bb]
654
+ end
655
+
656
+ specify "should populate all :children associations when eagerly loading descendants for a dataset" do
657
+ nodes = Node.filter(:id=>[@a.id, @b.id, @aaa.id]).order(:name).eager(:descendants).all
658
+ nodes[0].associations[:children].should == [@aa, @ab]
659
+ nodes[1].associations[:children].should == [@aaaa, @aaab]
660
+ nodes[2].associations[:children].should == [@ba, @bb]
661
+ nodes[0].associations[:children].map{|c1| c1.associations[:children]}.should == [[@aaa, @aab], [@aba, @abb]]
662
+ nodes[1].associations[:children].map{|c1| c1.associations[:children]}.should == [[@aaaaa], []]
663
+ nodes[2].associations[:children].map{|c1| c1.associations[:children]}.should == [[], []]
664
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[[@aaaa, @aaab], []], [[], []]]
665
+ nodes[1].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[[]], []]
666
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children].map{|c3| c3.associations[:children]}}}.should == [[[[@aaaaa], []], []], [[], []]]
667
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children].map{|c3| c3.associations[:children].map{|c4| c4.associations[:children]}}}}.should == [[[[[]], []], []], [[], []]]
668
+ end
669
+
670
+ specify "should not populate :children associations for final level when loading descendants to a given level" do
671
+ nodes = Node.filter(:id=>[@a.id, @b.id, @aaa.id]).order(:name).eager(:descendants=>1).all
672
+ nodes[0].associations[:children].should == [@aa, @ab]
673
+ nodes[0].associations[:children].map{|c1| c1.associations[:children]}.should == [nil, nil]
674
+ nodes[1].associations[:children].should == [@aaaa, @aaab]
675
+ nodes[1].associations[:children].map{|c1| c1.associations[:children]}.should == [nil, nil]
676
+ nodes[2].associations[:children].should == [@ba, @bb]
677
+ nodes[2].associations[:children].map{|c1| c1.associations[:children]}.should == [nil, nil]
678
+
679
+ nodes[0].associations[:children].map{|c1| c1.children}.should == [[@aaa, @aab], [@aba, @abb]]
680
+ nodes[1].associations[:children].map{|c1| c1.children}.should == [[@aaaaa], []]
681
+ nodes[2].associations[:children].map{|c1| c1.children}.should == [[], []]
682
+
683
+ nodes = Node.filter(:id=>[@a.id, @b.id, @aaa.id]).order(:name).eager(:descendants=>2).all
684
+ nodes[0].associations[:children].should == [@aa, @ab]
685
+ nodes[0].associations[:children].map{|c1| c1.associations[:children]}.should == [[@aaa, @aab], [@aba, @abb]]
686
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[[@aaaa, @aaab], nil], [nil, nil]]
687
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| (cc2 = c2.associations[:children]) ? cc2.map{|c3| c3.associations[:children]} : nil}}.should == [[[[@aaaaa], []], nil], [nil, nil]]
688
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| (cc2 = c2.associations[:children]) ? cc2.map{|c3| (cc3 = c3.associations[:children]) ? cc3.map{|c4| c4.associations[:children]} : nil} : nil}}.should == [[[[nil], []], nil], [nil, nil]]
689
+
690
+ nodes[1].associations[:children].should == [@aaaa, @aaab]
691
+ nodes[1].associations[:children].map{|c1| c1.associations[:children]}.should == [[@aaaaa], []]
692
+ nodes[1].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[nil], []]
693
+
694
+ nodes[2].associations[:children].should == [@ba, @bb]
695
+ nodes[2].associations[:children].map{|c1| c1.associations[:children]}.should == [[], []]
696
+
697
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.children}}.should == [[[@aaaa, @aaab], []], [[], []]]
698
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.children.map{|c3| c3.children}}}.should == [[[[@aaaaa], []], []], [[], []]]
699
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.children.map{|c3| c3.children.map{|c4| c4.children}}}}.should == [[[[[]], []], []], [[], []]]
700
+ nodes[1].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.children}}.should == [[[]], []]
701
+ end
702
+
703
+ specify "should populate all :children associations when lazily loading descendants" do
704
+ @a.descendants
705
+ @a.associations[:children].should == [@aa, @ab]
706
+ @a.associations[:children].map{|c1| c1.associations[:children]}.should == [[@aaa, @aab], [@aba, @abb]]
707
+ @a.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[[@aaaa, @aaab], []], [[], []]]
708
+ @a.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children].map{|c3| c3.associations[:children]}}}.should == [[[[@aaaaa], []], []], [[], []]]
709
+ @a.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children].map{|c3| c3.associations[:children].map{|c4| c4.associations[:children]}}}}.should == [[[[[]], []], []], [[], []]]
710
+
711
+ @b.descendants
712
+ @b.associations[:children].should == [@ba, @bb]
713
+ @b.associations[:children].map{|c1| c1.associations[:children]}.should == [[], []]
714
+
715
+ @aaa.descendants
716
+ @aaa.associations[:children].map{|c1| c1.associations[:children]}.should == [[@aaaaa], []]
717
+ @aaa.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[[]], []]
718
+ end
719
+
720
+ specify "should populate all :parent associations when eagerly loading ancestors for a dataset" do
721
+ nodes = Node.filter(:id=>[@a.id, @ba.id, @aaa.id, @aaaaa.id]).order(:name).eager(:ancestors).all
722
+ nodes[0].associations.fetch(:parent, 1).should == nil
723
+ nodes[1].associations[:parent].should == @aa
724
+ nodes[1].associations[:parent].associations[:parent].should == @a
725
+ nodes[1].associations[:parent].associations[:parent].associations.fetch(:parent, 1) == nil
726
+ nodes[2].associations[:parent].should == @aaaa
727
+ nodes[2].associations[:parent].associations[:parent].should == @aaa
728
+ nodes[2].associations[:parent].associations[:parent].associations[:parent].should == @aa
729
+ nodes[2].associations[:parent].associations[:parent].associations[:parent].associations[:parent].should == @a
730
+ nodes[2].associations[:parent].associations[:parent].associations[:parent].associations[:parent].associations.fetch(:parent, 1).should == nil
731
+ nodes[3].associations[:parent].should == @b
732
+ nodes[3].associations[:parent].associations.fetch(:parent, 1).should == nil
733
+ end
734
+
735
+ specify "should populate all :parent associations when lazily loading ancestors" do
736
+ @a.reload
737
+ @a.ancestors
738
+ @a.associations[:parent].should == nil
739
+
740
+ @ba.reload
741
+ @ba.ancestors
742
+ @ba.associations[:parent].should == @b
743
+ @ba.associations[:parent].associations.fetch(:parent, 1).should == nil
744
+
745
+ @ba.reload
746
+ @aaaaa.ancestors
747
+ @aaaaa.associations[:parent].should == @aaaa
748
+ @aaaaa.associations[:parent].associations[:parent].should == @aaa
749
+ @aaaaa.associations[:parent].associations[:parent].associations[:parent].should == @aa
750
+ @aaaaa.associations[:parent].associations[:parent].associations[:parent].associations[:parent].should == @a
751
+ @aaaaa.associations[:parent].associations[:parent].associations[:parent].associations[:parent].associations.fetch(:parent, 1).should == nil
752
+ end
753
+ end
754
+ end