epugh-sequel 0.0.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 (134) hide show
  1. data/README.rdoc +652 -0
  2. data/VERSION.yml +4 -0
  3. data/bin/sequel +104 -0
  4. data/lib/sequel.rb +1 -0
  5. data/lib/sequel/adapters/ado.rb +85 -0
  6. data/lib/sequel/adapters/db2.rb +132 -0
  7. data/lib/sequel/adapters/dbi.rb +101 -0
  8. data/lib/sequel/adapters/do.rb +197 -0
  9. data/lib/sequel/adapters/do/mysql.rb +38 -0
  10. data/lib/sequel/adapters/do/postgres.rb +92 -0
  11. data/lib/sequel/adapters/do/sqlite.rb +31 -0
  12. data/lib/sequel/adapters/firebird.rb +307 -0
  13. data/lib/sequel/adapters/informix.rb +75 -0
  14. data/lib/sequel/adapters/jdbc.rb +485 -0
  15. data/lib/sequel/adapters/jdbc/h2.rb +62 -0
  16. data/lib/sequel/adapters/jdbc/mysql.rb +56 -0
  17. data/lib/sequel/adapters/jdbc/oracle.rb +23 -0
  18. data/lib/sequel/adapters/jdbc/postgresql.rb +101 -0
  19. data/lib/sequel/adapters/jdbc/sqlite.rb +43 -0
  20. data/lib/sequel/adapters/mysql.rb +370 -0
  21. data/lib/sequel/adapters/odbc.rb +184 -0
  22. data/lib/sequel/adapters/openbase.rb +57 -0
  23. data/lib/sequel/adapters/oracle.rb +140 -0
  24. data/lib/sequel/adapters/postgres.rb +453 -0
  25. data/lib/sequel/adapters/shared/mssql.rb +93 -0
  26. data/lib/sequel/adapters/shared/mysql.rb +341 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +62 -0
  28. data/lib/sequel/adapters/shared/postgres.rb +743 -0
  29. data/lib/sequel/adapters/shared/progress.rb +34 -0
  30. data/lib/sequel/adapters/shared/sqlite.rb +263 -0
  31. data/lib/sequel/adapters/sqlite.rb +243 -0
  32. data/lib/sequel/adapters/utils/date_format.rb +21 -0
  33. data/lib/sequel/adapters/utils/stored_procedures.rb +75 -0
  34. data/lib/sequel/adapters/utils/unsupported.rb +62 -0
  35. data/lib/sequel/connection_pool.rb +258 -0
  36. data/lib/sequel/core.rb +204 -0
  37. data/lib/sequel/core_sql.rb +185 -0
  38. data/lib/sequel/database.rb +687 -0
  39. data/lib/sequel/database/schema_generator.rb +324 -0
  40. data/lib/sequel/database/schema_methods.rb +164 -0
  41. data/lib/sequel/database/schema_sql.rb +324 -0
  42. data/lib/sequel/dataset.rb +422 -0
  43. data/lib/sequel/dataset/convenience.rb +237 -0
  44. data/lib/sequel/dataset/prepared_statements.rb +220 -0
  45. data/lib/sequel/dataset/sql.rb +1105 -0
  46. data/lib/sequel/deprecated.rb +529 -0
  47. data/lib/sequel/exceptions.rb +44 -0
  48. data/lib/sequel/extensions/blank.rb +42 -0
  49. data/lib/sequel/extensions/inflector.rb +288 -0
  50. data/lib/sequel/extensions/pagination.rb +96 -0
  51. data/lib/sequel/extensions/pretty_table.rb +78 -0
  52. data/lib/sequel/extensions/query.rb +48 -0
  53. data/lib/sequel/extensions/string_date_time.rb +47 -0
  54. data/lib/sequel/metaprogramming.rb +44 -0
  55. data/lib/sequel/migration.rb +212 -0
  56. data/lib/sequel/model.rb +142 -0
  57. data/lib/sequel/model/association_reflection.rb +263 -0
  58. data/lib/sequel/model/associations.rb +1024 -0
  59. data/lib/sequel/model/base.rb +911 -0
  60. data/lib/sequel/model/deprecated.rb +188 -0
  61. data/lib/sequel/model/deprecated_hooks.rb +103 -0
  62. data/lib/sequel/model/deprecated_inflector.rb +335 -0
  63. data/lib/sequel/model/deprecated_validations.rb +384 -0
  64. data/lib/sequel/model/errors.rb +37 -0
  65. data/lib/sequel/model/exceptions.rb +7 -0
  66. data/lib/sequel/model/inflections.rb +230 -0
  67. data/lib/sequel/model/plugins.rb +74 -0
  68. data/lib/sequel/object_graph.rb +230 -0
  69. data/lib/sequel/plugins/caching.rb +122 -0
  70. data/lib/sequel/plugins/hook_class_methods.rb +122 -0
  71. data/lib/sequel/plugins/schema.rb +53 -0
  72. data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
  73. data/lib/sequel/plugins/validation_class_methods.rb +373 -0
  74. data/lib/sequel/sql.rb +854 -0
  75. data/lib/sequel/version.rb +11 -0
  76. data/lib/sequel_core.rb +1 -0
  77. data/lib/sequel_model.rb +1 -0
  78. data/spec/adapters/ado_spec.rb +46 -0
  79. data/spec/adapters/firebird_spec.rb +376 -0
  80. data/spec/adapters/informix_spec.rb +96 -0
  81. data/spec/adapters/mysql_spec.rb +875 -0
  82. data/spec/adapters/oracle_spec.rb +272 -0
  83. data/spec/adapters/postgres_spec.rb +692 -0
  84. data/spec/adapters/spec_helper.rb +10 -0
  85. data/spec/adapters/sqlite_spec.rb +550 -0
  86. data/spec/core/connection_pool_spec.rb +526 -0
  87. data/spec/core/core_ext_spec.rb +156 -0
  88. data/spec/core/core_sql_spec.rb +528 -0
  89. data/spec/core/database_spec.rb +1214 -0
  90. data/spec/core/dataset_spec.rb +3513 -0
  91. data/spec/core/expression_filters_spec.rb +363 -0
  92. data/spec/core/migration_spec.rb +261 -0
  93. data/spec/core/object_graph_spec.rb +280 -0
  94. data/spec/core/pretty_table_spec.rb +58 -0
  95. data/spec/core/schema_generator_spec.rb +167 -0
  96. data/spec/core/schema_spec.rb +778 -0
  97. data/spec/core/spec_helper.rb +82 -0
  98. data/spec/core/version_spec.rb +7 -0
  99. data/spec/extensions/blank_spec.rb +67 -0
  100. data/spec/extensions/caching_spec.rb +201 -0
  101. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  102. data/spec/extensions/inflector_spec.rb +122 -0
  103. data/spec/extensions/pagination_spec.rb +99 -0
  104. data/spec/extensions/pretty_table_spec.rb +91 -0
  105. data/spec/extensions/query_spec.rb +85 -0
  106. data/spec/extensions/schema_spec.rb +111 -0
  107. data/spec/extensions/single_table_inheritance_spec.rb +53 -0
  108. data/spec/extensions/spec_helper.rb +90 -0
  109. data/spec/extensions/string_date_time_spec.rb +93 -0
  110. data/spec/extensions/validation_class_methods_spec.rb +1054 -0
  111. data/spec/integration/dataset_test.rb +160 -0
  112. data/spec/integration/eager_loader_test.rb +683 -0
  113. data/spec/integration/prepared_statement_test.rb +130 -0
  114. data/spec/integration/schema_test.rb +183 -0
  115. data/spec/integration/spec_helper.rb +75 -0
  116. data/spec/integration/type_test.rb +96 -0
  117. data/spec/model/association_reflection_spec.rb +93 -0
  118. data/spec/model/associations_spec.rb +1780 -0
  119. data/spec/model/base_spec.rb +494 -0
  120. data/spec/model/caching_spec.rb +217 -0
  121. data/spec/model/dataset_methods_spec.rb +78 -0
  122. data/spec/model/eager_loading_spec.rb +1165 -0
  123. data/spec/model/hooks_spec.rb +472 -0
  124. data/spec/model/inflector_spec.rb +126 -0
  125. data/spec/model/model_spec.rb +588 -0
  126. data/spec/model/plugins_spec.rb +142 -0
  127. data/spec/model/record_spec.rb +1243 -0
  128. data/spec/model/schema_spec.rb +92 -0
  129. data/spec/model/spec_helper.rb +124 -0
  130. data/spec/model/validations_spec.rb +1080 -0
  131. data/spec/rcov.opts +6 -0
  132. data/spec/spec.opts +0 -0
  133. data/spec/spec_config.rb.example +10 -0
  134. metadata +202 -0
@@ -0,0 +1,217 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe Sequel::Model, "caching" do
4
+ before do
5
+ MODEL_DB.reset
6
+
7
+ @cache_class = Class.new(Hash) do
8
+ attr_accessor :ttl
9
+ def set(k, v, ttl); self[k] = v; @ttl = ttl; end
10
+ def get(k); self[k]; end
11
+ end
12
+ cache = @cache_class.new
13
+ @cache = cache
14
+
15
+ @c = Class.new(Sequel::Model(:items))
16
+ deprec do
17
+ @c.class_eval do
18
+ set_cache cache
19
+ def self.name; 'Item' end
20
+
21
+ columns :name, :id
22
+ end
23
+ end
24
+
25
+ $cache_dataset_row = {:name => 'sharon', :id => 1}
26
+ @dataset = @c.dataset
27
+ $sqls = []
28
+ @dataset.extend(Module.new {
29
+ def fetch_rows(sql)
30
+ $sqls << sql
31
+ yield $cache_dataset_row
32
+ end
33
+
34
+ def update(values)
35
+ $sqls << update_sql(values)
36
+ $cache_dataset_row.merge!(values)
37
+ end
38
+
39
+ def delete
40
+ $sqls << delete_sql
41
+ end
42
+ })
43
+ @c2 = Class.new(@c) do
44
+ def self.name; 'SubItem' end
45
+ end
46
+ end
47
+
48
+ it "should set the model's cache store" do
49
+ @c.cache_store.should be(@cache)
50
+ @c2.cache_store.should be(@cache)
51
+ end
52
+
53
+ it "should have a default ttl of 3600" do
54
+ @c.cache_ttl.should == 3600
55
+ @c2.cache_ttl.should == 3600
56
+ end
57
+
58
+ it "should take a ttl option" do
59
+ @c.set_cache @cache, :ttl => 1234
60
+ @c.cache_ttl.should == 1234
61
+ Class.new(@c).cache_ttl.should == 1234
62
+ end
63
+
64
+ it "should offer a set_cache_ttl method for setting the ttl" do
65
+ @c.cache_ttl.should == 3600
66
+ @c.set_cache_ttl 1234
67
+ @c.cache_ttl.should == 1234
68
+ Class.new(@c).cache_ttl.should == 1234
69
+ end
70
+
71
+ it "should generate a cache key appropriate to the class" do
72
+ m = @c.new
73
+ m.values[:id] = 1
74
+ m.cache_key.should == "#{m.class}:1"
75
+ m = @c2.new
76
+ m.values[:id] = 1
77
+ m.cache_key.should == "#{m.class}:1"
78
+
79
+ # custom primary key
80
+ @c.set_primary_key :ttt
81
+ m = @c.new
82
+ m.values[:ttt] = 333
83
+ m.cache_key.should == "#{m.class}:333"
84
+ c = Class.new(@c)
85
+ m = c.new
86
+ m.values[:ttt] = 333
87
+ m.cache_key.should == "#{m.class}:333"
88
+
89
+ # composite primary key
90
+ @c.set_primary_key [:a, :b, :c]
91
+ m = @c.new
92
+ m.values[:a] = 123
93
+ m.values[:c] = 456
94
+ m.values[:b] = 789
95
+ m.cache_key.should == "#{m.class}:123,789,456"
96
+ c = Class.new(@c)
97
+ m = c.new
98
+ m.values[:a] = 123
99
+ m.values[:c] = 456
100
+ m.values[:b] = 789
101
+ m.cache_key.should == "#{m.class}:123,789,456"
102
+ end
103
+
104
+ it "should raise error if attempting to generate cache_key and primary key value is null" do
105
+ m = @c.new
106
+ proc {m.cache_key}.should raise_error(Sequel::Error)
107
+ m.values[:id] = 1
108
+ proc {m.cache_key}.should_not raise_error(Sequel::Error)
109
+
110
+ m = @c2.new
111
+ proc {m.cache_key}.should raise_error(Sequel::Error)
112
+ m.values[:id] = 1
113
+ proc {m.cache_key}.should_not raise_error(Sequel::Error)
114
+ end
115
+
116
+ it "should not raise error if trying to save a new record" do
117
+ proc {@c.new(:name=>'blah').save}.should_not raise_error
118
+ proc {@c.create(:name=>'blah')}.should_not raise_error
119
+ proc {@c2.new(:name=>'blah').save}.should_not raise_error
120
+ proc {@c2.create(:name=>'blah')}.should_not raise_error
121
+ end
122
+
123
+ it "should set the cache when reading from the database" do
124
+ $sqls.should == []
125
+ @cache.should be_empty
126
+
127
+ m = @c[1]
128
+ $sqls.should == ['SELECT * FROM items WHERE (id = 1) LIMIT 1']
129
+ m.values.should == $cache_dataset_row
130
+ @cache[m.cache_key].should == m
131
+ m2 = @c[1]
132
+ $sqls.should == ['SELECT * FROM items WHERE (id = 1) LIMIT 1']
133
+ m2.should == m
134
+ m2.values.should == $cache_dataset_row
135
+
136
+ $sqls.clear
137
+ m = @c2[1]
138
+ $sqls.should == ['SELECT * FROM items WHERE (id = 1) LIMIT 1']
139
+ m.values.should == $cache_dataset_row
140
+ @cache[m.cache_key].should == m
141
+ m2 = @c2[1]
142
+ $sqls.should == ['SELECT * FROM items WHERE (id = 1) LIMIT 1']
143
+ m2.should == m
144
+ m2.values.should == $cache_dataset_row
145
+ end
146
+
147
+ it "should delete the cache when writing to the database" do
148
+ m = @c[1]
149
+ @cache[m.cache_key].should == m
150
+ m.name = 'hey'
151
+ m.save
152
+ @cache.has_key?(m.cache_key).should be_false
153
+ $sqls.last.should == "UPDATE items SET name = 'hey', id = 1 WHERE (id = 1)"
154
+
155
+ m = @c2[1]
156
+ @cache[m.cache_key].should == m
157
+ m.name = 'hey'
158
+ m.save
159
+ @cache.has_key?(m.cache_key).should be_false
160
+ $sqls.last.should == "UPDATE items SET name = 'hey', id = 1 WHERE (id = 1)"
161
+ end
162
+
163
+ deprec_specify "should delete the cache when using update_values" do
164
+ m = @c[1]
165
+ @cache[m.cache_key].should == m
166
+ m.update_values(:name => 'tutu')
167
+ @cache.has_key?(m.cache_key).should be_false
168
+ $sqls.last.should == "UPDATE items SET name = 'tutu' WHERE (id = 1)"
169
+
170
+ m = @c2[1]
171
+ @cache[m.cache_key].should == m
172
+ m.update_values(:name => 'tutu')
173
+ @cache.has_key?(m.cache_key).should be_false
174
+ $sqls.last.should == "UPDATE items SET name = 'tutu' WHERE (id = 1)"
175
+ end
176
+
177
+ it "should delete the cache when deleting the record" do
178
+ m = @c[1]
179
+ @cache[m.cache_key].should == m
180
+ m.delete
181
+ @cache.has_key?(m.cache_key).should be_false
182
+ $sqls.last.should == "DELETE FROM items WHERE (id = 1)"
183
+
184
+ m = @c2[1]
185
+ @cache[m.cache_key].should == m
186
+ m.delete
187
+ @cache.has_key?(m.cache_key).should be_false
188
+ $sqls.last.should == "DELETE FROM items WHERE (id = 1)"
189
+ end
190
+
191
+ it "should support #[] as a shortcut to #find with hash" do
192
+ m = @c[:id => 3]
193
+ @cache[m.cache_key].should be_nil
194
+ $sqls.last.should == "SELECT * FROM items WHERE (id = 3) LIMIT 1"
195
+ m = @c[1]
196
+ @cache[m.cache_key].should == m
197
+ $sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1", \
198
+ "SELECT * FROM items WHERE (id = 1) LIMIT 1"]
199
+ @c[:id => 4]
200
+ $sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1", \
201
+ "SELECT * FROM items WHERE (id = 1) LIMIT 1", \
202
+ "SELECT * FROM items WHERE (id = 4) LIMIT 1"]
203
+
204
+ $sqls.clear
205
+ m = @c2[:id => 3]
206
+ @cache[m.cache_key].should be_nil
207
+ $sqls.last.should == "SELECT * FROM items WHERE (id = 3) LIMIT 1"
208
+ m = @c2[1]
209
+ @cache[m.cache_key].should == m
210
+ $sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1", \
211
+ "SELECT * FROM items WHERE (id = 1) LIMIT 1"]
212
+ @c2[:id => 4]
213
+ $sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1", \
214
+ "SELECT * FROM items WHERE (id = 1) LIMIT 1", \
215
+ "SELECT * FROM items WHERE (id = 4) LIMIT 1"]
216
+ end
217
+ end
@@ -0,0 +1,78 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe Sequel::Model::DatasetMethods, "#destroy" do
4
+ before do
5
+ @c = Class.new(Sequel::Model(:items)) do
6
+ @@destroyed = []
7
+ def destroy
8
+ @@destroyed << self
9
+ end
10
+ def self.destroyed
11
+ @@destroyed
12
+ end
13
+ end
14
+ @d = @c.dataset
15
+ end
16
+
17
+ it "should instantiate objects in the dataset and call destroy on each" do
18
+ def @d.fetch_rows(sql)
19
+ yield({:id=>1})
20
+ yield({:id=>2})
21
+ end
22
+ @d.destroy
23
+ @c.destroyed.collect{|x| x.values}.should == [{:id=>1}, {:id=>2}]
24
+ end
25
+
26
+ it "should return the number of records destroyed" do
27
+ def @d.fetch_rows(sql)
28
+ yield({:id=>1})
29
+ yield({:id=>2})
30
+ end
31
+ @d.destroy.should == 2
32
+ def @d.fetch_rows(sql)
33
+ yield({:id=>1})
34
+ end
35
+ @d.destroy.should == 1
36
+ def @d.fetch_rows(sql)
37
+ end
38
+ @d.destroy.should == 0
39
+ end
40
+ end
41
+
42
+ describe Sequel::Model::DatasetMethods, "#to_hash" do
43
+ before do
44
+ @c = Class.new(Sequel::Model(:items)) do
45
+ set_primary_key :name
46
+ end
47
+ @d = @c.dataset
48
+ end
49
+
50
+ it "should result in a hash with primary key value keys and model object values" do
51
+ def @d.fetch_rows(sql)
52
+ yield({:name=>1})
53
+ yield({:name=>2})
54
+ end
55
+ h = @d.to_hash
56
+ h.should be_a_kind_of(Hash)
57
+ a = h.to_a
58
+ a.collect{|x| x[1].class}.should == [@c, @c]
59
+ a.sort_by{|x| x[0]}.collect{|x| [x[0], x[1].values]}.should == [[1, {:name=>1}], [2, {:name=>2}]]
60
+ end
61
+
62
+ it "should result in a hash with given value keys and model object values" do
63
+ def @d.fetch_rows(sql)
64
+ yield({:name=>1, :number=>3})
65
+ yield({:name=>2, :number=>4})
66
+ end
67
+ h = @d.to_hash(:number)
68
+ h.should be_a_kind_of(Hash)
69
+ a = h.to_a
70
+ a.collect{|x| x[1].class}.should == [@c, @c]
71
+ a.sort_by{|x| x[0]}.collect{|x| [x[0], x[1].values]}.should == [[3, {:name=>1, :number=>3}], [4, {:name=>2, :number=>4}]]
72
+ end
73
+
74
+ it "should raise an error if the class doesn't have a primary key" do
75
+ @c.no_primary_key
76
+ proc{@d.to_hash}.should raise_error(Sequel::Error)
77
+ end
78
+ end
@@ -0,0 +1,1165 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe Sequel::Model, "#eager" do
4
+ before(:each) do
5
+ MODEL_DB.reset
6
+
7
+ class ::EagerAlbum < Sequel::Model(:albums)
8
+ columns :id, :band_id
9
+ many_to_one :band, :class=>'EagerBand', :key=>:band_id
10
+ one_to_many :tracks, :class=>'EagerTrack', :key=>:album_id
11
+ many_to_many :genres, :class=>'EagerGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag
12
+ one_to_many :good_tracks, :class=>'EagerTrack', :key=>:album_id do |ds|
13
+ ds.filter(:name=>'Good')
14
+ end
15
+ many_to_one :band_name, :class=>'EagerBand', :key=>:band_id, :select=>[:id, :name]
16
+ one_to_many :track_names, :class=>'EagerTrack', :key=>:album_id, :select=>[:id, :name]
17
+ many_to_many :genre_names, :class=>'EagerGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :select=>[:id]
18
+ end
19
+
20
+ class ::EagerBand < Sequel::Model(:bands)
21
+ columns :id, :p_k
22
+ one_to_many :albums, :class=>'EagerAlbum', :key=>:band_id, :eager=>:tracks
23
+ one_to_many :graph_albums, :class=>'EagerAlbum', :key=>:band_id, :eager_graph=>:tracks
24
+ many_to_many :members, :class=>'EagerBandMember', :left_key=>:band_id, :right_key=>:member_id, :join_table=>:bm
25
+ one_to_many :good_albums, :class=>'EagerAlbum', :key=>:band_id, :eager_block=>proc{|ds| ds.filter(:name=>'good')} do |ds|
26
+ ds.filter(:name=>'Good')
27
+ end
28
+ one_to_many :self_titled_albums, :class=>'EagerAlbum', :key=>:band_id, :allow_eager=>false do |ds|
29
+ ds.filter(:name=>name)
30
+ end
31
+ one_to_many :albums_by_name, :class=>'EagerAlbum', :key=>:band_id, :order=>:name, :allow_eager=>false
32
+ one_to_many :top_10_albums, :class=>'EagerAlbum', :key=>:band_id, :limit=>10
33
+ end
34
+
35
+ class ::EagerTrack < Sequel::Model(:tracks)
36
+ columns :id, :album_id
37
+ many_to_one :album, :class=>'EagerAlbum', :key=>:album_id
38
+ end
39
+
40
+ class ::EagerGenre < Sequel::Model(:genres)
41
+ columns :id, :xxx
42
+ many_to_many :albums, :class=>'EagerAlbum', :left_key=>:genre_id, :right_key=>:album_id, :join_table=>:ag
43
+ end
44
+
45
+ class ::EagerBandMember < Sequel::Model(:members)
46
+ columns :id
47
+ many_to_many :bands, :class=>'EagerBand', :left_key=>:member_id, :right_key=>:band_id, :join_table=>:bm, :order =>:id
48
+ end
49
+
50
+ EagerAlbum.dataset.extend(Module.new {
51
+ def columns
52
+ [:id, :band_id]
53
+ end
54
+
55
+ def fetch_rows(sql)
56
+ h = if sql =~ /101/
57
+ {:id => 101, :band_id=> 101}
58
+ else
59
+ {:id => 1, :band_id=> 2}
60
+ end
61
+ h.merge!(:x_foreign_key_x=>4) if sql =~ /ag\.genre_id/
62
+ @db << sql
63
+ yield h
64
+ end
65
+ })
66
+
67
+ EagerBand.dataset.extend(Module.new {
68
+ def fetch_rows(sql)
69
+ h = {:id => 2}
70
+ h.merge!(:x_foreign_key_x=>5) if sql =~ /bm\.member_id/
71
+ @db << sql
72
+ case sql
73
+ when /id IN (101)/
74
+ when /id > 100/
75
+ yield({:id => 101})
76
+ yield({:id => 102})
77
+ else
78
+ yield h
79
+ end
80
+ end
81
+ })
82
+
83
+ EagerTrack.dataset.extend(Module.new {
84
+ def fetch_rows(sql)
85
+ @db << sql
86
+ yield({:id => 3, :album_id => 1})
87
+ end
88
+ })
89
+
90
+ EagerGenre.dataset.extend(Module.new {
91
+ def fetch_rows(sql)
92
+ h = {:id => 4}
93
+ h.merge!(:x_foreign_key_x=>1) if sql =~ /ag\.album_id/
94
+ @db << sql
95
+ yield h
96
+ end
97
+ })
98
+
99
+ EagerBandMember.dataset.extend(Module.new {
100
+ def fetch_rows(sql)
101
+ h = {:id => 5}
102
+ h.merge!(:x_foreign_key_x=>2) if sql =~ /bm\.band_id/
103
+ @db << sql
104
+ yield h
105
+ end
106
+ })
107
+ end
108
+
109
+ it "should raise an error if called without a symbol or hash" do
110
+ proc{EagerAlbum.eager(Object.new)}.should raise_error(Sequel::Error)
111
+ end
112
+
113
+ it "should eagerly load a single many_to_one association" do
114
+ a = EagerAlbum.eager(:band).all
115
+ a.should be_a_kind_of(Array)
116
+ a.size.should == 1
117
+ a.first.should be_a_kind_of(EagerAlbum)
118
+ a.first.values.should == {:id => 1, :band_id => 2}
119
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM bands WHERE (bands.id IN (2))']
120
+ a = a.first
121
+ a.band.should be_a_kind_of(EagerBand)
122
+ a.band.values.should == {:id => 2}
123
+ MODEL_DB.sqls.length.should == 2
124
+ end
125
+
126
+ it "should eagerly load a single one_to_many association" do
127
+ a = EagerAlbum.eager(:tracks).all
128
+ a.should be_a_kind_of(Array)
129
+ a.size.should == 1
130
+ a.first.should be_a_kind_of(EagerAlbum)
131
+ a.first.values.should == {:id => 1, :band_id => 2}
132
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE (tracks.album_id IN (1))']
133
+ a = a.first
134
+ a.tracks.should be_a_kind_of(Array)
135
+ a.tracks.size.should == 1
136
+ a.tracks.first.should be_a_kind_of(EagerTrack)
137
+ a.tracks.first.values.should == {:id => 3, :album_id=>1}
138
+ MODEL_DB.sqls.length.should == 2
139
+ end
140
+
141
+ it "should eagerly load a single many_to_many association" do
142
+ a = EagerAlbum.eager(:genres).all
143
+ a.should be_a_kind_of(Array)
144
+ a.size.should == 1
145
+ a.first.should be_a_kind_of(EagerAlbum)
146
+ a.first.values.should == {:id => 1, :band_id => 2}
147
+ 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.id) AND (ag.album_id IN (1)))"]
148
+ a = a.first
149
+ a.genres.should be_a_kind_of(Array)
150
+ a.genres.size.should == 1
151
+ a.genres.first.should be_a_kind_of(EagerGenre)
152
+ a.genres.first.values.should == {:id => 4}
153
+ MODEL_DB.sqls.length.should == 2
154
+ end
155
+
156
+ it "should eagerly load multiple associations in a single call" do
157
+ a = EagerAlbum.eager(:genres, :tracks, :band).all
158
+ a.should be_a_kind_of(Array)
159
+ a.size.should == 1
160
+ a.first.should be_a_kind_of(EagerAlbum)
161
+ a.first.values.should == {:id => 1, :band_id => 2}
162
+ MODEL_DB.sqls.length.should == 4
163
+ MODEL_DB.sqls[0].should == 'SELECT * FROM albums'
164
+ MODEL_DB.sqls[1..-1].should(include('SELECT * FROM bands WHERE (bands.id IN (2))'))
165
+ MODEL_DB.sqls[1..-1].should(include('SELECT * FROM tracks WHERE (tracks.album_id IN (1))'))
166
+ MODEL_DB.sqls[1..-1].should(include('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)))'))
167
+ a = a.first
168
+ a.band.should be_a_kind_of(EagerBand)
169
+ a.band.values.should == {:id => 2}
170
+ a.tracks.should be_a_kind_of(Array)
171
+ a.tracks.size.should == 1
172
+ a.tracks.first.should be_a_kind_of(EagerTrack)
173
+ a.tracks.first.values.should == {:id => 3, :album_id=>1}
174
+ a.genres.should be_a_kind_of(Array)
175
+ a.genres.size.should == 1
176
+ a.genres.first.should be_a_kind_of(EagerGenre)
177
+ a.genres.first.values.should == {:id => 4}
178
+ MODEL_DB.sqls.length.should == 4
179
+ end
180
+
181
+ it "should eagerly load multiple associations in separate calls" do
182
+ a = EagerAlbum.eager(:genres).eager(:tracks).eager(:band).all
183
+ a.should be_a_kind_of(Array)
184
+ a.size.should == 1
185
+ a.first.should be_a_kind_of(EagerAlbum)
186
+ a.first.values.should == {:id => 1, :band_id => 2}
187
+ MODEL_DB.sqls.length.should == 4
188
+ MODEL_DB.sqls[0].should == 'SELECT * FROM albums'
189
+ MODEL_DB.sqls[1..-1].should(include('SELECT * FROM bands WHERE (bands.id IN (2))'))
190
+ MODEL_DB.sqls[1..-1].should(include('SELECT * FROM tracks WHERE (tracks.album_id IN (1))'))
191
+ MODEL_DB.sqls[1..-1].should(include('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)))'))
192
+ a = a.first
193
+ a.band.should be_a_kind_of(EagerBand)
194
+ a.band.values.should == {:id => 2}
195
+ a.tracks.should be_a_kind_of(Array)
196
+ a.tracks.size.should == 1
197
+ a.tracks.first.should be_a_kind_of(EagerTrack)
198
+ a.tracks.first.values.should == {:id => 3, :album_id=>1}
199
+ a.genres.should be_a_kind_of(Array)
200
+ a.genres.size.should == 1
201
+ a.genres.first.should be_a_kind_of(EagerGenre)
202
+ a.genres.first.values.should == {:id => 4}
203
+ MODEL_DB.sqls.length.should == 4
204
+ end
205
+
206
+ it "should allow cascading of eager loading for associations of associated models" do
207
+ a = EagerTrack.eager(:album=>{:band=>:members}).all
208
+ a.should be_a_kind_of(Array)
209
+ a.size.should == 1
210
+ a.first.should be_a_kind_of(EagerTrack)
211
+ a.first.values.should == {:id => 3, :album_id => 1}
212
+ MODEL_DB.sqls.length.should == 4
213
+ MODEL_DB.sqls.should == ['SELECT * FROM tracks',
214
+ 'SELECT * FROM albums WHERE (albums.id IN (1))',
215
+ 'SELECT * FROM bands WHERE (bands.id IN (2))',
216
+ "SELECT members.*, bm.band_id AS x_foreign_key_x FROM members INNER JOIN bm ON ((bm.member_id = members.id) AND (bm.band_id IN (2)))"]
217
+ a = a.first
218
+ a.album.should be_a_kind_of(EagerAlbum)
219
+ a.album.values.should == {:id => 1, :band_id => 2}
220
+ a.album.band.should be_a_kind_of(EagerBand)
221
+ a.album.band.values.should == {:id => 2}
222
+ a.album.band.members.should be_a_kind_of(Array)
223
+ a.album.band.members.size.should == 1
224
+ a.album.band.members.first.should be_a_kind_of(EagerBandMember)
225
+ a.album.band.members.first.values.should == {:id => 5}
226
+ MODEL_DB.sqls.length.should == 4
227
+ end
228
+
229
+ it "should cascade eagerly loading when the :eager association option is used" do
230
+ a = EagerBand.eager(:albums).all
231
+ a.should be_a_kind_of(Array)
232
+ a.size.should == 1
233
+ a.first.should be_a_kind_of(EagerBand)
234
+ a.first.values.should == {:id => 2}
235
+ MODEL_DB.sqls.should == ['SELECT * FROM bands',
236
+ 'SELECT * FROM albums WHERE (albums.band_id IN (2))',
237
+ 'SELECT * FROM tracks WHERE (tracks.album_id IN (1))']
238
+ a = a.first
239
+ a.albums.should be_a_kind_of(Array)
240
+ a.albums.size.should == 1
241
+ a.albums.first.should be_a_kind_of(EagerAlbum)
242
+ a.albums.first.values.should == {:id => 1, :band_id => 2}
243
+ a = a.albums.first
244
+ a.tracks.should be_a_kind_of(Array)
245
+ a.tracks.size.should == 1
246
+ a.tracks.first.should be_a_kind_of(EagerTrack)
247
+ a.tracks.first.values.should == {:id => 3, :album_id => 1}
248
+ MODEL_DB.sqls.length.should == 3
249
+ end
250
+
251
+ it "should respect :eager when lazily loading an association" do
252
+ a = EagerBand.all
253
+ a.should be_a_kind_of(Array)
254
+ a.size.should == 1
255
+ a.first.should be_a_kind_of(EagerBand)
256
+ a.first.values.should == {:id => 2}
257
+ MODEL_DB.sqls.should == ['SELECT * FROM bands']
258
+ a = a.first
259
+ a.albums
260
+ MODEL_DB.sqls.should == ['SELECT * FROM bands',
261
+ 'SELECT * FROM albums WHERE (albums.band_id = 2)',
262
+ 'SELECT * FROM tracks WHERE (tracks.album_id IN (1))']
263
+ a.albums.should be_a_kind_of(Array)
264
+ a.albums.size.should == 1
265
+ a.albums.first.should be_a_kind_of(EagerAlbum)
266
+ a.albums.first.values.should == {:id => 1, :band_id => 2}
267
+ a = a.albums.first
268
+ a.tracks.should be_a_kind_of(Array)
269
+ a.tracks.size.should == 1
270
+ a.tracks.first.should be_a_kind_of(EagerTrack)
271
+ a.tracks.first.values.should == {:id => 3, :album_id => 1}
272
+ MODEL_DB.sqls.length.should == 3
273
+ end
274
+
275
+ it "should cascade eagerly loading when the :eager_graph association option is used" do
276
+ EagerAlbum.dataset.extend(Module.new {
277
+ def fetch_rows(sql)
278
+ @db << sql
279
+ yield({:id=>1, :band_id=>2, :tracks_id=>3, :album_id=>1})
280
+ end
281
+ })
282
+ a = EagerBand.eager(:graph_albums).all
283
+ a.should be_a_kind_of(Array)
284
+ a.size.should == 1
285
+ a.first.should be_a_kind_of(EagerBand)
286
+ a.first.values.should == {:id => 2}
287
+ MODEL_DB.sqls.should == ['SELECT * FROM bands',
288
+ '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) WHERE (albums.band_id IN (2))']
289
+ a = a.first
290
+ a.graph_albums.should be_a_kind_of(Array)
291
+ a.graph_albums.size.should == 1
292
+ a.graph_albums.first.should be_a_kind_of(EagerAlbum)
293
+ a.graph_albums.first.values.should == {:id => 1, :band_id => 2}
294
+ a = a.graph_albums.first
295
+ a.tracks.should be_a_kind_of(Array)
296
+ a.tracks.size.should == 1
297
+ a.tracks.first.should be_a_kind_of(EagerTrack)
298
+ a.tracks.first.values.should == {:id => 3, :album_id => 1}
299
+ MODEL_DB.sqls.length.should == 2
300
+ end
301
+
302
+ it "should respect :eager_graph when lazily loading an association" do
303
+ a = EagerBand.all
304
+ a.should be_a_kind_of(Array)
305
+ a.size.should == 1
306
+ a.first.should be_a_kind_of(EagerBand)
307
+ a.first.values.should == {:id => 2}
308
+ MODEL_DB.sqls.should == ['SELECT * FROM bands']
309
+ a = a.first
310
+ EagerAlbum.dataset.extend(Module.new {
311
+ def fetch_rows(sql)
312
+ @db << sql
313
+ yield({:id=>1, :band_id=>2, :tracks_id=>3, :album_id=>1})
314
+ end
315
+ })
316
+ a.graph_albums
317
+ MODEL_DB.sqls.should == ['SELECT * FROM bands',
318
+ '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) WHERE (albums.band_id = 2)']
319
+ a.graph_albums.should be_a_kind_of(Array)
320
+ a.graph_albums.size.should == 1
321
+ a.graph_albums.first.should be_a_kind_of(EagerAlbum)
322
+ a.graph_albums.first.values.should == {:id => 1, :band_id => 2}
323
+ a = a.graph_albums.first
324
+ a.tracks.should be_a_kind_of(Array)
325
+ a.tracks.size.should == 1
326
+ a.tracks.first.should be_a_kind_of(EagerTrack)
327
+ a.tracks.first.values.should == {:id => 3, :album_id => 1}
328
+ MODEL_DB.sqls.length.should == 2
329
+ end
330
+
331
+ it "should respect :conditions when eagerly loading" do
332
+ EagerBandMember.many_to_many :good_bands, :clone=>:bands, :conditions=>{:a=>32}
333
+ a = EagerBandMember.eager(:good_bands).all
334
+ a.should be_a_kind_of(Array)
335
+ a.size.should == 1
336
+ a.first.should be_a_kind_of(EagerBandMember)
337
+ a.first.values.should == {:id => 5}
338
+ MODEL_DB.sqls.should == ['SELECT * FROM members', 'SELECT bands.*, bm.member_id AS x_foreign_key_x FROM bands INNER JOIN bm ON ((bm.band_id = bands.id) AND (bm.member_id IN (5))) WHERE (a = 32) ORDER BY id']
339
+ a = a.first
340
+ a.good_bands.should be_a_kind_of(Array)
341
+ a.good_bands.size.should == 1
342
+ a.good_bands.first.should be_a_kind_of(EagerBand)
343
+ a.good_bands.first.values.should == {:id => 2}
344
+ MODEL_DB.sqls.length.should == 2
345
+
346
+ MODEL_DB.sqls.clear
347
+ EagerBandMember.many_to_many :good_bands, :clone=>:bands, :conditions=>"x = 1"
348
+ a = EagerBandMember.eager(:good_bands).all
349
+ MODEL_DB.sqls.should == ['SELECT * FROM members', 'SELECT bands.*, bm.member_id AS x_foreign_key_x FROM bands INNER JOIN bm ON ((bm.band_id = bands.id) AND (bm.member_id IN (5))) WHERE (x = 1) ORDER BY id']
350
+ end
351
+
352
+ it "should respect :order when eagerly loading" do
353
+ a = EagerBandMember.eager(:bands).all
354
+ a.should be_a_kind_of(Array)
355
+ a.size.should == 1
356
+ a.first.should be_a_kind_of(EagerBandMember)
357
+ a.first.values.should == {:id => 5}
358
+ MODEL_DB.sqls.should == ['SELECT * FROM members', 'SELECT bands.*, bm.member_id AS x_foreign_key_x FROM bands INNER JOIN bm ON ((bm.band_id = bands.id) AND (bm.member_id IN (5))) ORDER BY id']
359
+ a = a.first
360
+ a.bands.should be_a_kind_of(Array)
361
+ a.bands.size.should == 1
362
+ a.bands.first.should be_a_kind_of(EagerBand)
363
+ a.bands.first.values.should == {:id => 2}
364
+ MODEL_DB.sqls.length.should == 2
365
+ end
366
+
367
+ it "should populate the reciprocal many_to_one association when eagerly loading the one_to_many association" do
368
+ a = EagerAlbum.eager(:tracks).all
369
+ a.should be_a_kind_of(Array)
370
+ a.size.should == 1
371
+ a.first.should be_a_kind_of(EagerAlbum)
372
+ a.first.values.should == {:id => 1, :band_id => 2}
373
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE (tracks.album_id IN (1))']
374
+ a = a.first
375
+ a.tracks.should be_a_kind_of(Array)
376
+ a.tracks.size.should == 1
377
+ a.tracks.first.should be_a_kind_of(EagerTrack)
378
+ a.tracks.first.values.should == {:id => 3, :album_id=>1}
379
+ a.tracks.first.album.should be_a_kind_of(EagerAlbum)
380
+ a.tracks.first.album.should == a
381
+ MODEL_DB.sqls.length.should == 2
382
+ end
383
+
384
+ it "should cache the negative lookup when eagerly loading a many_to_one association" do
385
+ a = EagerAlbum.eager(:band).filter(:id=>101).all
386
+ a.should be_a_kind_of(Array)
387
+ a.size.should == 1
388
+ a.first.should be_a_kind_of(EagerAlbum)
389
+ a.first.values.should == {:id => 101, :band_id => 101}
390
+ MODEL_DB.sqls.should == ['SELECT * FROM albums WHERE (id = 101)', 'SELECT * FROM bands WHERE (bands.id IN (101))']
391
+ a = a.first
392
+ a.associations.fetch(:band, 2).should == nil
393
+ a.band.should == nil
394
+ MODEL_DB.sqls.length.should == 2
395
+ end
396
+
397
+ it "should cache the negative lookup when eagerly loading a *_to_many associations" do
398
+ a = EagerBand.eager(:albums).filter('id > 100').all
399
+ a.should be_a_kind_of(Array)
400
+ a.size.should == 2
401
+ a.first.should be_a_kind_of(EagerBand)
402
+ a.first.values.should == {:id => 101}
403
+ a.last.values.should == {:id => 102}
404
+ MODEL_DB.sqls.should == ['SELECT * FROM bands WHERE (id > 100)', 'SELECT * FROM albums WHERE (albums.band_id IN (101, 102))', "SELECT * FROM tracks WHERE (tracks.album_id IN (101))"]
405
+ a.first.associations[:albums].should be_a_kind_of(Array)
406
+ a.first.albums.length.should == 1
407
+ a.first.albums.first.should be_a_kind_of(EagerAlbum)
408
+ a.last.associations[:albums].should == []
409
+ a.last.albums.should == []
410
+ MODEL_DB.sqls.length.should == 3
411
+ end
412
+
413
+ it "should use the association's block when eager loading by default" do
414
+ EagerAlbum.eager(:good_tracks).all
415
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM tracks WHERE ((tracks.album_id IN (1)) AND (name = 'Good'))"]
416
+ end
417
+
418
+ it "should use the eager_block option when eager loading if given" do
419
+ EagerBand.eager(:good_albums).all
420
+ MODEL_DB.sqls.should == ['SELECT * FROM bands', "SELECT * FROM albums WHERE ((albums.band_id IN (2)) AND (name = 'good'))"]
421
+ MODEL_DB.sqls.clear
422
+ EagerBand.eager(:good_albums=>:good_tracks).all
423
+ MODEL_DB.sqls.should == ['SELECT * FROM bands', "SELECT * FROM albums WHERE ((albums.band_id IN (2)) AND (name = 'good'))", "SELECT * FROM tracks WHERE ((tracks.album_id IN (1)) AND (name = 'Good'))"]
424
+ end
425
+
426
+ it "should raise an error when attempting to eagerly load an association with the :allow_eager option set to false" do
427
+ proc{EagerBand.eager(:self_titled_albums).all}.should raise_error(Sequel::Error)
428
+ proc{EagerBand.eager(:albums_by_name).all}.should raise_error(Sequel::Error)
429
+ end
430
+
431
+ it "should respect the association's :select option" do
432
+ EagerAlbum.eager(:band_name).all
433
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT id, name FROM bands WHERE (bands.id IN (2))"]
434
+ MODEL_DB.sqls.clear
435
+ EagerAlbum.eager(:track_names).all
436
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT id, name FROM tracks WHERE (tracks.album_id IN (1))"]
437
+ MODEL_DB.sqls.clear
438
+ EagerAlbum.eager(:genre_names).all
439
+ 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)))"]
440
+ end
441
+
442
+ it "should respect the association's :primary_key option" do
443
+ EagerAlbum.many_to_one :special_band, :class=>:EagerBand, :primary_key=>:p_k, :key=>:band_id
444
+ EagerBand.dataset.extend(Module.new {
445
+ def fetch_rows(sql)
446
+ MODEL_DB.sqls << sql
447
+ yield({:p_k=>2, :id=>1})
448
+ end
449
+ })
450
+ as = EagerAlbum.eager(:special_band).all
451
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM bands WHERE (bands.p_k IN (2))"]
452
+ as.length.should == 1
453
+ as.first.special_band.should == EagerBand.load(:p_k=>2, :id=>1)
454
+ MODEL_DB.sqls.clear
455
+ EagerAlbum.one_to_many :special_tracks, :class=>:EagerTrack, :primary_key=>:band_id, :key=>:album_id
456
+ EagerTrack.dataset.extend(Module.new {
457
+ def fetch_rows(sql)
458
+ MODEL_DB.sqls << sql
459
+ yield({:album_id=>2, :id=>1})
460
+ end
461
+ })
462
+ as = EagerAlbum.eager(:special_tracks).all
463
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM tracks WHERE (tracks.album_id IN (2))"]
464
+ as.length.should == 1
465
+ as.first.special_tracks.should == [EagerTrack.load(:album_id=>2, :id=>1)]
466
+ end
467
+
468
+ it "should respect many_to_many association's :left_primary_key and :right_primary_key options" do
469
+ 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
470
+ EagerGenre.dataset.extend(Module.new {
471
+ def fetch_rows(sql)
472
+ MODEL_DB.sqls << sql
473
+ yield({:x_foreign_key_x=>2, :id=>5})
474
+ yield({:x_foreign_key_x=>2, :id=>6})
475
+ end
476
+ })
477
+ as = EagerAlbum.eager(:special_genres).all
478
+ 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)))"]
479
+ as.length.should == 1
480
+ as.first.special_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
481
+ end
482
+
483
+ it "should use the :eager_loader association option when eager loading" do
484
+ EagerAlbum.many_to_one :special_band, :eager_loader=>(proc do |key_hash, records, assocs|
485
+ item = EagerBand.filter(:album_id=>records.collect{|r| [r.pk, r.pk*2]}.flatten).order(:name).first
486
+ records.each{|r| r.associations[:special_band] = item}
487
+ end)
488
+ EagerAlbum.one_to_many :special_tracks, :eager_loader=>(proc do |key_hash, records, assocs|
489
+ items = EagerTrack.filter(:album_id=>records.collect{|r| [r.pk, r.pk*2]}.flatten).all
490
+ records.each{|r| r.associations[:special_tracks] = items}
491
+ end)
492
+ EagerAlbum.many_to_many :special_genres, :class=>:EagerGenre, :eager_loader=>(proc do |key_hash, records, assocs|
493
+ items = EagerGenre.inner_join(:ag, [:genre_id]).filter(:album_id=>records.collect{|r| r.pk}).all
494
+ records.each{|r| r.associations[:special_genres] = items}
495
+ end)
496
+ a = EagerAlbum.eager(:special_genres, :special_tracks, :special_band).all
497
+ a.should be_a_kind_of(Array)
498
+ a.size.should == 1
499
+ a.first.should be_a_kind_of(EagerAlbum)
500
+ a.first.values.should == {:id => 1, :band_id => 2}
501
+ MODEL_DB.sqls.length.should == 4
502
+ MODEL_DB.sqls[0].should == 'SELECT * FROM albums'
503
+ MODEL_DB.sqls[1..-1].should(include('SELECT * FROM bands WHERE (album_id IN (1, 2)) ORDER BY name LIMIT 1'))
504
+ MODEL_DB.sqls[1..-1].should(include('SELECT * FROM tracks WHERE (album_id IN (1, 2))'))
505
+ MODEL_DB.sqls[1..-1].should(include('SELECT * FROM genres INNER JOIN ag USING (genre_id) WHERE (album_id IN (1))'))
506
+ a = a.first
507
+ a.special_band.should be_a_kind_of(EagerBand)
508
+ a.special_band.values.should == {:id => 2}
509
+ a.special_tracks.should be_a_kind_of(Array)
510
+ a.special_tracks.size.should == 1
511
+ a.special_tracks.first.should be_a_kind_of(EagerTrack)
512
+ a.special_tracks.first.values.should == {:id => 3, :album_id=>1}
513
+ a.special_genres.should be_a_kind_of(Array)
514
+ a.special_genres.size.should == 1
515
+ a.special_genres.first.should be_a_kind_of(EagerGenre)
516
+ a.special_genres.first.values.should == {:id => 4}
517
+ MODEL_DB.sqls.length.should == 4
518
+ end
519
+
520
+ it "should respect :after_load callbacks on associations when eager loading" do
521
+ EagerAlbum.many_to_one :al_band, :class=>'EagerBand', :key=>:band_id, :after_load=>proc{|o, a| a.id *=2}
522
+ EagerAlbum.one_to_many :al_tracks, :class=>'EagerTrack', :key=>:album_id, :after_load=>proc{|o, os| os.each{|a| a.id *=2}}
523
+ 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}}
524
+ a = EagerAlbum.eager(:al_band, :al_tracks, :al_genres).all.first
525
+ a.should == EagerAlbum.load(:id => 1, :band_id => 2)
526
+ a.al_band.should == EagerBand.load(:id=>4)
527
+ a.al_tracks.should == [EagerTrack.load(:id=>6, :album_id=>1)]
528
+ a.al_genres.should == [EagerGenre.load(:id=>8)]
529
+ end
530
+ end
531
+
532
+ describe Sequel::Model, "#eager_graph" do
533
+ after(:all) do
534
+ class ::MockDataset
535
+ alias clone orig_clone
536
+ end
537
+ end
538
+
539
+ before(:all) do
540
+ class ::MockDataset
541
+ alias orig_clone clone
542
+ def clone(opts = {})
543
+ c = super()
544
+ c.opts = @opts.merge(opts)
545
+ c.instance_variable_set(:@columns, (@columns.dup if @columns))
546
+ c
547
+ end
548
+ end
549
+
550
+ class ::GraphAlbum < Sequel::Model(:albums)
551
+ dataset.opts[:from] = [:albums]
552
+ columns :id, :band_id
553
+ many_to_one :band, :class=>'GraphBand', :key=>:band_id
554
+ one_to_many :tracks, :class=>'GraphTrack', :key=>:album_id
555
+ many_to_many :genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag
556
+ many_to_one :previous_album, :class=>'GraphAlbum'
557
+ end
558
+
559
+ class ::GraphBand < Sequel::Model(:bands)
560
+ dataset.opts[:from] = [:bands]
561
+ columns :id, :vocalist_id
562
+ many_to_one :vocalist, :class=>'GraphBandMember', :key=>:vocalist_id
563
+ one_to_many :albums, :class=>'GraphAlbum', :key=>:band_id
564
+ many_to_many :members, :class=>'GraphBandMember', :left_key=>:band_id, :right_key=>:member_id, :join_table=>:bm
565
+ many_to_many :genres, :class=>'GraphGenre', :left_key=>:band_id, :right_key=>:genre_id, :join_table=>:bg
566
+ end
567
+
568
+ class ::GraphTrack < Sequel::Model(:tracks)
569
+ dataset.opts[:from] = [:tracks]
570
+ columns :id, :album_id
571
+ many_to_one :album, :class=>'GraphAlbum', :key=>:album_id
572
+ end
573
+
574
+ class ::GraphGenre < Sequel::Model(:genres)
575
+ dataset.opts[:from] = [:genres]
576
+ columns :id
577
+ many_to_many :albums, :class=>'GraphAlbum', :left_key=>:genre_id, :right_key=>:album_id, :join_table=>:ag
578
+ end
579
+
580
+ class ::GraphBandMember < Sequel::Model(:members)
581
+ dataset.opts[:from] = [:members]
582
+ columns :id
583
+ many_to_many :bands, :class=>'GraphBand', :left_key=>:member_id, :right_key=>:band_id, :join_table=>:bm
584
+ end
585
+ end
586
+
587
+ it "should raise an error if called without a symbol or hash" do
588
+ proc{GraphAlbum.eager_graph(Object.new)}.should raise_error(Sequel::Error)
589
+ end
590
+
591
+ it "should eagerly load a single many_to_one association" do
592
+ ds = GraphAlbum.eager_graph(:band)
593
+ 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)'
594
+ def ds.fetch_rows(sql, &block)
595
+ yield({:id=>1, :band_id=>2, :band_id_0=>2, :vocalist_id=>3})
596
+ end
597
+ a = ds.all
598
+ a.should be_a_kind_of(Array)
599
+ a.size.should == 1
600
+ a.first.should be_a_kind_of(GraphAlbum)
601
+ a.first.values.should == {:id => 1, :band_id => 2}
602
+ a = a.first
603
+ a.band.should be_a_kind_of(GraphBand)
604
+ a.band.values.should == {:id => 2, :vocalist_id=>3}
605
+ end
606
+
607
+ it "should eagerly load a single one_to_many association" do
608
+ ds = GraphAlbum.eager_graph(:tracks)
609
+ 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)'
610
+ def ds.fetch_rows(sql, &block)
611
+ yield({:id=>1, :band_id=>2, :tracks_id=>3, :album_id=>1})
612
+ end
613
+ a = ds.all
614
+ a.should be_a_kind_of(Array)
615
+ a.size.should == 1
616
+ a.first.should be_a_kind_of(GraphAlbum)
617
+ a.first.values.should == {:id => 1, :band_id => 2}
618
+ a = a.first
619
+ a.tracks.should be_a_kind_of(Array)
620
+ a.tracks.size.should == 1
621
+ a.tracks.first.should be_a_kind_of(GraphTrack)
622
+ a.tracks.first.values.should == {:id => 3, :album_id=>1}
623
+ end
624
+
625
+ it "should eagerly load a single many_to_many association" do
626
+ ds = GraphAlbum.eager_graph(:genres)
627
+ 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)'
628
+ def ds.fetch_rows(sql, &block)
629
+ yield({:id=>1, :band_id=>2, :genres_id=>4})
630
+ end
631
+ a = ds.all
632
+ a.should be_a_kind_of(Array)
633
+ a.size.should == 1
634
+ a.first.should be_a_kind_of(GraphAlbum)
635
+ a.first.values.should == {:id => 1, :band_id => 2}
636
+ a = a.first
637
+ a.genres.should be_a_kind_of(Array)
638
+ a.genres.size.should == 1
639
+ a.genres.first.should be_a_kind_of(GraphGenre)
640
+ a.genres.first.values.should == {:id => 4}
641
+ end
642
+
643
+ it "should eagerly load multiple associations in a single call" do
644
+ ds = GraphAlbum.eager_graph(:genres, :tracks, :band)
645
+ 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)'
646
+ def ds.fetch_rows(sql, &block)
647
+ yield({:id=>1, :band_id=>2, :genres_id=>4, :tracks_id=>3, :album_id=>1, :band_id_0=>2, :vocalist_id=>6})
648
+ end
649
+ a = ds.all
650
+ a.should be_a_kind_of(Array)
651
+ a.size.should == 1
652
+ a.first.should be_a_kind_of(GraphAlbum)
653
+ a.first.values.should == {:id => 1, :band_id => 2}
654
+ a = a.first
655
+ a.band.should be_a_kind_of(GraphBand)
656
+ a.band.values.should == {:id => 2, :vocalist_id=>6}
657
+ a.tracks.should be_a_kind_of(Array)
658
+ a.tracks.size.should == 1
659
+ a.tracks.first.should be_a_kind_of(GraphTrack)
660
+ a.tracks.first.values.should == {:id => 3, :album_id=>1}
661
+ a.genres.should be_a_kind_of(Array)
662
+ a.genres.size.should == 1
663
+ a.genres.first.should be_a_kind_of(GraphGenre)
664
+ a.genres.first.values.should == {:id => 4}
665
+ end
666
+
667
+ it "should eagerly load multiple associations in separate calls" do
668
+ ds = GraphAlbum.eager_graph(:genres).eager_graph(:tracks).eager_graph(:band)
669
+ 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)'
670
+ def ds.fetch_rows(sql, &block)
671
+ yield({:id=>1, :band_id=>2, :genres_id=>4, :tracks_id=>3, :album_id=>1, :band_id_0=>2, :vocalist_id=>6})
672
+ end
673
+ a = ds.all
674
+ a.should be_a_kind_of(Array)
675
+ a.size.should == 1
676
+ a.first.should be_a_kind_of(GraphAlbum)
677
+ a.first.values.should == {:id => 1, :band_id => 2}
678
+ a = a.first
679
+ a.band.should be_a_kind_of(GraphBand)
680
+ a.band.values.should == {:id => 2, :vocalist_id=>6}
681
+ a.tracks.should be_a_kind_of(Array)
682
+ a.tracks.size.should == 1
683
+ a.tracks.first.should be_a_kind_of(GraphTrack)
684
+ a.tracks.first.values.should == {:id => 3, :album_id=>1}
685
+ a.genres.should be_a_kind_of(Array)
686
+ a.genres.size.should == 1
687
+ a.genres.first.should be_a_kind_of(GraphGenre)
688
+ a.genres.first.values.should == {:id => 4}
689
+ end
690
+
691
+ it "should allow cascading of eager loading for associations of associated models" do
692
+ ds = GraphTrack.eager_graph(:album=>{:band=>:members})
693
+ 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)'
694
+ def ds.fetch_rows(sql, &block)
695
+ yield({:id=>3, :album_id=>1, :album_id_0=>1, :band_id=>2, :members_id=>5, :band_id_0=>2, :vocalist_id=>6})
696
+ end
697
+ a = ds.all
698
+ a.should be_a_kind_of(Array)
699
+ a.size.should == 1
700
+ a.first.should be_a_kind_of(GraphTrack)
701
+ a.first.values.should == {:id => 3, :album_id => 1}
702
+ a = a.first
703
+ a.album.should be_a_kind_of(GraphAlbum)
704
+ a.album.values.should == {:id => 1, :band_id => 2}
705
+ a.album.band.should be_a_kind_of(GraphBand)
706
+ a.album.band.values.should == {:id => 2, :vocalist_id=>6}
707
+ a.album.band.members.should be_a_kind_of(Array)
708
+ a.album.band.members.size.should == 1
709
+ a.album.band.members.first.should be_a_kind_of(GraphBandMember)
710
+ a.album.band.members.first.values.should == {:id => 5}
711
+ end
712
+
713
+ it "should allow cascading of eager loading for multiple *_to_many associations, eliminating duplicates caused by cartesian products" do
714
+ ds = GraphBand.eager_graph({:albums=>:tracks}, :members)
715
+ ds.sql.should == 'SELECT bands.id, bands.vocalist_id, albums.id AS albums_id, albums.band_id, tracks.id AS tracks_id, tracks.album_id, members.id AS members_id FROM bands LEFT OUTER JOIN albums ON (albums.band_id = bands.id) LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id) LEFT OUTER JOIN bm ON (bm.band_id = bands.id) LEFT OUTER JOIN members ON (members.id = bm.member_id)'
716
+ def ds.fetch_rows(sql, &block)
717
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>4, :album_id=>3, :members_id=>5})
718
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>4, :album_id=>3, :members_id=>6})
719
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>5, :album_id=>3, :members_id=>5})
720
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>5, :album_id=>3, :members_id=>6})
721
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>6, :album_id=>4, :members_id=>5})
722
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>6, :album_id=>4, :members_id=>6})
723
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>7, :album_id=>4, :members_id=>5})
724
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>7, :album_id=>4, :members_id=>6})
725
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>8, :album_id=>5, :members_id=>5})
726
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>8, :album_id=>5, :members_id=>6})
727
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>9, :album_id=>5, :members_id=>5})
728
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>9, :album_id=>5, :members_id=>6})
729
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>1, :album_id=>6, :members_id=>5})
730
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>1, :album_id=>6, :members_id=>6})
731
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>2, :album_id=>6, :members_id=>5})
732
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>2, :album_id=>6, :members_id=>6})
733
+ end
734
+ a = ds.all
735
+ a.should == [GraphBand.load(:id=>1, :vocalist_id=>2), GraphBand.load(:id=>2, :vocalist_id=>2)]
736
+ members = a.map{|x| x.members}
737
+ members.should == [[GraphBandMember.load(:id=>5), GraphBandMember.load(:id=>6)], [GraphBandMember.load(:id=>5), GraphBandMember.load(:id=>6)]]
738
+ albums = a.map{|x| x.albums}
739
+ albums.should == [[GraphAlbum.load(:id=>3, :band_id=>1), GraphAlbum.load(:id=>4, :band_id=>1)], [GraphAlbum.load(:id=>5, :band_id=>2), GraphAlbum.load(:id=>6, :band_id=>2)]]
740
+ tracks = albums.map{|x| x.map{|y| y.tracks}}
741
+ tracks.should == [[[GraphTrack.load(:id=>4, :album_id=>3), GraphTrack.load(:id=>5, :album_id=>3)], [GraphTrack.load(:id=>6, :album_id=>4), GraphTrack.load(:id=>7, :album_id=>4)]], [[GraphTrack.load(:id=>8, :album_id=>5), GraphTrack.load(:id=>9, :album_id=>5)], [GraphTrack.load(:id=>1, :album_id=>6), GraphTrack.load(:id=>2, :album_id=>6)]]]
742
+ end
743
+
744
+ it "should populate the reciprocal many_to_one association when eagerly loading the one_to_many association" do
745
+ MODEL_DB.reset
746
+ ds = GraphAlbum.eager_graph(:tracks)
747
+ 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)'
748
+ def ds.fetch_rows(sql, &block)
749
+ @db << sql
750
+ yield({:id=>1, :band_id=>2, :tracks_id=>3, :album_id=>1})
751
+ end
752
+ a = ds.all
753
+ a.should be_a_kind_of(Array)
754
+ a.size.should == 1
755
+ a.first.should be_a_kind_of(GraphAlbum)
756
+ a.first.values.should == {:id => 1, :band_id => 2}
757
+ a = a.first
758
+ a.tracks.should be_a_kind_of(Array)
759
+ a.tracks.size.should == 1
760
+ a.tracks.first.should be_a_kind_of(GraphTrack)
761
+ a.tracks.first.values.should == {:id => 3, :album_id=>1}
762
+ a.tracks.first.album.should be_a_kind_of(GraphAlbum)
763
+ a.tracks.first.album.should == a
764
+ MODEL_DB.sqls.length.should == 1
765
+ end
766
+
767
+ it "should eager load multiple associations from the same table" do
768
+ ds = GraphBand.eager_graph(:vocalist, :members)
769
+ ds.sql.should == 'SELECT bands.id, bands.vocalist_id, vocalist.id AS vocalist_id_0, members.id AS members_id FROM bands LEFT OUTER JOIN members AS vocalist ON (vocalist.id = bands.vocalist_id) LEFT OUTER JOIN bm ON (bm.band_id = bands.id) LEFT OUTER JOIN members ON (members.id = bm.member_id)'
770
+ def ds.fetch_rows(sql, &block)
771
+ yield({:id=>2, :vocalist_id=>6, :vocalist_id_0=>6, :members_id=>5})
772
+ end
773
+ a = ds.all
774
+ a.should be_a_kind_of(Array)
775
+ a.size.should == 1
776
+ a.first.should be_a_kind_of(GraphBand)
777
+ a.first.values.should == {:id => 2, :vocalist_id => 6}
778
+ a = a.first
779
+ a.vocalist.should be_a_kind_of(GraphBandMember)
780
+ a.vocalist.values.should == {:id => 6}
781
+ a.members.should be_a_kind_of(Array)
782
+ a.members.size.should == 1
783
+ a.members.first.should be_a_kind_of(GraphBandMember)
784
+ a.members.first.values.should == {:id => 5}
785
+ end
786
+
787
+ it "should give you a graph of tables when called without .all" do
788
+ ds = GraphAlbum.eager_graph(:band)
789
+ 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)'
790
+ def ds.fetch_rows(sql, &block)
791
+ yield({:id=>1, :band_id=>2, :band_id_0=>2, :vocalist_id=>3})
792
+ end
793
+ ds.first.should == {:albums=>GraphAlbum.load(:id => 1, :band_id => 2), :band=>GraphBand.load(:id => 2, :vocalist_id=>3)}
794
+ end
795
+
796
+ it "should not drop any associated objects if the graph could not be a cartesian product" do
797
+ ds = GraphBand.eager_graph(:members, :vocalist)
798
+ ds.sql.should == 'SELECT bands.id, bands.vocalist_id, members.id AS members_id, vocalist.id AS vocalist_id_0 FROM bands LEFT OUTER JOIN bm ON (bm.band_id = bands.id) LEFT OUTER JOIN members ON (members.id = bm.member_id) LEFT OUTER JOIN members AS vocalist ON (vocalist.id = bands.vocalist_id)'
799
+ def ds.fetch_rows(sql, &block)
800
+ yield({:id=>2, :vocalist_id=>6, :members_id=>5, :vocalist_id_0=>6})
801
+ yield({:id=>2, :vocalist_id=>6, :members_id=>5, :vocalist_id_0=>6})
802
+ end
803
+ a = ds.all
804
+ a.should be_a_kind_of(Array)
805
+ a.size.should == 1
806
+ a.first.should be_a_kind_of(GraphBand)
807
+ a.first.values.should == {:id => 2, :vocalist_id => 6}
808
+ a = a.first
809
+ a.vocalist.should be_a_kind_of(GraphBandMember)
810
+ a.vocalist.values.should == {:id => 6}
811
+ a.members.should be_a_kind_of(Array)
812
+ a.members.size.should == 2
813
+ a.members.first.should be_a_kind_of(GraphBandMember)
814
+ a.members.first.values.should == {:id => 5}
815
+ a.members.last.should be_a_kind_of(GraphBandMember)
816
+ a.members.last.values.should == {:id => 5}
817
+ end
818
+
819
+ it "should drop duplicate items that occur in sequence if the graph could be a cartesian product" do
820
+ ds = GraphBand.eager_graph(:members, :genres)
821
+ ds.sql.should == 'SELECT bands.id, bands.vocalist_id, members.id AS members_id, genres.id AS genres_id FROM bands LEFT OUTER JOIN bm ON (bm.band_id = bands.id) LEFT OUTER JOIN members ON (members.id = bm.member_id) LEFT OUTER JOIN bg ON (bg.band_id = bands.id) LEFT OUTER JOIN genres ON (genres.id = bg.genre_id)'
822
+ def ds.fetch_rows(sql, &block)
823
+ yield({:id=>2, :vocalist_id=>6, :members_id=>5, :genres_id=>7})
824
+ yield({:id=>2, :vocalist_id=>6, :members_id=>5, :genres_id=>8})
825
+ yield({:id=>2, :vocalist_id=>6, :members_id=>6, :genres_id=>7})
826
+ yield({:id=>2, :vocalist_id=>6, :members_id=>6, :genres_id=>8})
827
+ end
828
+ a = ds.all
829
+ a.should be_a_kind_of(Array)
830
+ a.size.should == 1
831
+ a.first.should be_a_kind_of(GraphBand)
832
+ a.first.values.should == {:id => 2, :vocalist_id => 6}
833
+ a = a.first
834
+ a.members.should be_a_kind_of(Array)
835
+ a.members.size.should == 2
836
+ a.members.first.should be_a_kind_of(GraphBandMember)
837
+ a.members.first.values.should == {:id => 5}
838
+ a.members.last.should be_a_kind_of(GraphBandMember)
839
+ a.members.last.values.should == {:id => 6}
840
+ a.genres.size.should == 2
841
+ a.genres.first.should be_a_kind_of(GraphGenre)
842
+ a.genres.first.values.should == {:id => 7}
843
+ a.genres.last.should be_a_kind_of(GraphGenre)
844
+ a.genres.last.values.should == {:id => 8}
845
+ end
846
+
847
+ it "should be able to be used in combination with #eager" do
848
+ MODEL_DB.reset
849
+ ds = GraphAlbum.eager_graph(:tracks).eager(:genres)
850
+ def ds.fetch_rows(sql, &block)
851
+ @db << sql
852
+ yield({:id=>1, :band_id=>2, :tracks_id=>3, :album_id=>1})
853
+ end
854
+ ds2 = GraphGenre.dataset
855
+ def ds2.fetch_rows(sql, &block)
856
+ @db << sql
857
+ yield({:id=>6, :x_foreign_key_x=>1})
858
+ end
859
+ a = ds.all
860
+ a.should be_a_kind_of(Array)
861
+ a.size.should == 1
862
+ a.first.should be_a_kind_of(GraphAlbum)
863
+ a.first.values.should == {:id => 1, :band_id => 2}
864
+ a = a.first
865
+ a.tracks.should be_a_kind_of(Array)
866
+ a.tracks.size.should == 1
867
+ a.tracks.first.should be_a_kind_of(GraphTrack)
868
+ a.tracks.first.values.should == {:id=>3, :album_id=>1}
869
+ a.genres.should be_a_kind_of(Array)
870
+ a.genres.size.should == 1
871
+ a.genres.first.should be_a_kind_of(GraphGenre)
872
+ a.genres.first.values.should == {:id=>6}
873
+ MODEL_DB.sqls.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)',
874
+ "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)))"]
875
+ end
876
+
877
+ it "should handle no associated records for a single many_to_one association" do
878
+ ds = GraphAlbum.eager_graph(:band)
879
+ 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)'
880
+ def ds.fetch_rows(sql, &block)
881
+ yield({:id=>1, :band_id=>2, :band_id_0=>nil, :vocalist_id=>nil})
882
+ end
883
+ a = ds.all
884
+ a.should be_a_kind_of(Array)
885
+ a.size.should == 1
886
+ a.first.should be_a_kind_of(GraphAlbum)
887
+ a.first.values.should == {:id => 1, :band_id => 2}
888
+ a.first.associations.fetch(:band, 2).should == nil
889
+ end
890
+
891
+ it "should handle no associated records for a single one_to_many association" do
892
+ ds = GraphAlbum.eager_graph(:tracks)
893
+ 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)'
894
+ def ds.fetch_rows(sql, &block)
895
+ yield({:id=>1, :band_id=>2, :tracks_id=>nil, :album_id=>nil})
896
+ end
897
+ a = ds.all
898
+ a.should be_a_kind_of(Array)
899
+ a.size.should == 1
900
+ a.first.should be_a_kind_of(GraphAlbum)
901
+ a.first.values.should == {:id => 1, :band_id => 2}
902
+ a.first.tracks.should == []
903
+ end
904
+
905
+ it "should handle no associated records for a single many_to_many association" do
906
+ ds = GraphAlbum.eager_graph(:genres)
907
+ 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)'
908
+ def ds.fetch_rows(sql, &block)
909
+ yield({:id=>1, :band_id=>2, :genres_id=>nil})
910
+ end
911
+ a = ds.all
912
+ a.should be_a_kind_of(Array)
913
+ a.size.should == 1
914
+ a.first.should be_a_kind_of(GraphAlbum)
915
+ a.first.values.should == {:id => 1, :band_id => 2}
916
+ a.first.genres.should == []
917
+ end
918
+
919
+ it "should handle missing associated records when loading multiple associations" do
920
+ ds = GraphAlbum.eager_graph(:genres, :tracks, :band)
921
+ 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)'
922
+ def ds.fetch_rows(sql, &block)
923
+ yield({:id=>1, :band_id=>2, :genres_id=>nil, :tracks_id=>3, :album_id=>1, :band_id_0=>nil, :vocalist_id=>nil})
924
+ yield({:id=>1, :band_id=>2, :genres_id=>nil, :tracks_id=>4, :album_id=>1, :band_id_0=>nil, :vocalist_id=>nil})
925
+ yield({:id=>1, :band_id=>2, :genres_id=>nil, :tracks_id=>5, :album_id=>1, :band_id_0=>nil, :vocalist_id=>nil})
926
+ yield({:id=>1, :band_id=>2, :genres_id=>nil, :tracks_id=>6, :album_id=>1, :band_id_0=>nil, :vocalist_id=>nil})
927
+ end
928
+ a = ds.all
929
+ a.should be_a_kind_of(Array)
930
+ a.size.should == 1
931
+ a.first.should be_a_kind_of(GraphAlbum)
932
+ a.first.values.should == {:id => 1, :band_id => 2}
933
+ a = a.first
934
+ a.tracks.should be_a_kind_of(Array)
935
+ a.tracks.size.should == 4
936
+ a.tracks.first.should be_a_kind_of(GraphTrack)
937
+ a.tracks.collect{|x|x[:id]}.should == [3,4,5,6]
938
+ a.associations.fetch(:band, 2).should == nil
939
+ a.genres.should == []
940
+ end
941
+
942
+ it "should handle missing associated records when cascading eager loading for associations of associated models" do
943
+ ds = GraphTrack.eager_graph(:album=>{:band=>:members})
944
+ 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)'
945
+ def ds.fetch_rows(sql, &block)
946
+ yield({:id=>2, :album_id=>2, :album_id_0=>nil, :band_id=>nil, :members_id=>nil, :band_id_0=>nil, :vocalist_id=>nil})
947
+ yield({:id=>3, :album_id=>3, :album_id_0=>3, :band_id=>3, :members_id=>nil, :band_id_0=>nil, :vocalist_id=>nil})
948
+ yield({:id=>4, :album_id=>4, :album_id_0=>4, :band_id=>2, :members_id=>nil, :band_id_0=>2, :vocalist_id=>6})
949
+ yield({:id=>5, :album_id=>1, :album_id_0=>1, :band_id=>4, :members_id=>5, :band_id_0=>4, :vocalist_id=>8})
950
+ yield({:id=>5, :album_id=>1, :album_id_0=>1, :band_id=>4, :members_id=>6, :band_id_0=>4, :vocalist_id=>8})
951
+ end
952
+ a = ds.all
953
+ a.should be_a_kind_of(Array)
954
+ a.size.should == 4
955
+ a.first.should be_a_kind_of(GraphTrack)
956
+ a.collect{|x|x[:id]}.should == [2,3,4,5]
957
+ a[0].associations.fetch(:album, 2).should == nil
958
+ a[1].album.should be_a_kind_of(GraphAlbum)
959
+ a[1].album.values.should == {:id => 3, :band_id => 3}
960
+ a[1].album.associations.fetch(:band, 2).should == nil
961
+ a[2].album.should be_a_kind_of(GraphAlbum)
962
+ a[2].album.values.should == {:id => 4, :band_id => 2}
963
+ a[2].album.band.should be_a_kind_of(GraphBand)
964
+ a[2].album.band.values.should == {:id => 2, :vocalist_id=>6}
965
+ a[2].album.band.members.should == []
966
+ a[3].album.should be_a_kind_of(GraphAlbum)
967
+ a[3].album.values.should == {:id => 1, :band_id => 4}
968
+ a[3].album.band.should be_a_kind_of(GraphBand)
969
+ a[3].album.band.values.should == {:id => 4, :vocalist_id=>8}
970
+ a[3].album.band.members.size.should == 2
971
+ a[3].album.band.members.first.should be_a_kind_of(GraphBandMember)
972
+ a[3].album.band.members.first.values.should == {:id => 5}
973
+ a[3].album.band.members.last.should be_a_kind_of(GraphBandMember)
974
+ a[3].album.band.members.last.values.should == {:id => 6}
975
+ end
976
+
977
+ it "should respect the association's :primary_key option" do
978
+ GraphAlbum.many_to_one :inner_band, :class=>'GraphBand', :key=>:band_id, :primary_key=>:vocalist_id
979
+ ds = GraphAlbum.eager_graph(:inner_band)
980
+ 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)'
981
+ def ds.fetch_rows(sql, &block)
982
+ yield({:id=>3, :band_id=>2, :inner_band_id=>5, :vocalist_id=>2})
983
+ end
984
+ as = ds.all
985
+ as.should == [GraphAlbum.load(:id=>3, :band_id=>2)]
986
+ as.first.inner_band.should == GraphBand.load(:id=>5, :vocalist_id=>2)
987
+
988
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :primary_key=>:band_id
989
+ ds = GraphAlbum.eager_graph(:right_tracks)
990
+ 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)'
991
+ def ds.fetch_rows(sql, &block)
992
+ yield({:id=>3, :band_id=>2, :right_tracks_id=>5, :album_id=>2})
993
+ yield({:id=>3, :band_id=>2, :right_tracks_id=>6, :album_id=>2})
994
+ end
995
+ as = ds.all
996
+ as.should == [GraphAlbum.load(:id=>3, :band_id=>2)]
997
+ as.first.right_tracks.should == [GraphTrack.load(:id=>5, :album_id=>2), GraphTrack.load(:id=>6, :album_id=>2)]
998
+ end
999
+
1000
+ it "should respect many_to_many association's :left_primary_key and :right_primary_key options" do
1001
+ 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
1002
+ ds = GraphAlbum.eager_graph(:inner_genres)
1003
+ 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)'
1004
+ def ds.fetch_rows(sql, &block)
1005
+ yield({:id=>3, :band_id=>2, :inner_genres_id=>5, :xxx=>12})
1006
+ yield({:id=>3, :band_id=>2, :inner_genres_id=>6, :xxx=>22})
1007
+ end
1008
+ as = ds.all
1009
+ as.should == [GraphAlbum.load(:id=>3, :band_id=>2)]
1010
+ as.first.inner_genres.should == [GraphGenre.load(:id=>5), GraphGenre.load(:id=>6)]
1011
+ end
1012
+
1013
+ it "should respect the association's :graph_select option" do
1014
+ GraphAlbum.many_to_one :inner_band, :class=>'GraphBand', :key=>:band_id, :graph_select=>:vocalist_id
1015
+ 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)'
1016
+
1017
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :graph_select=>[:album_id]
1018
+ GraphAlbum.eager_graph(:right_tracks).sql.should == 'SELECT albums.id, albums.band_id, right_tracks.album_id FROM albums LEFT OUTER JOIN tracks AS right_tracks ON (right_tracks.album_id = albums.id)'
1019
+
1020
+ GraphAlbum.many_to_many :inner_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :graph_select=>[]
1021
+ GraphAlbum.eager_graph(:inner_genres).sql.should == 'SELECT albums.id, albums.band_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres AS inner_genres ON (inner_genres.id = ag.genre_id)'
1022
+ end
1023
+
1024
+ it "should respect the association's :graph_join_type option" do
1025
+ GraphAlbum.many_to_one :inner_band, :class=>'GraphBand', :key=>:band_id, :graph_join_type=>:inner
1026
+ GraphAlbum.eager_graph(:inner_band).sql.should == 'SELECT albums.id, albums.band_id, inner_band.id AS inner_band_id, inner_band.vocalist_id FROM albums INNER JOIN bands AS inner_band ON (inner_band.id = albums.band_id)'
1027
+
1028
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :graph_join_type=>:right_outer
1029
+ 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 RIGHT OUTER JOIN tracks AS right_tracks ON (right_tracks.album_id = albums.id)'
1030
+
1031
+ GraphAlbum.many_to_many :inner_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :graph_join_type=>:inner
1032
+ GraphAlbum.eager_graph(:inner_genres).sql.should == 'SELECT albums.id, albums.band_id, inner_genres.id AS inner_genres_id FROM albums INNER JOIN ag ON (ag.album_id = albums.id) INNER JOIN genres AS inner_genres ON (inner_genres.id = ag.genre_id)'
1033
+ end
1034
+
1035
+ it "should respect the association's :graph_join_type option" do
1036
+ GraphAlbum.many_to_many :inner_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :graph_join_table_join_type=>:inner
1037
+ GraphAlbum.eager_graph(:inner_genres).sql.should == 'SELECT albums.id, albums.band_id, inner_genres.id AS inner_genres_id FROM albums INNER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres AS inner_genres ON (inner_genres.id = ag.genre_id)'
1038
+
1039
+ GraphAlbum.many_to_many :inner_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :graph_join_table_join_type=>:inner, :graph_join_type=>:right_outer
1040
+ GraphAlbum.eager_graph(:inner_genres).sql.should == 'SELECT albums.id, albums.band_id, inner_genres.id AS inner_genres_id FROM albums INNER JOIN ag ON (ag.album_id = albums.id) RIGHT OUTER JOIN genres AS inner_genres ON (inner_genres.id = ag.genre_id)'
1041
+ end
1042
+
1043
+ it "should respect the association's :conditions option" do
1044
+ GraphAlbum.many_to_one :active_band, :class=>'GraphBand', :key=>:band_id, :conditions=>{:active=>true}
1045
+ 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.id = albums.band_id) AND (active_band.active IS TRUE))"
1046
+
1047
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :conditions=>{:id=>(0..100)}
1048
+ 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 LEFT OUTER JOIN tracks AS right_tracks ON ((right_tracks.album_id = albums.id) AND ((right_tracks.id >= 0) AND (right_tracks.id <= 100)))'
1049
+
1050
+ GraphAlbum.many_to_many :active_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :conditions=>{true=>:active}
1051
+ 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) LEFT OUTER JOIN genres AS active_genres ON ((active_genres.id = ag.genre_id) AND ('t' = ag.active))"
1052
+ end
1053
+
1054
+ it "should respect the association's :graph_conditions option" do
1055
+ GraphAlbum.many_to_one :active_band, :class=>'GraphBand', :key=>:band_id, :graph_conditions=>{:active=>true}
1056
+ 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.id = albums.band_id) AND (active_band.active IS TRUE))"
1057
+
1058
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :graph_conditions=>{:id=>(0..100)}
1059
+ 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 LEFT OUTER JOIN tracks AS right_tracks ON ((right_tracks.album_id = albums.id) AND ((right_tracks.id >= 0) AND (right_tracks.id <= 100)))'
1060
+
1061
+ GraphAlbum.many_to_many :active_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :graph_conditions=>{true=>:active}
1062
+ 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) LEFT OUTER JOIN genres AS active_genres ON ((active_genres.id = ag.genre_id) AND ('t' = ag.active))"
1063
+ end
1064
+
1065
+ it "should respect the association's :graph_join_table_conditions option" do
1066
+ GraphAlbum.many_to_many :active_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :graph_join_table_conditions=>{:active=>true}
1067
+ 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 (ag.active IS TRUE)) LEFT OUTER JOIN genres AS active_genres ON (active_genres.id = ag.genre_id)"
1068
+
1069
+ GraphAlbum.many_to_many :active_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :graph_conditions=>{true=>:active}, :graph_join_table_conditions=>{true=>:active}
1070
+ 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))"
1071
+ end
1072
+
1073
+ it "should respect the association's :graph_block option" do
1074
+ GraphAlbum.many_to_one :active_band, :class=>'GraphBand', :key=>:band_id, :graph_block=>proc{|ja,lja,js| {:active.qualify(ja)=>true}}
1075
+ 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.id = albums.band_id) AND (active_band.active IS TRUE))"
1076
+
1077
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :graph_block=>proc{|ja,lja,js| {:id.qualify(ja)=>(0..100)}}
1078
+ 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 LEFT OUTER JOIN tracks AS right_tracks ON ((right_tracks.album_id = albums.id) AND ((right_tracks.id >= 0) AND (right_tracks.id <= 100)))'
1079
+
1080
+ GraphAlbum.many_to_many :active_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :graph_block=>proc{|ja,lja,js| {true=>:active.qualify(lja)}}
1081
+ 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) LEFT OUTER JOIN genres AS active_genres ON ((active_genres.id = ag.genre_id) AND ('t' = ag.active))"
1082
+ end
1083
+
1084
+ it "should respect the association's :graph_join_block option" do
1085
+ GraphAlbum.many_to_many :active_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :graph_join_table_block=>proc{|ja,lja,js| {:active.qualify(ja)=>true}}
1086
+ 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 (ag.active IS TRUE)) LEFT OUTER JOIN genres AS active_genres ON (active_genres.id = ag.genre_id)"
1087
+
1088
+ GraphAlbum.many_to_many :active_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :graph_block=>proc{|ja,lja,js| {true=>:active.qualify(lja)}}, :graph_join_table_block=>proc{|ja,lja,js| {true=>:active.qualify(lja)}}
1089
+ 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))"
1090
+ end
1091
+
1092
+ it "should respect the association's :eager_grapher option" do
1093
+ 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)}
1094
+ 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 IS TRUE)"
1095
+
1096
+ 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)}
1097
+ 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'
1098
+
1099
+ 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)}
1100
+ 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)"
1101
+ end
1102
+
1103
+ it "should respect the association's :graph_only_conditions option" do
1104
+ GraphAlbum.many_to_one :active_band, :class=>'GraphBand', :key=>:band_id, :graph_only_conditions=>{:active=>true}
1105
+ 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 IS TRUE)"
1106
+
1107
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :graph_only_conditions=>nil, :graph_join_type=>:natural
1108
+ 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'
1109
+
1110
+ GraphAlbum.many_to_many :active_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :graph_only_conditions=>[:album_id]
1111
+ 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) LEFT OUTER JOIN genres AS active_genres USING (album_id)"
1112
+ end
1113
+
1114
+ it "should respect the association's :graph_join_table_only_conditions option" do
1115
+ GraphAlbum.many_to_many :active_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :graph_join_table_only_conditions=>{:active=>true}
1116
+ 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.active IS TRUE) LEFT OUTER JOIN genres AS active_genres ON (active_genres.id = ag.genre_id)"
1117
+
1118
+ GraphAlbum.many_to_many :active_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :graph_only_conditions=>(:price + 2 > 100), :graph_join_table_only_conditions=>"active"
1119
+ 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 (active) LEFT OUTER JOIN genres AS active_genres ON ((price + 2) > 100)"
1120
+ end
1121
+
1122
+ it "should create unique table aliases for all associations" do
1123
+ GraphAlbum.eager_graph(:previous_album=>{:previous_album=>:previous_album}).sql.should == "SELECT albums.id, albums.band_id, previous_album.id AS previous_album_id, previous_album.band_id AS previous_album_band_id, previous_album_0.id AS previous_album_0_id, previous_album_0.band_id AS previous_album_0_band_id, previous_album_1.id AS previous_album_1_id, previous_album_1.band_id AS previous_album_1_band_id FROM albums LEFT OUTER JOIN albums AS previous_album ON (previous_album.id = albums.previous_album_id) LEFT OUTER JOIN albums AS previous_album_0 ON (previous_album_0.id = previous_album.previous_album_id) LEFT OUTER JOIN albums AS previous_album_1 ON (previous_album_1.id = previous_album_0.previous_album_id)"
1124
+ end
1125
+
1126
+ it "should respect the association's :order" do
1127
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:id, :album_id]
1128
+ 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 LEFT OUTER JOIN tracks AS right_tracks ON (right_tracks.album_id = albums.id) ORDER BY right_tracks.id, right_tracks.album_id'
1129
+ end
1130
+
1131
+ it "should only qualify unqualified symbols, identifiers, or ordered versions in association's :order" do
1132
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:blah__id.identifier, :blah__id.identifier.desc, :blah__id.desc, :blah__id, :album_id, :album_id.desc, 1, 'RANDOM()'.lit, :a.qualify(:b)]
1133
+ 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 LEFT OUTER JOIN tracks AS right_tracks ON (right_tracks.album_id = albums.id) ORDER BY right_tracks.blah__id, right_tracks.blah__id DESC, blah.id DESC, blah.id, right_tracks.album_id, right_tracks.album_id DESC, 1, RANDOM(), b.a'
1134
+ end
1135
+
1136
+ it "should not respect the association's :order if :order_eager_graph is false" do
1137
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:id, :album_id], :order_eager_graph=>false
1138
+ 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 LEFT OUTER JOIN tracks AS right_tracks ON (right_tracks.album_id = albums.id)'
1139
+ end
1140
+
1141
+ it "should add the association's :order to the existing order" do
1142
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:id, :album_id]
1143
+ 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'
1144
+ end
1145
+
1146
+ it "should add the association's :order for cascading associations" do
1147
+ GraphBand.one_to_many :a_albums, :class=>'GraphAlbum', :key=>:band_id, :order=>:name
1148
+ GraphAlbum.one_to_many :b_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:id, :album_id]
1149
+ GraphBand.eager_graph(:a_albums=>:b_tracks).sql.should == 'SELECT bands.id, bands.vocalist_id, a_albums.id AS a_albums_id, a_albums.band_id, b_tracks.id AS b_tracks_id, b_tracks.album_id FROM bands LEFT OUTER JOIN albums AS a_albums ON (a_albums.band_id = bands.id) LEFT OUTER JOIN tracks AS b_tracks ON (b_tracks.album_id = a_albums.id) ORDER BY a_albums.name, b_tracks.id, b_tracks.album_id'
1150
+ GraphAlbum.one_to_many :albums, :class=>'GraphAlbum', :key=>:band_id, :order=>[:band_id, :id]
1151
+ GraphAlbum.eager_graph(:albums=>{:albums=>:albums}).sql.should == 'SELECT albums.id, albums.band_id, albums_0.id AS albums_0_id, albums_0.band_id AS albums_0_band_id, albums_1.id AS albums_1_id, albums_1.band_id AS albums_1_band_id, albums_2.id AS albums_2_id, albums_2.band_id AS albums_2_band_id FROM albums LEFT OUTER JOIN albums AS albums_0 ON (albums_0.band_id = albums.id) LEFT OUTER JOIN albums AS albums_1 ON (albums_1.band_id = albums_0.id) LEFT OUTER JOIN albums AS albums_2 ON (albums_2.band_id = albums_1.id) ORDER BY albums_0.band_id, albums_0.id, albums_1.band_id, albums_1.id, albums_2.band_id, albums_2.id'
1152
+ end
1153
+
1154
+ it "should add the associations :order for multiple associations" do
1155
+ GraphAlbum.many_to_many :a_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :order=>:id
1156
+ GraphAlbum.one_to_many :b_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:id, :album_id]
1157
+ 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'
1158
+ end
1159
+
1160
+ it "should use the correct qualifier when graphing multiple tables with extra conditions" do
1161
+ GraphAlbum.many_to_many :a_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag
1162
+ GraphAlbum.one_to_many :b_tracks, :class=>'GraphTrack', :key=>:album_id, :graph_conditions=>{:a=>:b}
1163
+ 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))'
1164
+ end
1165
+ end