sequel 4.7.0 → 4.8.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. checksums.yaml +4 -4
  2. data/CHANGELOG +46 -0
  3. data/README.rdoc +25 -1
  4. data/doc/active_record.rdoc +1 -1
  5. data/doc/advanced_associations.rdoc +143 -17
  6. data/doc/association_basics.rdoc +80 -59
  7. data/doc/release_notes/4.8.0.txt +175 -0
  8. data/lib/sequel/adapters/odbc.rb +1 -1
  9. data/lib/sequel/adapters/odbc/mssql.rb +4 -2
  10. data/lib/sequel/adapters/shared/postgres.rb +19 -3
  11. data/lib/sequel/adapters/shared/sqlite.rb +3 -3
  12. data/lib/sequel/ast_transformer.rb +1 -1
  13. data/lib/sequel/dataset/actions.rb +1 -1
  14. data/lib/sequel/dataset/graph.rb +23 -9
  15. data/lib/sequel/dataset/misc.rb +2 -2
  16. data/lib/sequel/dataset/sql.rb +3 -3
  17. data/lib/sequel/extensions/columns_introspection.rb +1 -1
  18. data/lib/sequel/extensions/mssql_emulate_lateral_with_apply.rb +1 -1
  19. data/lib/sequel/extensions/pg_array.rb +1 -1
  20. data/lib/sequel/extensions/pg_array_ops.rb +6 -0
  21. data/lib/sequel/extensions/pg_hstore_ops.rb +7 -0
  22. data/lib/sequel/extensions/pg_json_ops.rb +5 -0
  23. data/lib/sequel/extensions/query.rb +8 -2
  24. data/lib/sequel/extensions/to_dot.rb +1 -1
  25. data/lib/sequel/model/associations.rb +476 -152
  26. data/lib/sequel/plugins/class_table_inheritance.rb +11 -3
  27. data/lib/sequel/plugins/dataset_associations.rb +21 -18
  28. data/lib/sequel/plugins/many_through_many.rb +87 -20
  29. data/lib/sequel/plugins/nested_attributes.rb +12 -0
  30. data/lib/sequel/plugins/pg_array_associations.rb +31 -12
  31. data/lib/sequel/plugins/single_table_inheritance.rb +9 -1
  32. data/lib/sequel/sql.rb +1 -0
  33. data/lib/sequel/version.rb +1 -1
  34. data/spec/adapters/mssql_spec.rb +2 -2
  35. data/spec/adapters/postgres_spec.rb +7 -0
  36. data/spec/core/object_graph_spec.rb +250 -196
  37. data/spec/extensions/core_refinements_spec.rb +1 -1
  38. data/spec/extensions/dataset_associations_spec.rb +100 -6
  39. data/spec/extensions/many_through_many_spec.rb +1002 -19
  40. data/spec/extensions/nested_attributes_spec.rb +24 -0
  41. data/spec/extensions/pg_array_associations_spec.rb +17 -12
  42. data/spec/extensions/pg_array_spec.rb +4 -2
  43. data/spec/extensions/spec_helper.rb +1 -1
  44. data/spec/integration/associations_test.rb +1003 -48
  45. data/spec/integration/dataset_test.rb +12 -5
  46. data/spec/integration/prepared_statement_test.rb +1 -1
  47. data/spec/integration/type_test.rb +1 -1
  48. data/spec/model/associations_spec.rb +467 -130
  49. data/spec/model/eager_loading_spec.rb +332 -5
  50. metadata +5 -3
@@ -7,6 +7,7 @@ describe Sequel::Model, "#eager" do
7
7
  many_to_one :band, :class=>'EagerBand', :key=>:band_id
8
8
  one_to_many :tracks, :class=>'EagerTrack', :key=>:album_id
9
9
  many_to_many :genres, :class=>'EagerGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag
10
+ one_through_one :genre, :clone=>:genres
10
11
  one_to_many :good_tracks, :class=>'EagerTrack', :reciprocal=>nil, :key=>:album_id do |ds|
11
12
  ds.filter(:name=>'Good')
12
13
  end
@@ -168,10 +169,10 @@ describe Sequel::Model, "#eager" do
168
169
 
169
170
  it "should eagerly load a single one_to_one association using the :distinct_on strategy" do
170
171
  def (EagerTrack.dataset).supports_distinct_on?() true end
171
- EagerAlbum.one_to_one :track, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>true
172
+ EagerAlbum.one_to_one :track, :class=>'EagerTrack', :key=>:album_id, :order=>:a
172
173
  a = EagerAlbum.eager(:track).all
173
174
  a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
174
- DB.sqls.should == ['SELECT * FROM albums', 'SELECT DISTINCT ON (tracks.album_id) * FROM tracks WHERE (tracks.album_id IN (1)) ORDER BY tracks.album_id']
175
+ DB.sqls.should == ['SELECT * FROM albums', 'SELECT DISTINCT ON (tracks.album_id) * FROM tracks WHERE (tracks.album_id IN (1)) ORDER BY tracks.album_id, a']
175
176
  a.first.track.should == EagerTrack.load(:id => 3, :album_id=>1)
176
177
  DB.sqls.should == []
177
178
  end
@@ -215,6 +216,52 @@ describe Sequel::Model, "#eager" do
215
216
  DB.sqls.should == []
216
217
  end
217
218
 
219
+ it "should eagerly load a single one_through_one association" do
220
+ a = EagerAlbum.eager(:genre).all
221
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
222
+ 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.id) AND (ag.album_id IN (1)))"]
223
+ a.first.genre.should == EagerGenre.load(:id=>4)
224
+ DB.sqls.should == []
225
+ end
226
+
227
+ it "should use first matching entry when eager loading one_through_one association" do
228
+ EagerGenre.dataset._fetch = [{:id => 3, :x_foreign_key_x=>1}, {:id => 4, :x_foreign_key_x=>1}]
229
+ a = EagerAlbum.eager(:genre).all
230
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
231
+ 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.id) AND (ag.album_id IN (1)))"]
232
+ a.first.genre.should == EagerGenre.load(:id=>3)
233
+ DB.sqls.should == []
234
+ end
235
+
236
+ it "should eagerly load a single one_through_one association using the :distinct_on strategy" do
237
+ def (EagerGenre.dataset).supports_distinct_on?() true end
238
+ EagerAlbum.one_through_one :genre, :clone=>:genre, :order=>:a
239
+ a = EagerAlbum.eager(:genre).all
240
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
241
+ DB.sqls.should == ['SELECT * FROM albums', "SELECT DISTINCT ON (ag.album_id) genres.*, 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))) ORDER BY ag.album_id, a"]
242
+ a.first.genre.should == EagerGenre.load(:id=>4)
243
+ DB.sqls.should == []
244
+ end
245
+
246
+ it "should eagerly load a single one_through_one association using the :window_function strategy" do
247
+ def (EagerGenre.dataset).supports_window_functions?() true end
248
+ EagerAlbum.one_through_one :genre, :clone=>:genre, :eager_limit_strategy=>true
249
+ a = EagerAlbum.eager(:genre).all
250
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
251
+ DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM (SELECT genres.*, ag.album_id AS x_foreign_key_x, row_number() OVER (PARTITION BY ag.album_id) AS x_sequel_row_number_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (1)))) AS t1 WHERE (x_sequel_row_number_x = 1)"]
252
+ a.first.genre.should == EagerGenre.load(:id=>4)
253
+ DB.sqls.should == []
254
+ end
255
+
256
+ it "should automatically use an eager limit stategy if the association has an offset" do
257
+ EagerGenre.dataset._fetch = [{:id => 3, :x_foreign_key_x=>1}, {:id => 4, :x_foreign_key_x=>1}]
258
+ a = EagerAlbum.eager(:genre).all
259
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
260
+ 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.id) AND (ag.album_id IN (1)))"]
261
+ a.first.genre.should == EagerGenre.load(:id=>3)
262
+ DB.sqls.should == []
263
+ end
264
+
218
265
  it "should eagerly load a single many_to_many association" do
219
266
  a = EagerAlbum.eager(:genres).all
220
267
  a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
@@ -253,6 +300,16 @@ describe Sequel::Model, "#eager" do
253
300
  DB.sqls.should == []
254
301
  end
255
302
 
303
+ it "should support using a custom :left_primary_key option when eager loading one_through_one associations" do
304
+ EagerAlbum.one_through_one :sgenre, :clone=>:genre, :left_primary_key=>:band_id3
305
+ EagerGenre.dataset._fetch = {:id=>4, :x_foreign_key_x=>6}
306
+ a = EagerAlbum.eager(:sgenre).all
307
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
308
+ 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.id) AND (ag.album_id IN (6)))"]
309
+ a.first.sgenre.should == EagerGenre.load(:id=>4)
310
+ DB.sqls.should == []
311
+ end
312
+
256
313
  it "should handle a :eager_loading_predicate_key option to change the SQL used in the lookup, for many_to_one associations" do
257
314
  EagerAlbum.many_to_one :sband, :clone=>:band, :eager_loading_predicate_key=>Sequel./(:bands__id, 3), :primary_key_method=>:id3
258
315
  EagerBand.dataset._fetch = {:id=>6}
@@ -282,6 +339,15 @@ describe Sequel::Model, "#eager" do
282
339
  DB.sqls.should == []
283
340
  end
284
341
 
342
+ it "should handle a :eager_loading_predicate_key option to change the SQL used in the lookup, for one_through_one associations" do
343
+ EagerAlbum.one_through_one :sgenre, :clone=>:genre, :eager_loading_predicate_key=>Sequel.*(:ag__album_id, 1)
344
+ a = EagerAlbum.eager(:sgenre).all
345
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
346
+ DB.sqls.should == ['SELECT * FROM albums', "SELECT genres.*, (ag.album_id * 1) AS x_foreign_key_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND ((ag.album_id * 1) IN (1)))"]
347
+ a.first.sgenre.should == EagerGenre.load(:id=>4)
348
+ DB.sqls.should == []
349
+ end
350
+
285
351
  it "should raise an error for an unhandled :eager_loader_key option" do
286
352
  EagerAlbum.many_to_many :sgenres, :clone=>:genres, :eager_loader_key=>1
287
353
  ds = EagerAlbum.eager(:sgenres)
@@ -303,12 +369,24 @@ describe Sequel::Model, "#eager" do
303
369
  DB.sqls.should == ['SELECT * FROM albums', "SELECT *, 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)))"]
304
370
  end
305
371
 
372
+ it "should correctly handle a :select=>[] option to one_through_one" do
373
+ EagerAlbum.one_through_one :sgenre, :clone=>:genre, :select=>[]
374
+ EagerAlbum.eager(:sgenre).all
375
+ DB.sqls.should == ['SELECT * FROM albums', "SELECT *, 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)))"]
376
+ end
377
+
306
378
  it "should correctly handle an aliased join table in many_to_many" do
307
379
  EagerAlbum.many_to_many :sgenres, :clone=>:genres, :join_table=>:ag___ga
308
380
  EagerAlbum.eager(:sgenres).all
309
381
  DB.sqls.should == ['SELECT * FROM albums', "SELECT genres.*, ga.album_id AS x_foreign_key_x FROM genres INNER JOIN ag AS ga ON ((ga.genre_id = genres.id) AND (ga.album_id IN (1)))"]
310
382
  end
311
383
 
384
+ it "should correctly handle an aliased join table in one_through_one" do
385
+ EagerAlbum.one_through_one :sgenre, :clone=>:genre, :join_table=>:ag___ga
386
+ EagerAlbum.eager(:sgenre).all
387
+ DB.sqls.should == ['SELECT * FROM albums', "SELECT genres.*, ga.album_id AS x_foreign_key_x FROM genres INNER JOIN ag AS ga ON ((ga.genre_id = genres.id) AND (ga.album_id IN (1)))"]
388
+ end
389
+
312
390
  it "should eagerly load multiple associations in a single call" do
313
391
  a = EagerAlbum.eager(:genres, :tracks, :band).all
314
392
  a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
@@ -542,6 +620,15 @@ describe Sequel::Model, "#eager" do
542
620
  as.first.special_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
543
621
  end
544
622
 
623
+ it "should respect one_through_one association's composite keys" do
624
+ EagerAlbum.one_through_one :special_genre, :class=>:EagerGenre, :left_primary_key=>[:band_id, :id], :left_key=>[:l1, :l2], :right_primary_key=>[:xxx, :id], :right_key=>[:r1, :r2], :join_table=>:ag
625
+ EagerGenre.dataset._fetch = [{:x_foreign_key_0_x=>2, :x_foreign_key_1_x=>1, :id=>5}]
626
+ as = EagerAlbum.eager(:special_genre).all
627
+ DB.sqls.should == ['SELECT * FROM albums', "SELECT genres.*, ag.l1 AS x_foreign_key_0_x, ag.l2 AS x_foreign_key_1_x FROM genres INNER JOIN ag ON ((ag.r1 = genres.xxx) AND (ag.r2 = genres.id) AND ((ag.l1, ag.l2) IN ((2, 1))))"]
628
+ as.length.should == 1
629
+ as.first.special_genre.should == EagerGenre.load(:id=>5)
630
+ end
631
+
545
632
  it "should respect many_to_many association's :left_primary_key and :right_primary_key options" do
546
633
  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
547
634
  EagerGenre.dataset._fetch = [{:x_foreign_key_x=>2, :id=>5}, {:x_foreign_key_x=>2, :id=>6}]
@@ -551,6 +638,15 @@ describe Sequel::Model, "#eager" do
551
638
  as.first.special_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
552
639
  end
553
640
 
641
+ it "should respect one_through_one association's :left_primary_key and :right_primary_key options" do
642
+ EagerAlbum.one_through_one :special_genre, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_primary_key=>:xxx, :right_key=>:genre_id, :join_table=>:ag
643
+ EagerGenre.dataset._fetch = [{:x_foreign_key_x=>2, :id=>5}]
644
+ as = EagerAlbum.eager(:special_genre).all
645
+ 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)))"]
646
+ as.length.should == 1
647
+ as.first.special_genre.should == EagerGenre.load(:id=>5)
648
+ end
649
+
554
650
  it "should respect the :limit option on a one_to_many association" do
555
651
  EagerAlbum.one_to_many :first_two_tracks, :class=>:EagerTrack, :key=>:album_id, :limit=>2
556
652
  EagerTrack.dataset._fetch = [{:album_id=>1, :id=>2}, {:album_id=>1, :id=>3}, {:album_id=>1, :id=>4}]
@@ -739,6 +835,14 @@ describe Sequel::Model, "#eager" do
739
835
  DB.sqls.should == []
740
836
  end
741
837
 
838
+ it "should eagerly load a one_through_one association with custom eager block" do
839
+ a = EagerAlbum.eager(:genre => proc {|ds| ds.select(:name)}).all
840
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
841
+ DB.sqls.should == ['SELECT * FROM albums', "SELECT name, 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)))"]
842
+ a.first.genre.should == EagerGenre.load(:id => 4)
843
+ DB.sqls.should == []
844
+ end
845
+
742
846
  it "should allow cascading of eager loading within a custom eager block" do
743
847
  a = EagerTrack.eager(:album => proc {|ds| ds.eager(:band => :members)}).all
744
848
  a.should == [EagerTrack.load(:id => 3, :album_id => 1)]
@@ -807,7 +911,9 @@ describe Sequel::Model, "#eager_graph" do
807
911
  columns :id, :band_id
808
912
  many_to_one :band, :class=>'GraphBand', :key=>:band_id
809
913
  one_to_many :tracks, :class=>'GraphTrack', :key=>:album_id
914
+ one_to_one :track, :class=>'GraphTrack', :key=>:album_id
810
915
  many_to_many :genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag
916
+ one_through_one :genre, :clone=>:genres
811
917
  many_to_one :previous_album, :class=>'GraphAlbum'
812
918
  end
813
919
 
@@ -909,6 +1015,21 @@ describe Sequel::Model, "#eager_graph" do
909
1015
  a.album.band.members.should == [GraphBandMember.load(:id => 5)]
910
1016
  end
911
1017
 
1018
+ it "should set up correct inner joins when using association_join" do
1019
+ GraphAlbum.association_join(:band).sql.should == 'SELECT * FROM albums INNER JOIN bands AS band ON (band.id = albums.band_id)'
1020
+ GraphAlbum.association_join(:track).sql.should == 'SELECT * FROM albums INNER JOIN tracks AS track ON (track.album_id = albums.id)'
1021
+ GraphAlbum.association_join(:tracks).sql.should == 'SELECT * FROM albums INNER JOIN tracks ON (tracks.album_id = albums.id)'
1022
+ GraphAlbum.association_join(:genres).sql.should == 'SELECT * FROM albums INNER JOIN ag ON (ag.album_id = albums.id) INNER JOIN genres ON (genres.id = ag.genre_id)'
1023
+ GraphAlbum.association_join(:genre).sql.should == 'SELECT * FROM albums INNER JOIN ag ON (ag.album_id = albums.id) INNER JOIN genres AS genre ON (genre.id = ag.genre_id)'
1024
+ end
1025
+
1026
+ it "should set up correct join types when using association_*_join" do
1027
+ GraphAlbum.association_inner_join(:band).sql.should == 'SELECT * FROM albums INNER JOIN bands AS band ON (band.id = albums.band_id)'
1028
+ GraphAlbum.association_left_join(:track).sql.should == 'SELECT * FROM albums LEFT JOIN tracks AS track ON (track.album_id = albums.id)'
1029
+ GraphAlbum.association_right_join(:tracks).sql.should == 'SELECT * FROM albums RIGHT JOIN tracks ON (tracks.album_id = albums.id)'
1030
+ GraphAlbum.association_full_join(:genres).sql.should == 'SELECT * FROM albums FULL JOIN ag ON (ag.album_id = albums.id) FULL JOIN genres ON (genres.id = ag.genre_id)'
1031
+ end
1032
+
912
1033
  it "should eagerly load a single many_to_one association" do
913
1034
  ds = GraphAlbum.eager_graph(:band)
914
1035
  ds.sql.should == 'SELECT albums.id, albums.band_id, band.id AS band_id_0, band.vocalist_id FROM albums LEFT OUTER JOIN bands AS band ON (band.id = albums.band_id)'
@@ -929,8 +1050,16 @@ describe Sequel::Model, "#eager_graph" do
929
1050
  a.first.band_id.should == GraphBand.load(:id => 2, :vocalist_id=>3)
930
1051
  end
931
1052
 
1053
+ it "should support :join_type eager_graph option one_to_one association" do
1054
+ ds = GraphAlbum.eager_graph_with_options(:track, :join_type=>:inner)
1055
+ ds.sql.should == 'SELECT albums.id, albums.band_id, track.id AS track_id, track.album_id FROM albums INNER JOIN tracks AS track ON (track.album_id = albums.id)'
1056
+ ds._fetch = {:id=>1, :band_id=>2, :track_id=>3, :album_id=>1}
1057
+ a = ds.all
1058
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1059
+ a.first.track.should == GraphTrack.load(:id => 3, :album_id=>1)
1060
+ end
1061
+
932
1062
  it "should eagerly load a single one_to_one association" do
933
- GraphAlbum.one_to_one :track, :class=>'GraphTrack', :key=>:album_id
934
1063
  ds = GraphAlbum.eager_graph(:track)
935
1064
  ds.sql.should == 'SELECT albums.id, albums.band_id, track.id AS track_id, track.album_id FROM albums LEFT OUTER JOIN tracks AS track ON (track.album_id = albums.id)'
936
1065
  ds._fetch = {:id=>1, :band_id=>2, :track_id=>3, :album_id=>1}
@@ -939,6 +1068,32 @@ describe Sequel::Model, "#eager_graph" do
939
1068
  a.first.track.should == GraphTrack.load(:id => 3, :album_id=>1)
940
1069
  end
941
1070
 
1071
+ it "should eagerly graph a single one_to_one association using the :distinct_on strategy" do
1072
+ sub = Class.new(GraphTrack)
1073
+ def (sub.dataset).supports_distinct_on?() true end
1074
+ def (sub.dataset).columns() [:id, :album_id] end
1075
+ GraphAlbum.one_to_one :ltrack, :clone=>:track, :class=>sub
1076
+ ds = GraphAlbum.eager_graph_with_options(:ltrack, :limit_strategy=>true)
1077
+ ds.sql.should == 'SELECT albums.id, albums.band_id, ltrack.id AS ltrack_id, ltrack.album_id FROM albums LEFT OUTER JOIN (SELECT DISTINCT ON (tracks.album_id) * FROM tracks ORDER BY tracks.album_id) AS ltrack ON (ltrack.album_id = albums.id)'
1078
+ ds._fetch = {:id=>1, :band_id=>2, :ltrack_id=>3, :album_id=>1}
1079
+ a = ds.all
1080
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1081
+ a.first.ltrack.should == sub.load(:id => 3, :album_id=>1)
1082
+ end
1083
+
1084
+ it "should eagerly graph a single one_to_one association using the :window_function strategy" do
1085
+ sub = Class.new(GraphTrack)
1086
+ def (sub.dataset).supports_window_functions?() true end
1087
+ def (sub.dataset).columns() [:id, :album_id] end
1088
+ GraphAlbum.one_to_one :ltrack, :clone=>:track, :class=>sub
1089
+ ds = GraphAlbum.eager_graph_with_options(:ltrack, :limit_strategy=>true)
1090
+ ds.sql.should == 'SELECT albums.id, albums.band_id, ltrack.id AS ltrack_id, ltrack.album_id FROM albums LEFT OUTER JOIN (SELECT id, album_id FROM (SELECT *, row_number() OVER (PARTITION BY tracks.album_id) AS x_sequel_row_number_x FROM tracks) AS t1 WHERE (x_sequel_row_number_x = 1)) AS ltrack ON (ltrack.album_id = albums.id)'
1091
+ ds._fetch = {:id=>1, :band_id=>2, :ltrack_id=>3, :album_id=>1}
1092
+ a = ds.all
1093
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1094
+ a.first.ltrack.should == sub.load(:id => 3, :album_id=>1)
1095
+ end
1096
+
942
1097
  it "should eagerly load a single one_to_many association" do
943
1098
  ds = GraphAlbum.eager_graph(:tracks)
944
1099
  ds.sql.should == 'SELECT albums.id, albums.band_id, tracks.id AS tracks_id, tracks.album_id FROM albums LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)'
@@ -948,6 +1103,19 @@ describe Sequel::Model, "#eager_graph" do
948
1103
  a.first.tracks.should == [GraphTrack.load(:id => 3, :album_id=>1)]
949
1104
  end
950
1105
 
1106
+ it "should eagerly graph a single one_to_many association using the :window_function strategy" do
1107
+ sub = Class.new(GraphTrack)
1108
+ def (sub.dataset).supports_window_functions?() true end
1109
+ def (sub.dataset).columns() [:id, :album_id] end
1110
+ GraphAlbum.one_to_many :ltracks, :clone=>:tracks, :limit=>2, :class=>sub
1111
+ ds = GraphAlbum.eager_graph_with_options(:ltracks, :limit_strategy=>true)
1112
+ ds.sql.should == 'SELECT albums.id, albums.band_id, ltracks.id AS ltracks_id, ltracks.album_id FROM albums LEFT OUTER JOIN (SELECT id, album_id FROM (SELECT *, row_number() OVER (PARTITION BY tracks.album_id) AS x_sequel_row_number_x FROM tracks) AS t1 WHERE (x_sequel_row_number_x <= 2)) AS ltracks ON (ltracks.album_id = albums.id)'
1113
+ ds._fetch = {:id=>1, :band_id=>2, :ltracks_id=>3, :album_id=>1}
1114
+ a = ds.all
1115
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1116
+ a.first.ltracks.should == [sub.load(:id => 3, :album_id=>1)]
1117
+ end
1118
+
951
1119
  it "should eagerly load a single many_to_many association" do
952
1120
  ds = GraphAlbum.eager_graph(:genres)
953
1121
  ds.sql.should == 'SELECT albums.id, albums.band_id, genres.id AS genres_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres ON (genres.id = ag.genre_id)'
@@ -957,11 +1125,62 @@ describe Sequel::Model, "#eager_graph" do
957
1125
  a.first.genres.should == [GraphGenre.load(:id => 4)]
958
1126
  end
959
1127
 
960
- it "should correctly handle an aliased join table in many_to_many" do
1128
+ it "should eagerly graph a single many_to_many association using the :window_function strategy" do
1129
+ sub = Class.new(GraphGenre)
1130
+ def (sub.dataset).supports_window_functions?() true end
1131
+ def (sub.dataset).columns() literal(opts[:select]) =~ /x_foreign_key_x/ ? [:id, :x_foreign_key_x] : [:id] end
1132
+ GraphAlbum.many_to_many :lgenres, :clone=>:genres, :class=>sub, :limit=>2
1133
+ ds = GraphAlbum.eager_graph_with_options(:lgenres, :limit_strategy=>true)
1134
+ ds.sql.should == 'SELECT albums.id, albums.band_id, lgenres.id AS lgenres_id FROM albums LEFT OUTER JOIN (SELECT id, x_foreign_key_x FROM (SELECT genres.*, ag.album_id AS x_foreign_key_x, row_number() OVER (PARTITION BY ag.album_id) AS x_sequel_row_number_x FROM genres INNER JOIN ag ON (ag.genre_id = genres.id)) AS t1 WHERE (x_sequel_row_number_x <= 2)) AS lgenres ON (lgenres.x_foreign_key_x = albums.id)'
1135
+ ds._fetch = {:id=>1, :band_id=>2, :lgenres_id=>4}
1136
+ a = ds.all
1137
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1138
+ a.first.lgenres.should == [sub.load(:id => 4)]
1139
+ end
1140
+
1141
+ it "should eagerly load a single one_through_one association" do
1142
+ ds = GraphAlbum.eager_graph(:genre)
1143
+ ds.sql.should == 'SELECT albums.id, albums.band_id, genre.id AS genre_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres AS genre ON (genre.id = ag.genre_id)'
1144
+ ds._fetch = {:id=>1, :band_id=>2, :genre_id=>4}
1145
+ a = ds.all
1146
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1147
+ a.first.genre.should == GraphGenre.load(:id => 4)
1148
+ end
1149
+
1150
+ it "should eagerly graph a single one_through_one association using the :distinct_on strategy" do
1151
+ sub = Class.new(GraphGenre)
1152
+ def (sub.dataset).supports_distinct_on?() true end
1153
+ def (sub.dataset).columns() [:id] end
1154
+ GraphAlbum.one_through_one :lgenre, :clone=>:genre, :class=>sub
1155
+ ds = GraphAlbum.eager_graph_with_options(:lgenre, :limit_strategy=>true)
1156
+ ds.sql.should == 'SELECT albums.id, albums.band_id, lgenre.id AS lgenre_id FROM albums LEFT OUTER JOIN (SELECT DISTINCT ON (ag.album_id) genres.*, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON (ag.genre_id = genres.id) ORDER BY ag.album_id) AS lgenre ON (lgenre.x_foreign_key_x = albums.id)'
1157
+ ds._fetch = {:id=>1, :band_id=>2, :lgenre_id=>4}
1158
+ a = ds.all
1159
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1160
+ a.first.lgenre.should == sub.load(:id => 4)
1161
+ end
1162
+
1163
+ it "should eagerly graph a single one_through_one association using the :window_function strategy" do
1164
+ sub = Class.new(GraphGenre)
1165
+ def (sub.dataset).supports_window_functions?() true end
1166
+ def (sub.dataset).columns() literal(opts[:select]) =~ /x_foreign_key_x/ ? [:id, :x_foreign_key_x] : [:id] end
1167
+ GraphAlbum.one_through_one :lgenre, :clone=>:genre, :class=>sub
1168
+ ds = GraphAlbum.eager_graph_with_options(:lgenre, :limit_strategy=>true)
1169
+ ds.sql.should == 'SELECT albums.id, albums.band_id, lgenre.id AS lgenre_id FROM albums LEFT OUTER JOIN (SELECT id, x_foreign_key_x FROM (SELECT genres.*, ag.album_id AS x_foreign_key_x, row_number() OVER (PARTITION BY ag.album_id) AS x_sequel_row_number_x FROM genres INNER JOIN ag ON (ag.genre_id = genres.id)) AS t1 WHERE (x_sequel_row_number_x = 1)) AS lgenre ON (lgenre.x_foreign_key_x = albums.id)'
1170
+ ds._fetch = {:id=>1, :band_id=>2, :lgenre_id=>4}
1171
+ a = ds.all
1172
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1173
+ a.first.lgenre.should == sub.load(:id => 4)
1174
+ end
1175
+
1176
+ it "should correctly handle an aliased join table in many_to_many and one_through_one" do
961
1177
  c = Class.new(GraphAlbum)
962
1178
  c.many_to_many :genres, :clone=>:genres, :join_table=>:ag___ga
963
1179
  c.eager_graph(:genres).sql.should == 'SELECT albums.id, albums.band_id, genres.id AS genres_id FROM albums LEFT OUTER JOIN ag AS ga ON (ga.album_id = albums.id) LEFT OUTER JOIN genres ON (genres.id = ga.genre_id)'
964
1180
 
1181
+ c.many_to_many :genre, :clone=>:genre, :join_table=>:ag___ga
1182
+ c.eager_graph(:genre).sql.should == 'SELECT albums.id, albums.band_id, genre.id AS genre_id FROM albums LEFT OUTER JOIN ag AS ga ON (ga.album_id = albums.id) LEFT OUTER JOIN genres AS genre ON (genre.id = ga.genre_id)'
1183
+
965
1184
  c.many_to_many :genres, :clone=>:genres, :join_table=>:ag___albums
966
1185
  c.eager_graph(:genres).sql.should == 'SELECT albums.id, albums.band_id, genres.id AS genres_id FROM albums LEFT OUTER JOIN ag AS albums_0 ON (albums_0.album_id = albums.id) LEFT OUTER JOIN genres ON (genres.id = albums_0.genre_id)'
967
1186
 
@@ -969,6 +1188,10 @@ describe Sequel::Model, "#eager_graph" do
969
1188
  c.eager_graph(:genres).sql.should == 'SELECT albums.id, albums.band_id, genres.id AS genres_id FROM albums LEFT OUTER JOIN ag AS genres_0 ON (genres_0.album_id = albums.id) LEFT OUTER JOIN genres ON (genres.id = genres_0.genre_id)'
970
1189
  end
971
1190
 
1191
+ it "should handle multiple associations in a single call to association_join" do
1192
+ GraphAlbum.association_join(:genres, :tracks, :band).sql.should == 'SELECT * FROM albums INNER JOIN ag ON (ag.album_id = albums.id) INNER JOIN genres ON (genres.id = ag.genre_id) INNER JOIN tracks ON (tracks.album_id = albums.id) INNER JOIN bands AS band ON (band.id = albums.band_id)'
1193
+ end
1194
+
972
1195
  it "should eagerly load multiple associations in a single call" do
973
1196
  ds = GraphAlbum.eager_graph(:genres, :tracks, :band)
974
1197
  ds.sql.should == 'SELECT albums.id, albums.band_id, genres.id AS genres_id, tracks.id AS tracks_id, tracks.album_id, band.id AS band_id_0, band.vocalist_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres ON (genres.id = ag.genre_id) LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id) LEFT OUTER JOIN bands AS band ON (band.id = albums.band_id)'
@@ -981,6 +1204,28 @@ describe Sequel::Model, "#eager_graph" do
981
1204
  a.genres.should == [GraphGenre.load(:id => 4)]
982
1205
  end
983
1206
 
1207
+ it "should eagerly load multiple associations with different limit strategies in a single call" do
1208
+ subg = Class.new(GraphGenre)
1209
+ def (subg.dataset).supports_distinct_on?() true end
1210
+ def (subg.dataset).supports_window_functions?() true end
1211
+ def (subg.dataset).columns() literal(opts[:select]) =~ /x_foreign_key_x/ ? [:id, :x_foreign_key_x] : [:id] end
1212
+ GraphAlbum.one_through_one :lgenre, :clone=>:genre, :class=>subg
1213
+ GraphAlbum.many_to_many :lgenres, :clone=>:genres, :class=>subg, :limit=>2
1214
+
1215
+ ds = GraphAlbum.eager_graph_with_options([:lgenre, :lgenres], :limit_strategy=>{:lgenre=>:distinct_on, :lgenres=>:window_function})
1216
+ ds.sql.should == 'SELECT albums.id, albums.band_id, lgenre.id AS lgenre_id, lgenres.id AS lgenres_id FROM albums LEFT OUTER JOIN (SELECT DISTINCT ON (ag.album_id) genres.*, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON (ag.genre_id = genres.id) ORDER BY ag.album_id) AS lgenre ON (lgenre.x_foreign_key_x = albums.id) LEFT OUTER JOIN (SELECT id, x_foreign_key_x FROM (SELECT genres.*, ag.album_id AS x_foreign_key_x, row_number() OVER (PARTITION BY ag.album_id) AS x_sequel_row_number_x FROM genres INNER JOIN ag ON (ag.genre_id = genres.id)) AS t1 WHERE (x_sequel_row_number_x <= 2)) AS lgenres ON (lgenres.x_foreign_key_x = albums.id)'
1217
+ ds._fetch = {:id=>1, :band_id=>2, :lgenres_id=>4, :lgenre_id=>3}
1218
+ a = ds.all
1219
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1220
+ a = a.first
1221
+ a.lgenre.should == subg.load(:id => 3)
1222
+ a.lgenres.should == [subg.load(:id => 4)]
1223
+ end
1224
+
1225
+ it "should handle multiple associations in separate calls to association_join" do
1226
+ GraphAlbum.association_join(:genres).association_join(:tracks).association_join(:band).sql.should == 'SELECT * FROM albums INNER JOIN ag ON (ag.album_id = albums.id) INNER JOIN genres ON (genres.id = ag.genre_id) INNER JOIN tracks ON (tracks.album_id = albums.id) INNER JOIN bands AS band ON (band.id = albums.band_id)'
1227
+ end
1228
+
984
1229
  it "should eagerly load multiple associations in separate calls" do
985
1230
  ds = GraphAlbum.eager_graph(:genres).eager_graph(:tracks).eager_graph(:band)
986
1231
  ds.sql.should == 'SELECT albums.id, albums.band_id, genres.id AS genres_id, tracks.id AS tracks_id, tracks.album_id, band.id AS band_id_0, band.vocalist_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres ON (genres.id = ag.genre_id) LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id) LEFT OUTER JOIN bands AS band ON (band.id = albums.band_id)'
@@ -993,6 +1238,15 @@ describe Sequel::Model, "#eager_graph" do
993
1238
  a.genres.should == [GraphGenre.load(:id => 4)]
994
1239
  end
995
1240
 
1241
+ it "should handle cascading associations in a single call to association_join" do
1242
+ GraphTrack.association_join(:album=>{:band=>:members}).sql.should == 'SELECT * FROM tracks INNER JOIN albums AS album ON (album.id = tracks.album_id) INNER JOIN bands AS band ON (band.id = album.band_id) INNER JOIN bm ON (bm.band_id = band.id) INNER JOIN members ON (members.id = bm.member_id)'
1243
+ GraphBand.association_join({:albums=>:tracks}, :members).sql.should == 'SELECT * FROM bands INNER JOIN albums ON (albums.band_id = bands.id) INNER JOIN tracks ON (tracks.album_id = albums.id) INNER JOIN bm ON (bm.band_id = bands.id) INNER JOIN members ON (members.id = bm.member_id)'
1244
+ end
1245
+
1246
+ it "should handle matching association names for different models when using association_join" do
1247
+ GraphAlbum.association_join(:genres).association_join(:band=>:genres).sql.should == 'SELECT * FROM albums INNER JOIN ag ON (ag.album_id = albums.id) INNER JOIN genres ON (genres.id = ag.genre_id) INNER JOIN bands AS band ON (band.id = albums.band_id) INNER JOIN bg ON (bg.band_id = band.id) INNER JOIN genres AS genres_0 ON (genres_0.id = bg.genre_id)'
1248
+ end
1249
+
996
1250
  it "should allow cascading of eager loading for associations of associated models" do
997
1251
  ds = GraphTrack.eager_graph(:album=>{:band=>:members})
998
1252
  ds.sql.should == 'SELECT tracks.id, tracks.album_id, album.id AS album_id_0, album.band_id, band.id AS band_id_0, band.vocalist_id, members.id AS members_id FROM tracks LEFT OUTER JOIN albums AS album ON (album.id = tracks.album_id) LEFT OUTER JOIN bands AS band ON (band.id = album.band_id) LEFT OUTER JOIN bm ON (bm.band_id = band.id) LEFT OUTER JOIN members ON (members.id = bm.member_id)'
@@ -1125,6 +1379,15 @@ describe Sequel::Model, "#eager_graph" do
1125
1379
  a.first.band.should == nil
1126
1380
  end
1127
1381
 
1382
+ it "should handle no associated records for a single one_to_one association" do
1383
+ ds = GraphAlbum.eager_graph(:track)
1384
+ ds.sql.should == 'SELECT albums.id, albums.band_id, track.id AS track_id, track.album_id FROM albums LEFT OUTER JOIN tracks AS track ON (track.album_id = albums.id)'
1385
+ ds._fetch = {:id=>1, :band_id=>2, :track_id=>nil, :album_id=>nil}
1386
+ a = ds.all
1387
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1388
+ a.first.track.should == nil
1389
+ end
1390
+
1128
1391
  it "should handle no associated records for a single one_to_many association" do
1129
1392
  ds = GraphAlbum.eager_graph(:tracks)
1130
1393
  ds.sql.should == 'SELECT albums.id, albums.band_id, tracks.id AS tracks_id, tracks.album_id FROM albums LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)'
@@ -1134,6 +1397,15 @@ describe Sequel::Model, "#eager_graph" do
1134
1397
  a.first.tracks.should == []
1135
1398
  end
1136
1399
 
1400
+ it "should handle no associated records for a single one_through_one association" do
1401
+ ds = GraphAlbum.eager_graph(:genre)
1402
+ ds.sql.should == 'SELECT albums.id, albums.band_id, genre.id AS genre_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres AS genre ON (genre.id = ag.genre_id)'
1403
+ ds._fetch = {:id=>1, :band_id=>2, :genres_id=>nil}
1404
+ a = ds.all
1405
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1406
+ a.first.genre.should == nil
1407
+ end
1408
+
1137
1409
  it "should handle no associated records for a single many_to_many association" do
1138
1410
  ds = GraphAlbum.eager_graph(:genres)
1139
1411
  ds.sql.should == 'SELECT albums.id, albums.band_id, genres.id AS genres_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres ON (genres.id = ag.genre_id)'
@@ -1391,6 +1663,11 @@ describe Sequel::Model, "#eager_graph" do
1391
1663
  GraphAlbum.order(:band_id).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 LEFT OUTER JOIN tracks AS right_tracks ON (right_tracks.album_id = albums.id) ORDER BY band_id, right_tracks.id, right_tracks.album_id'
1392
1664
  end
1393
1665
 
1666
+ it "should use the association's :graph_order in preference or order" do
1667
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:tracks__id, :tracks__album_id], :graph_order=>[:id, :album_id]
1668
+ GraphAlbum.order(:band_id).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 LEFT OUTER JOIN tracks AS right_tracks ON (right_tracks.album_id = albums.id) ORDER BY band_id, right_tracks.id, right_tracks.album_id'
1669
+ end
1670
+
1394
1671
  it "should add the association's :order for cascading associations" do
1395
1672
  GraphBand.one_to_many :a_albums, :class=>'GraphAlbum', :key=>:band_id, :order=>:name, :reciprocal=>nil
1396
1673
  GraphAlbum.one_to_many :b_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:id, :album_id]
@@ -1480,6 +1757,48 @@ describe Sequel::Model, "#eager_graph" do
1480
1757
  a.al_genres.should == [GraphGenre.load(:id=>7), GraphGenre.load(:id=>12)]
1481
1758
  end
1482
1759
 
1760
+ it "should handle offsets on associations with no results when eager graphing" do
1761
+ GraphAlbum.one_to_many :al_tracks, :class=>GraphTrack, :key=>:album_id, :limit=>[2, 1]
1762
+ ds = GraphAlbum.eager_graph(:al_tracks)
1763
+ ds.sql.should == "SELECT albums.id, albums.band_id, al_tracks.id AS al_tracks_id, al_tracks.album_id FROM albums LEFT OUTER JOIN tracks AS al_tracks ON (al_tracks.album_id = albums.id)"
1764
+ ds._fetch = [{:id=>1, :band_id=>2, :al_tracks_id=>nil, :album_id=>nil}]
1765
+ a = ds.all.first
1766
+ a.should == GraphAlbum.load(:id => 1, :band_id => 2)
1767
+ a.al_tracks.should == []
1768
+ end
1769
+
1770
+ it "should respect offsets on associations when eager graphing" do
1771
+ GraphAlbum.many_to_one :al_band, :class=>GraphBand, :key=>:band_id
1772
+ GraphAlbum.one_to_many :al_tracks, :class=>GraphTrack, :key=>:album_id, :limit=>[1, 1]
1773
+ GraphAlbum.many_to_many :al_genres, :class=>GraphGenre, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :limit=>[1,1]
1774
+ ds = GraphAlbum.eager_graph(:al_band, :al_tracks, :al_genres)
1775
+ ds.sql.should == "SELECT albums.id, albums.band_id, al_band.id AS al_band_id, al_band.vocalist_id, al_tracks.id AS al_tracks_id, al_tracks.album_id, al_genres.id AS al_genres_id FROM albums LEFT OUTER JOIN bands AS al_band ON (al_band.id = albums.band_id) LEFT OUTER JOIN tracks AS al_tracks ON (al_tracks.album_id = albums.id) LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres AS al_genres ON (al_genres.id = ag.genre_id)"
1776
+ ds._fetch = [{:id=>1, :band_id=>2, :al_band_id=>3, :vocalist_id=>4, :al_tracks_id=>5, :album_id=>6, :al_genres_id=>7},
1777
+ {:id=>1, :band_id=>2, :al_band_id=>8, :vocalist_id=>9, :al_tracks_id=>10, :album_id=>11, :al_genres_id=>12},
1778
+ {:id=>1, :band_id=>2, :al_band_id=>13, :vocalist_id=>14, :al_tracks_id=>15, :album_id=>16, :al_genres_id=>17}]
1779
+ a = ds.all.first
1780
+ a.should == GraphAlbum.load(:id => 1, :band_id => 2)
1781
+ a.al_band.should == GraphBand.load(:id=>3, :vocalist_id=>4)
1782
+ a.al_tracks.should == [GraphTrack.load(:id=>10, :album_id=>11)]
1783
+ a.al_genres.should == [GraphGenre.load(:id=>12)]
1784
+ end
1785
+
1786
+ it "should respect offsets on associations when eager graphing one_to_one and one_through_one associations" do
1787
+ GraphAlbum.many_to_one :al_band, :class=>GraphBand, :key=>:band_id
1788
+ GraphAlbum.one_to_one :al_track, :class=>GraphTrack, :key=>:album_id, :limit=>[nil, 1]
1789
+ GraphAlbum.one_through_one :al_genre, :class=>GraphGenre, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :limit=>[nil,1]
1790
+ ds = GraphAlbum.eager_graph(:al_band, :al_track, :al_genre)
1791
+ ds.sql.should == "SELECT albums.id, albums.band_id, al_band.id AS al_band_id, al_band.vocalist_id, al_track.id AS al_track_id, al_track.album_id, al_genre.id AS al_genre_id FROM albums LEFT OUTER JOIN bands AS al_band ON (al_band.id = albums.band_id) LEFT OUTER JOIN tracks AS al_track ON (al_track.album_id = albums.id) LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres AS al_genre ON (al_genre.id = ag.genre_id)"
1792
+ ds._fetch = [{:id=>1, :band_id=>2, :al_band_id=>3, :vocalist_id=>4, :al_track_id=>5, :album_id=>6, :al_genre_id=>7},
1793
+ {:id=>1, :band_id=>2, :al_band_id=>8, :vocalist_id=>9, :al_track_id=>10, :album_id=>11, :al_genre_id=>12},
1794
+ {:id=>1, :band_id=>2, :al_band_id=>13, :vocalist_id=>14, :al_track_id=>15, :album_id=>16, :al_genre_id=>17}]
1795
+ a = ds.all.first
1796
+ a.should == GraphAlbum.load(:id => 1, :band_id => 2)
1797
+ a.al_band.should == GraphBand.load(:id=>3, :vocalist_id=>4)
1798
+ a.al_track.should == GraphTrack.load(:id=>10, :album_id=>11)
1799
+ a.al_genre.should == GraphGenre.load(:id=>12)
1800
+ end
1801
+
1483
1802
  it "should eagerly load a many_to_one association with a custom callback" do
1484
1803
  ds = GraphAlbum.eager_graph(:band => proc {|ds1| ds1.select(:id).columns(:id)})
1485
1804
  ds.sql.should == 'SELECT albums.id, albums.band_id, band.id AS band_id_0 FROM albums LEFT OUTER JOIN (SELECT id FROM bands) AS band ON (band.id = albums.band_id)'
@@ -1490,7 +1809,6 @@ describe Sequel::Model, "#eager_graph" do
1490
1809
  end
1491
1810
 
1492
1811
  it "should eagerly load a one_to_one association with a custom callback" do
1493
- GraphAlbum.one_to_one :track, :class=>'GraphTrack', :key=>:album_id
1494
1812
  ds = GraphAlbum.eager_graph(:track => proc {|ds1| ds1.select(:album_id).columns(:album_id)})
1495
1813
  ds.sql.should == 'SELECT albums.id, albums.band_id, track.album_id FROM albums LEFT OUTER JOIN (SELECT album_id FROM tracks) AS track ON (track.album_id = albums.id)'
1496
1814
  ds._fetch = {:id=>1, :band_id=>2, :album_id=>1}
@@ -1508,6 +1826,15 @@ describe Sequel::Model, "#eager_graph" do
1508
1826
  a.first.tracks.should == [GraphTrack.load(:album_id=>1)]
1509
1827
  end
1510
1828
 
1829
+ it "should eagerly load a one_through_one association with a custom callback" do
1830
+ ds = GraphAlbum.eager_graph(:genre => proc {|ds1| ds1.select(:id).columns(:id)})
1831
+ ds.sql.should == 'SELECT albums.id, albums.band_id, genre.id AS genre_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN (SELECT id FROM genres) AS genre ON (genre.id = ag.genre_id)'
1832
+ ds._fetch = {:id=>1, :band_id=>2, :genre_id=>4}
1833
+ a = ds.all
1834
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1835
+ a.first.genre.should == GraphGenre.load(:id => 4)
1836
+ end
1837
+
1511
1838
  it "should eagerly load a many_to_many association with a custom callback" do
1512
1839
  ds = GraphAlbum.eager_graph(:genres => proc {|ds1| ds1.select(:id).columns(:id)})
1513
1840
  ds.sql.should == 'SELECT albums.id, albums.band_id, genres.id AS genres_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN (SELECT id FROM genres) AS genres ON (genres.id = ag.genre_id)'