sequel 3.9.0 → 3.10.0

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