sequel 2.6.0 → 2.7.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 (50) hide show
  1. data/CHANGELOG +64 -0
  2. data/Rakefile +1 -1
  3. data/lib/sequel_core/adapters/jdbc.rb +6 -2
  4. data/lib/sequel_core/adapters/jdbc/oracle.rb +23 -0
  5. data/lib/sequel_core/adapters/oracle.rb +4 -77
  6. data/lib/sequel_core/adapters/postgres.rb +39 -26
  7. data/lib/sequel_core/adapters/shared/mssql.rb +0 -1
  8. data/lib/sequel_core/adapters/shared/mysql.rb +1 -1
  9. data/lib/sequel_core/adapters/shared/oracle.rb +82 -0
  10. data/lib/sequel_core/adapters/shared/postgres.rb +65 -46
  11. data/lib/sequel_core/core_ext.rb +10 -0
  12. data/lib/sequel_core/core_sql.rb +7 -0
  13. data/lib/sequel_core/database.rb +22 -0
  14. data/lib/sequel_core/database/schema.rb +1 -1
  15. data/lib/sequel_core/dataset.rb +29 -11
  16. data/lib/sequel_core/dataset/sql.rb +27 -7
  17. data/lib/sequel_core/migration.rb +20 -2
  18. data/lib/sequel_core/object_graph.rb +24 -10
  19. data/lib/sequel_core/schema/generator.rb +22 -9
  20. data/lib/sequel_core/schema/sql.rb +13 -9
  21. data/lib/sequel_core/sql.rb +27 -2
  22. data/lib/sequel_model/association_reflection.rb +251 -141
  23. data/lib/sequel_model/associations.rb +114 -61
  24. data/lib/sequel_model/base.rb +25 -21
  25. data/lib/sequel_model/eager_loading.rb +17 -40
  26. data/lib/sequel_model/hooks.rb +25 -24
  27. data/lib/sequel_model/record.rb +29 -51
  28. data/lib/sequel_model/schema.rb +1 -1
  29. data/lib/sequel_model/validations.rb +13 -3
  30. data/spec/adapters/postgres_spec.rb +104 -18
  31. data/spec/adapters/spec_helper.rb +4 -1
  32. data/spec/integration/eager_loader_test.rb +5 -4
  33. data/spec/integration/spec_helper.rb +4 -1
  34. data/spec/sequel_core/connection_pool_spec.rb +24 -24
  35. data/spec/sequel_core/core_sql_spec.rb +12 -0
  36. data/spec/sequel_core/dataset_spec.rb +77 -2
  37. data/spec/sequel_core/expression_filters_spec.rb +6 -0
  38. data/spec/sequel_core/object_graph_spec.rb +40 -2
  39. data/spec/sequel_core/schema_spec.rb +13 -0
  40. data/spec/sequel_model/association_reflection_spec.rb +8 -8
  41. data/spec/sequel_model/associations_spec.rb +164 -3
  42. data/spec/sequel_model/caching_spec.rb +2 -1
  43. data/spec/sequel_model/eager_loading_spec.rb +107 -3
  44. data/spec/sequel_model/hooks_spec.rb +38 -22
  45. data/spec/sequel_model/model_spec.rb +11 -35
  46. data/spec/sequel_model/plugins_spec.rb +4 -2
  47. data/spec/sequel_model/record_spec.rb +8 -5
  48. data/spec/sequel_model/validations_spec.rb +25 -0
  49. data/spec/spec_config.rb +4 -3
  50. metadata +21 -19
@@ -18,21 +18,21 @@ describe Sequel::Model::Associations::AssociationReflection, "#associated_class"
18
18
  end
19
19
  end
20
20
 
21
- describe Sequel::Model::Associations::AssociationReflection, "#associated_primary_key" do
21
+ describe Sequel::Model::Associations::AssociationReflection, "#primary_key" do
22
22
  before do
23
23
  @c = Class.new(Sequel::Model)
24
24
  class ::ParParent < Sequel::Model; end
25
25
  end
26
26
 
27
- it "should use the :right_primary_key value if present" do
28
- @c.many_to_one :c, :class=>ParParent, :associated_primary_key=>:blah__blah
29
- @c.association_reflection(:c).should include(:associated_primary_key)
30
- @c.association_reflection(:c).associated_primary_key.should == :blah__blah
27
+ it "should use the :primary_key value if present" do
28
+ @c.many_to_one :c, :class=>ParParent, :primary_key=>:blah__blah
29
+ @c.association_reflection(:c).should include(:primary_key)
30
+ @c.association_reflection(:c).primary_key.should == :blah__blah
31
31
  end
32
- it "should use the associated table's primary key if :associated_primary_key is not present" do
32
+ it "should use the associated table's primary key if :primary_key is not present" do
33
33
  @c.many_to_one :c, :class=>'ParParent'
34
- @c.association_reflection(:c).should_not include(:associated_primary_key)
35
- @c.association_reflection(:c).associated_primary_key.should == :id
34
+ @c.association_reflection(:c).should_not include(:primary_key)
35
+ @c.association_reflection(:c).primary_key.should == :id
36
36
  end
37
37
  end
38
38
 
@@ -46,7 +46,7 @@ end
46
46
  describe Sequel::Model, "many_to_one" do
47
47
  before do
48
48
  MODEL_DB.reset
49
-
49
+
50
50
  @c2 = Class.new(Sequel::Model(:nodes)) do
51
51
  unrestrict_primary_key
52
52
  columns :id, :parent_id, :par_parent_id, :blah
@@ -105,6 +105,12 @@ describe Sequel::Model, "many_to_one" do
105
105
  MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.id = 567) LIMIT 1"]
106
106
  end
107
107
 
108
+ it "should use :primary_key option if given" do
109
+ @c2.many_to_one :parent, :class => @c2, :key => :blah, :primary_key => :pk
110
+ @c2.new(:id => 1, :blah => 567).parent
111
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.pk = 567) LIMIT 1"]
112
+ end
113
+
108
114
  it "should use :select option if given" do
109
115
  @c2.many_to_one :parent, :class => @c2, :key => :blah, :select=>[:id, :name]
110
116
  @c2.new(:id => 1, :blah => 567).parent
@@ -157,6 +163,21 @@ describe Sequel::Model, "many_to_one" do
157
163
  d.values.should == {:id => 1, :parent_id => 6677}
158
164
  end
159
165
 
166
+ it "should have the setter method respect the :primary_key option" do
167
+ @c2.many_to_one :parent, :class => @c2, :primary_key=>:blah
168
+
169
+ d = @c2.new(:id => 1)
170
+ d.parent = @c2.new(:id => 4321, :blah=>444)
171
+ d.values.should == {:id => 1, :parent_id => 444}
172
+
173
+ d.parent = nil
174
+ d.values.should == {:id => 1, :parent_id => nil}
175
+
176
+ e = @c2.new(:id => 6677, :blah=>8)
177
+ d.parent = e
178
+ d.values.should == {:id => 1, :parent_id => 8}
179
+ end
180
+
160
181
  it "should not persist changes until saved" do
161
182
  @c2.many_to_one :parent, :class => @c2
162
183
 
@@ -289,6 +310,14 @@ describe Sequel::Model, "many_to_one" do
289
310
  @c2.instance_methods.collect{|x| x.to_s}.should_not(include('parent='))
290
311
  end
291
312
 
313
+ it "should not add associations methods directly to class" do
314
+ @c2.many_to_one :parent, :class => @c2
315
+ @c2.instance_methods.collect{|x| x.to_s}.should(include('parent'))
316
+ @c2.instance_methods.collect{|x| x.to_s}.should(include('parent='))
317
+ @c2.instance_methods(false).collect{|x| x.to_s}.should_not(include('parent'))
318
+ @c2.instance_methods(false).collect{|x| x.to_s}.should_not(include('parent='))
319
+ end
320
+
292
321
  it "should raise an error if trying to set a model object that doesn't have a valid primary key" do
293
322
  @c2.many_to_one :parent, :class => @c2
294
323
  p = @c2.new
@@ -520,6 +549,18 @@ describe Sequel::Model, "one_to_many" do
520
549
  MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE (id = 2345)']
521
550
  end
522
551
 
552
+ it "should have add_ method respect the :primary_key option" do
553
+ @c2.one_to_many :attributes, :class => @c1, :primary_key=>:xxx
554
+
555
+ n = @c2.new(:id => 1234, :xxx=>5)
556
+ a = @c1.new(:id => 2345)
557
+ a.save!
558
+ MODEL_DB.reset
559
+ a.should == n.add_attribute(a)
560
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = 5 WHERE (id = 2345)']
561
+ end
562
+
563
+
523
564
  it "should raise an error in add_ and remove_ if the passed object returns false to save (is not valid)" do
524
565
  @c2.one_to_many :attributes, :class => @c1
525
566
  n = @c2.new(:id => 1234)
@@ -540,6 +581,12 @@ describe Sequel::Model, "one_to_many" do
540
581
  proc{a.remove_all_attributes}.should raise_error(Sequel::Error)
541
582
  end
542
583
 
584
+ it "should use :primary_key option if given" do
585
+ @c1.one_to_many :nodes, :class => @c2, :primary_key => :node_id, :key=>:id
586
+ n = @c1.load(:id => 1234, :node_id=>4321)
587
+ n.nodes_dataset.sql.should == "SELECT * FROM nodes WHERE (nodes.id = 4321)"
588
+ end
589
+
543
590
  it "should support a select option" do
544
591
  @c2.one_to_many :attributes, :class => @c1, :select => [:id, :name]
545
592
 
@@ -727,6 +774,22 @@ describe Sequel::Model, "one_to_many" do
727
774
  im.should_not(include('remove_all_attributes'))
728
775
  end
729
776
 
777
+ it "should not add associations methods directly to class" do
778
+ @c2.one_to_many :attributes, :class => @c1
779
+ im = @c2.instance_methods.collect{|x| x.to_s}
780
+ im.should(include('attributes'))
781
+ im.should(include('attributes_dataset'))
782
+ im.should(include('add_attribute'))
783
+ im.should(include('remove_attribute'))
784
+ im.should(include('remove_all_attributes'))
785
+ im2 = @c2.instance_methods(false).collect{|x| x.to_s}
786
+ im2.should_not(include('attributes'))
787
+ im2.should_not(include('attributes_dataset'))
788
+ im2.should_not(include('add_attribute'))
789
+ im2.should_not(include('remove_attribute'))
790
+ im2.should_not(include('remove_all_attributes'))
791
+ end
792
+
730
793
  it "should have has_many alias" do
731
794
  @c2.has_many :attributes, :class => @c1
732
795
 
@@ -777,6 +840,12 @@ describe Sequel::Model, "one_to_many" do
777
840
  MODEL_DB.sqls.first.should == 'UPDATE attributes SET node_id = NULL WHERE (node_id = 1234)'
778
841
  end
779
842
 
843
+ it "should have the remove_all_ method respect the :primary_key option" do
844
+ @c2.one_to_many :attributes, :class => @c1, :primary_key=>:xxx
845
+ @c2.new(:id => 1234, :xxx=>5).remove_all_attributes
846
+ MODEL_DB.sqls.first.should == 'UPDATE attributes SET node_id = NULL WHERE (node_id = 5)'
847
+ end
848
+
780
849
  it "remove_all should set the cached instance variable to []" do
781
850
  @c2.one_to_many :attributes, :class => @c1
782
851
  node = @c2.new(:id => 1234)
@@ -828,7 +897,7 @@ describe Sequel::Model, "one_to_many" do
828
897
  att.values.should == {}
829
898
  end
830
899
 
831
- it "should not add a getter method if the :one_to_one option is true and :read_only option is true" do
900
+ it "should not add a setter method if the :one_to_one option is true and :read_only option is true" do
832
901
  @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true, :read_only=>true
833
902
  im = @c2.instance_methods.collect{|x| x.to_s}
834
903
  im.should(include('attribute'))
@@ -861,6 +930,25 @@ describe Sequel::Model, "one_to_many" do
861
930
  MODEL_DB.sqls.last.should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))'
862
931
  end
863
932
 
933
+ it "should have the setter method for the :one_to_one option respect the :primary_key option" do
934
+ @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true, :primary_key=>:xxx
935
+ attrib = @c1.new(:id=>3)
936
+ d = @c1.dataset
937
+ def d.fetch_rows(s); yield({:id=>3}) end
938
+ @c2.new(:id => 1234, :xxx=>5).attribute = attrib
939
+ ['INSERT INTO attributes (node_id, id) VALUES (5, 3)',
940
+ 'INSERT INTO attributes (id, node_id) VALUES (3, 5)'].should(include(MODEL_DB.sqls.first))
941
+ MODEL_DB.sqls.last.should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 5) AND (id != 3))'
942
+ MODEL_DB.sqls.length.should == 2
943
+ @c2.new(:id => 321, :xxx=>5).attribute.should == attrib
944
+ MODEL_DB.sqls.clear
945
+ attrib = @c1.load(:id=>3)
946
+ @c2.new(:id => 621, :xxx=>5).attribute = attrib
947
+ MODEL_DB.sqls.length.should == 2
948
+ MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id = 5, id = 3|id = 3, node_id = 5) WHERE \(id = 3\)/
949
+ MODEL_DB.sqls.last.should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 5) AND (id != 3))'
950
+ end
951
+
864
952
  it "should raise an error if the one_to_one getter would be the same as the association name" do
865
953
  proc{@c2.one_to_many :song, :class => @c1, :one_to_one=>true}.should raise_error(Sequel::Error)
866
954
  end
@@ -873,7 +961,7 @@ describe Sequel::Model, "one_to_many" do
873
961
 
874
962
  it "should make non getter and setter methods private if :one_to_one option is used" do
875
963
  @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true do |ds| end
876
- meths = @c2.private_instance_methods(false).collect{|x| x.to_s}
964
+ meths = @c2.private_instance_methods.collect{|x| x.to_s}
877
965
  meths.should(include("attributes"))
878
966
  meths.should(include("add_attribute"))
879
967
  meths.should(include("attributes_dataset"))
@@ -994,6 +1082,7 @@ describe Sequel::Model, "many_to_many" do
994
1082
 
995
1083
  @c1 = Class.new(Sequel::Model(:attributes)) do
996
1084
  unrestrict_primary_key
1085
+ attr_accessor :yyy
997
1086
  def self.name; 'Attribute'; end
998
1087
  def self.to_s; 'Attribute'; end
999
1088
  columns :id
@@ -1081,6 +1170,11 @@ describe Sequel::Model, "many_to_many" do
1081
1170
  a.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) ORDER BY blah1, blah2'
1082
1171
  end
1083
1172
 
1173
+ it "should support :left_primary_key and :right_primary_key options" do
1174
+ @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx, :right_primary_key=>:yyy
1175
+ @c2.new(:id => 1234, :xxx=>5).attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.yyy) AND (attributes_nodes.node_id = 5))'
1176
+ end
1177
+
1084
1178
  it "should support a select option" do
1085
1179
  @c2.many_to_many :attributes, :class => @c1, :select => :blah
1086
1180
 
@@ -1182,6 +1276,26 @@ describe Sequel::Model, "many_to_many" do
1182
1276
  MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 2345))'
1183
1277
  end
1184
1278
 
1279
+ it "should have the add_ method respect the :left_primary_key and :right_primary_key options" do
1280
+ @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx, :right_primary_key=>:yyy
1281
+
1282
+ n = @c2.new(:id => 1234, :xxx=>5)
1283
+ a = @c1.new(:id => 2345, :yyy=>8)
1284
+ a.should == n.add_attribute(a)
1285
+ ['INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (5, 8)',
1286
+ 'INSERT INTO attributes_nodes (attribute_id, node_id) VALUES (8, 5)'
1287
+ ].should(include(MODEL_DB.sqls.first))
1288
+ end
1289
+
1290
+ it "should have the remove_ method respect the :left_primary_key and :right_primary_key options" do
1291
+ @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx, :right_primary_key=>:yyy
1292
+
1293
+ n = @c2.new(:id => 1234, :xxx=>5)
1294
+ a = @c1.new(:id => 2345, :yyy=>8)
1295
+ a.should == n.remove_attribute(a)
1296
+ MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE ((node_id = 5) AND (attribute_id = 8))'
1297
+ end
1298
+
1185
1299
  it "should raise an error if the model object doesn't have a valid primary key" do
1186
1300
  @c2.many_to_many :attributes, :class => @c1
1187
1301
  a = @c2.new
@@ -1300,6 +1414,22 @@ describe Sequel::Model, "many_to_many" do
1300
1414
  im.should_not(include('remove_all_attributes'))
1301
1415
  end
1302
1416
 
1417
+ it "should not add associations methods directly to class" do
1418
+ @c2.many_to_many :attributes, :class => @c1
1419
+ im = @c2.instance_methods.collect{|x| x.to_s}
1420
+ im.should(include('attributes'))
1421
+ im.should(include('attributes_dataset'))
1422
+ im.should(include('add_attribute'))
1423
+ im.should(include('remove_attribute'))
1424
+ im.should(include('remove_all_attributes'))
1425
+ im2 = @c2.instance_methods(false).collect{|x| x.to_s}
1426
+ im2.should_not(include('attributes'))
1427
+ im2.should_not(include('attributes_dataset'))
1428
+ im2.should_not(include('add_attribute'))
1429
+ im2.should_not(include('remove_attribute'))
1430
+ im2.should_not(include('remove_all_attributes'))
1431
+ end
1432
+
1303
1433
  it "should have has_and_belongs_to_many alias" do
1304
1434
  @c2.has_and_belongs_to_many :attributes, :class => @c1
1305
1435
 
@@ -1315,6 +1445,12 @@ describe Sequel::Model, "many_to_many" do
1315
1445
  MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE (node_id = 1234)'
1316
1446
  end
1317
1447
 
1448
+ it "should have the remove_all_ method respect the :left_primary_key option" do
1449
+ @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx
1450
+ @c2.new(:id => 1234, :xxx=>5).remove_all_attributes
1451
+ MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE (node_id = 5)'
1452
+ end
1453
+
1318
1454
  it "remove_all should set the cached instance variable to []" do
1319
1455
  @c2.many_to_many :attributes, :class => @c1
1320
1456
  node = @c2.new(:id => 1234)
@@ -1463,6 +1599,20 @@ describe Sequel::Model, "many_to_many" do
1463
1599
  p.remove_attribute(c).should == nil
1464
1600
  p.attributes.should == [c]
1465
1601
  end
1602
+
1603
+ it "should support a :uniq option that removes duplicates from the association" do
1604
+ h = []
1605
+ @c2.many_to_many :attributes, :class => @c1, :uniq=>true
1606
+ @c1.class_eval do
1607
+ def @dataset.fetch_rows(sql)
1608
+ yield({:id=>20})
1609
+ yield({:id=>30})
1610
+ yield({:id=>20})
1611
+ yield({:id=>30})
1612
+ end
1613
+ end
1614
+ @c2.load(:id=>10, :parent_id=>20).attributes.should == [@c1.load(:id=>20), @c1.load(:id=>30)]
1615
+ end
1466
1616
  end
1467
1617
 
1468
1618
  describe Sequel::Model, " association reflection methods" do
@@ -1514,4 +1664,15 @@ describe Sequel::Model, " association reflection methods" do
1514
1664
  @c1.associate :one_to_many, :children, :class => @c1
1515
1665
  @c1.associations.sort_by{|x|x.to_s}.should == [:children, :parent]
1516
1666
  end
1667
+
1668
+ it "association reflections should be copied upon subclasing" do
1669
+ @c1.associate :many_to_one, :parent, :class => @c1
1670
+ c = Class.new(@c1)
1671
+ @c1.associations.should == [:parent]
1672
+ c.associations.should == [:parent]
1673
+ c.associate :many_to_one, :parent2, :class => @c1
1674
+ @c1.associations.should == [:parent]
1675
+ c.associations.sort_by{|x| x.to_s}.should == [:parent, :parent2]
1676
+ c.instance_methods.map{|x| x.to_s}.should include('parent')
1677
+ end
1517
1678
  end
@@ -12,7 +12,8 @@ describe Sequel::Model, "caching" do
12
12
  cache = @cache_class.new
13
13
  @cache = cache
14
14
 
15
- @c = Class.new(Sequel::Model(:items)) do
15
+ @c = Class.new(Sequel::Model(:items))
16
+ @c.class_eval do
16
17
  set_cache cache
17
18
  def self.name; 'Item' end
18
19
 
@@ -18,7 +18,7 @@ describe Sequel::Model, "#eager" do
18
18
  end
19
19
 
20
20
  class ::EagerBand < Sequel::Model(:bands)
21
- columns :id
21
+ columns :id, :p_k
22
22
  one_to_many :albums, :class=>'EagerAlbum', :key=>:band_id, :eager=>:tracks
23
23
  one_to_many :graph_albums, :class=>'EagerAlbum', :key=>:band_id, :eager_graph=>:tracks
24
24
  many_to_many :members, :class=>'EagerBandMember', :left_key=>:band_id, :right_key=>:member_id, :join_table=>:bm
@@ -38,7 +38,7 @@ describe Sequel::Model, "#eager" do
38
38
  end
39
39
 
40
40
  class ::EagerGenre < Sequel::Model(:genres)
41
- columns :id
41
+ columns :id, :xxx
42
42
  many_to_many :albums, :class=>'EagerAlbum', :left_key=>:genre_id, :right_key=>:album_id, :join_table=>:ag
43
43
  end
44
44
 
@@ -418,6 +418,47 @@ describe Sequel::Model, "#eager" do
418
418
  MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT id, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (1)))"]
419
419
  end
420
420
 
421
+ it "should respect the association's :primary_key option" do
422
+ EagerAlbum.many_to_one :special_band, :class=>:EagerBand, :primary_key=>:p_k, :key=>:band_id
423
+ EagerBand.dataset.extend(Module.new {
424
+ def fetch_rows(sql)
425
+ MODEL_DB.sqls << sql
426
+ yield({:p_k=>2, :id=>1})
427
+ end
428
+ })
429
+ as = EagerAlbum.eager(:special_band).all
430
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM bands WHERE (bands.p_k IN (2))"]
431
+ as.length.should == 1
432
+ as.first.special_band.should == EagerBand.load(:p_k=>2, :id=>1)
433
+ MODEL_DB.sqls.clear
434
+ EagerAlbum.one_to_many :special_tracks, :class=>:EagerTrack, :primary_key=>:band_id, :key=>:album_id
435
+ EagerTrack.dataset.extend(Module.new {
436
+ def fetch_rows(sql)
437
+ MODEL_DB.sqls << sql
438
+ yield({:album_id=>2, :id=>1})
439
+ end
440
+ })
441
+ as = EagerAlbum.eager(:special_tracks).all
442
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM tracks WHERE (tracks.album_id IN (2))"]
443
+ as.length.should == 1
444
+ as.first.special_tracks.should == [EagerTrack.load(:album_id=>2, :id=>1)]
445
+ end
446
+
447
+ it "should respect many_to_many association's :left_primary_key and :right_primary_key options" do
448
+ EagerAlbum.many_to_many :special_genres, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_primary_key=>:xxx, :right_key=>:genre_id, :join_table=>:ag
449
+ EagerGenre.dataset.extend(Module.new {
450
+ def fetch_rows(sql)
451
+ MODEL_DB.sqls << sql
452
+ yield({:x_foreign_key_x=>2, :id=>5})
453
+ yield({:x_foreign_key_x=>2, :id=>6})
454
+ end
455
+ })
456
+ as = EagerAlbum.eager(:special_genres).all
457
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT genres.*, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.xxx) AND (ag.album_id IN (2)))"]
458
+ as.length.should == 1
459
+ as.first.special_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
460
+ end
461
+
421
462
  it "should use the :eager_loader association option when eager loading" do
422
463
  EagerAlbum.many_to_one :special_band, :eager_loader=>(proc do |key_hash, records, assocs|
423
464
  item = EagerBand.filter(:album_id=>records.collect{|r| [r.pk, r.pk*2]}.flatten).order(:name).first
@@ -427,7 +468,7 @@ describe Sequel::Model, "#eager" do
427
468
  items = EagerTrack.filter(:album_id=>records.collect{|r| [r.pk, r.pk*2]}.flatten).all
428
469
  records.each{|r| r.associations[:special_tracks] = items}
429
470
  end)
430
- EagerAlbum.many_to_many :special_genres, :eager_loader=>(proc do |key_hash, records, assocs|
471
+ EagerAlbum.many_to_many :special_genres, :class=>:EagerGenre, :eager_loader=>(proc do |key_hash, records, assocs|
431
472
  items = EagerGenre.inner_join(:ag, [:genre_id]).filter(:album_id=>records.collect{|r| r.pk}).all
432
473
  records.each{|r| r.associations[:special_genres] = items}
433
474
  end)
@@ -455,6 +496,16 @@ describe Sequel::Model, "#eager" do
455
496
  MODEL_DB.sqls.length.should == 4
456
497
  end
457
498
 
499
+ it "should respect :after_load callbacks on associations when eager loading" do
500
+ EagerAlbum.many_to_one :al_band, :class=>'EagerBand', :key=>:band_id, :after_load=>proc{|o, a| a.id *=2}
501
+ EagerAlbum.one_to_many :al_tracks, :class=>'EagerTrack', :key=>:album_id, :after_load=>proc{|o, os| os.each{|a| a.id *=2}}
502
+ EagerAlbum.many_to_many :al_genres, :class=>'EagerGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :after_load=>proc{|o, os| os.each{|a| a.id *=2}}
503
+ a = EagerAlbum.eager(:al_band, :al_tracks, :al_genres).all.first
504
+ a.should == EagerAlbum.load(:id => 1, :band_id => 2)
505
+ a.al_band.should == EagerBand.load(:id=>4)
506
+ a.al_tracks.should == [EagerTrack.load(:id=>6, :album_id=>1)]
507
+ a.al_genres.should == [EagerGenre.load(:id=>8)]
508
+ end
458
509
  end
459
510
 
460
511
  describe Sequel::Model, "#eager_graph" do
@@ -902,6 +953,42 @@ describe Sequel::Model, "#eager_graph" do
902
953
  a[3].album.band.members.last.values.should == {:id => 6}
903
954
  end
904
955
 
956
+ it "should respect the association's :primary_key option" do
957
+ GraphAlbum.many_to_one :inner_band, :class=>'GraphBand', :key=>:band_id, :primary_key=>:vocalist_id
958
+ ds = GraphAlbum.eager_graph(:inner_band)
959
+ ds.sql.should == 'SELECT albums.id, albums.band_id, inner_band.id AS inner_band_id, inner_band.vocalist_id FROM albums LEFT OUTER JOIN bands AS inner_band ON (inner_band.vocalist_id = albums.band_id)'
960
+ def ds.fetch_rows(sql, &block)
961
+ yield({:id=>3, :band_id=>2, :inner_band_id=>5, :vocalist_id=>2})
962
+ end
963
+ as = ds.all
964
+ as.should == [GraphAlbum.load(:id=>3, :band_id=>2)]
965
+ as.first.inner_band.should == GraphBand.load(:id=>5, :vocalist_id=>2)
966
+
967
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :primary_key=>:band_id
968
+ ds = GraphAlbum.eager_graph(:right_tracks)
969
+ ds.sql.should == 'SELECT albums.id, albums.band_id, right_tracks.id AS right_tracks_id, right_tracks.album_id FROM albums LEFT OUTER JOIN tracks AS right_tracks ON (right_tracks.album_id = albums.band_id)'
970
+ def ds.fetch_rows(sql, &block)
971
+ yield({:id=>3, :band_id=>2, :right_tracks_id=>5, :album_id=>2})
972
+ yield({:id=>3, :band_id=>2, :right_tracks_id=>6, :album_id=>2})
973
+ end
974
+ as = ds.all
975
+ as.should == [GraphAlbum.load(:id=>3, :band_id=>2)]
976
+ as.first.right_tracks.should == [GraphTrack.load(:id=>5, :album_id=>2), GraphTrack.load(:id=>6, :album_id=>2)]
977
+ end
978
+
979
+ it "should respect many_to_many association's :left_primary_key and :right_primary_key options" do
980
+ GraphAlbum.many_to_many :inner_genres, :class=>'GraphGenre', :left_key=>:album_id, :left_primary_key=>:band_id, :right_key=>:genre_id, :right_primary_key=>:xxx, :join_table=>:ag
981
+ ds = GraphAlbum.eager_graph(:inner_genres)
982
+ ds.sql.should == 'SELECT albums.id, albums.band_id, inner_genres.id AS inner_genres_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.band_id) LEFT OUTER JOIN genres AS inner_genres ON (inner_genres.xxx = ag.genre_id)'
983
+ def ds.fetch_rows(sql, &block)
984
+ yield({:id=>3, :band_id=>2, :inner_genres_id=>5, :xxx=>12})
985
+ yield({:id=>3, :band_id=>2, :inner_genres_id=>6, :xxx=>22})
986
+ end
987
+ as = ds.all
988
+ as.should == [GraphAlbum.load(:id=>3, :band_id=>2)]
989
+ as.first.inner_genres.should == [GraphGenre.load(:id=>5), GraphGenre.load(:id=>6)]
990
+ end
991
+
905
992
  it "should respect the association's :graph_select option" do
906
993
  GraphAlbum.many_to_one :inner_band, :class=>'GraphBand', :key=>:band_id, :graph_select=>:vocalist_id
907
994
  GraphAlbum.eager_graph(:inner_band).sql.should == 'SELECT albums.id, albums.band_id, inner_band.vocalist_id FROM albums LEFT OUTER JOIN bands AS inner_band ON (inner_band.id = albums.band_id)'
@@ -970,6 +1057,17 @@ describe Sequel::Model, "#eager_graph" do
970
1057
  GraphAlbum.eager_graph(:active_genres).sql.should == "SELECT albums.id, albums.band_id, active_genres.id AS active_genres_id FROM albums LEFT OUTER JOIN ag ON ((ag.album_id = albums.id) AND ('t' = albums.active)) LEFT OUTER JOIN genres AS active_genres ON ((active_genres.id = ag.genre_id) AND ('t' = ag.active))"
971
1058
  end
972
1059
 
1060
+ it "should respect the association's :eager_grapher option" do
1061
+ GraphAlbum.many_to_one :active_band, :class=>'GraphBand', :key=>:band_id, :eager_grapher=>proc{|ds, aa, ta| ds.graph(GraphBand, {:active=>true}, :table_alias=>aa, :join_type=>:inner)}
1062
+ GraphAlbum.eager_graph(:active_band).sql.should == "SELECT albums.id, albums.band_id, active_band.id AS active_band_id, active_band.vocalist_id FROM albums INNER JOIN bands AS active_band ON (active_band.active = 't')"
1063
+
1064
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :eager_grapher=>proc{|ds, aa, ta| ds.graph(GraphTrack, nil, :join_type=>:natural, :table_alias=>aa)}
1065
+ GraphAlbum.eager_graph(:right_tracks).sql.should == 'SELECT albums.id, albums.band_id, right_tracks.id AS right_tracks_id, right_tracks.album_id FROM albums NATURAL JOIN tracks AS right_tracks'
1066
+
1067
+ GraphAlbum.many_to_many :active_genres, :class=>'GraphGenre', :eager_grapher=>proc{|ds, aa, ta| ds.graph(:ag, {:album_id=>:id}, :table_alias=>:a123, :implicit_qualifier=>ta).graph(GraphGenre, [:album_id], :table_alias=>aa)}
1068
+ GraphAlbum.eager_graph(:active_genres).sql.should == "SELECT albums.id, albums.band_id, active_genres.id AS active_genres_id FROM albums LEFT OUTER JOIN ag AS a123 ON (a123.album_id = albums.id) LEFT OUTER JOIN genres AS active_genres USING (album_id)"
1069
+ end
1070
+
973
1071
  it "should respect the association's :graph_only_conditions option" do
974
1072
  GraphAlbum.many_to_one :active_band, :class=>'GraphBand', :key=>:band_id, :graph_only_conditions=>{:active=>true}
975
1073
  GraphAlbum.eager_graph(:active_band).sql.should == "SELECT albums.id, albums.band_id, active_band.id AS active_band_id, active_band.vocalist_id FROM albums LEFT OUTER JOIN bands AS active_band ON (active_band.active = 't')"
@@ -1026,4 +1124,10 @@ describe Sequel::Model, "#eager_graph" do
1026
1124
  GraphAlbum.one_to_many :b_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:id, :album_id]
1027
1125
  GraphAlbum.eager_graph(:a_genres, :b_tracks).sql.should == 'SELECT albums.id, albums.band_id, a_genres.id AS a_genres_id, b_tracks.id AS b_tracks_id, b_tracks.album_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres AS a_genres ON (a_genres.id = ag.genre_id) LEFT OUTER JOIN tracks AS b_tracks ON (b_tracks.album_id = albums.id) ORDER BY a_genres.id, b_tracks.id, b_tracks.album_id'
1028
1126
  end
1127
+
1128
+ it "should use the correct qualifier when graphing multiple tables with extra conditions" do
1129
+ GraphAlbum.many_to_many :a_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag
1130
+ GraphAlbum.one_to_many :b_tracks, :class=>'GraphTrack', :key=>:album_id, :graph_conditions=>{:a=>:b}
1131
+ GraphAlbum.eager_graph(:a_genres, :b_tracks).sql.should == 'SELECT albums.id, albums.band_id, a_genres.id AS a_genres_id, b_tracks.id AS b_tracks_id, b_tracks.album_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres AS a_genres ON (a_genres.id = ag.genre_id) LEFT OUTER JOIN tracks AS b_tracks ON ((b_tracks.album_id = albums.id) AND (b_tracks.a = albums.b))'
1132
+ end
1029
1133
  end