sequel 3.21.0 → 3.36.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 (311) hide show
  1. data/CHANGELOG +855 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +23 -7
  4. data/Rakefile +42 -24
  5. data/bin/sequel +26 -8
  6. data/doc/active_record.rdoc +1 -0
  7. data/doc/advanced_associations.rdoc +171 -11
  8. data/doc/association_basics.rdoc +293 -43
  9. data/doc/dataset_basics.rdoc +5 -5
  10. data/doc/dataset_filtering.rdoc +8 -0
  11. data/doc/mass_assignment.rdoc +56 -0
  12. data/doc/migration.rdoc +85 -552
  13. data/doc/model_hooks.rdoc +78 -27
  14. data/doc/object_model.rdoc +541 -0
  15. data/doc/opening_databases.rdoc +97 -50
  16. data/doc/prepared_statements.rdoc +40 -14
  17. data/doc/querying.rdoc +28 -4
  18. data/doc/reflection.rdoc +30 -4
  19. data/doc/release_notes/3.22.0.txt +39 -0
  20. data/doc/release_notes/3.23.0.txt +172 -0
  21. data/doc/release_notes/3.24.0.txt +420 -0
  22. data/doc/release_notes/3.25.0.txt +88 -0
  23. data/doc/release_notes/3.26.0.txt +88 -0
  24. data/doc/release_notes/3.27.0.txt +82 -0
  25. data/doc/release_notes/3.28.0.txt +304 -0
  26. data/doc/release_notes/3.29.0.txt +459 -0
  27. data/doc/release_notes/3.30.0.txt +135 -0
  28. data/doc/release_notes/3.31.0.txt +146 -0
  29. data/doc/release_notes/3.32.0.txt +202 -0
  30. data/doc/release_notes/3.33.0.txt +157 -0
  31. data/doc/release_notes/3.34.0.txt +671 -0
  32. data/doc/release_notes/3.35.0.txt +144 -0
  33. data/doc/release_notes/3.36.0.txt +245 -0
  34. data/doc/schema_modification.rdoc +572 -0
  35. data/doc/sharding.rdoc +95 -3
  36. data/doc/testing.rdoc +129 -0
  37. data/doc/thread_safety.rdoc +17 -0
  38. data/doc/transactions.rdoc +137 -0
  39. data/doc/virtual_rows.rdoc +51 -2
  40. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  41. data/lib/sequel/adapters/ado.rb +40 -18
  42. data/lib/sequel/adapters/amalgalite.rb +17 -9
  43. data/lib/sequel/adapters/db2.rb +175 -86
  44. data/lib/sequel/adapters/dbi.rb +15 -15
  45. data/lib/sequel/adapters/do/mysql.rb +17 -11
  46. data/lib/sequel/adapters/do/postgres.rb +1 -57
  47. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  48. data/lib/sequel/adapters/do.rb +18 -36
  49. data/lib/sequel/adapters/firebird.rb +27 -208
  50. data/lib/sequel/adapters/ibmdb.rb +453 -0
  51. data/lib/sequel/adapters/informix.rb +6 -23
  52. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  53. data/lib/sequel/adapters/jdbc/db2.rb +47 -0
  54. data/lib/sequel/adapters/jdbc/derby.rb +287 -0
  55. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  56. data/lib/sequel/adapters/jdbc/h2.rb +73 -31
  57. data/lib/sequel/adapters/jdbc/hsqldb.rb +184 -0
  58. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  59. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  61. data/lib/sequel/adapters/jdbc/mysql.rb +7 -24
  62. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +58 -48
  64. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  65. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  66. data/lib/sequel/adapters/jdbc/sqlserver.rb +66 -0
  67. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  68. data/lib/sequel/adapters/jdbc.rb +265 -94
  69. data/lib/sequel/adapters/mock.rb +361 -0
  70. data/lib/sequel/adapters/mysql.rb +118 -222
  71. data/lib/sequel/adapters/mysql2.rb +58 -57
  72. data/lib/sequel/adapters/odbc/mssql.rb +15 -7
  73. data/lib/sequel/adapters/odbc.rb +28 -9
  74. data/lib/sequel/adapters/openbase.rb +3 -5
  75. data/lib/sequel/adapters/oracle.rb +349 -53
  76. data/lib/sequel/adapters/postgres.rb +356 -107
  77. data/lib/sequel/adapters/shared/access.rb +32 -9
  78. data/lib/sequel/adapters/shared/db2.rb +342 -0
  79. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  80. data/lib/sequel/adapters/shared/informix.rb +53 -0
  81. data/lib/sequel/adapters/shared/mssql.rb +335 -118
  82. data/lib/sequel/adapters/shared/mysql.rb +340 -76
  83. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +155 -0
  84. data/lib/sequel/adapters/shared/oracle.rb +243 -45
  85. data/lib/sequel/adapters/shared/postgres.rb +524 -279
  86. data/lib/sequel/adapters/shared/progress.rb +5 -8
  87. data/lib/sequel/adapters/shared/sqlite.rb +265 -89
  88. data/lib/sequel/adapters/sqlite.rb +101 -39
  89. data/lib/sequel/adapters/swift/mysql.rb +9 -12
  90. data/lib/sequel/adapters/swift/postgres.rb +9 -74
  91. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  92. data/lib/sequel/adapters/swift.rb +8 -7
  93. data/lib/sequel/adapters/tinytds.rb +161 -21
  94. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  95. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  96. data/lib/sequel/ast_transformer.rb +194 -0
  97. data/lib/sequel/connection_pool/sharded_single.rb +11 -1
  98. data/lib/sequel/connection_pool/sharded_threaded.rb +28 -2
  99. data/lib/sequel/connection_pool/single.rb +5 -0
  100. data/lib/sequel/connection_pool/threaded.rb +38 -14
  101. data/lib/sequel/connection_pool.rb +10 -2
  102. data/lib/sequel/core.rb +118 -11
  103. data/lib/sequel/database/connecting.rb +38 -28
  104. data/lib/sequel/database/dataset.rb +4 -4
  105. data/lib/sequel/database/dataset_defaults.rb +63 -2
  106. data/lib/sequel/database/logging.rb +7 -2
  107. data/lib/sequel/database/misc.rb +150 -46
  108. data/lib/sequel/database/query.rb +215 -65
  109. data/lib/sequel/database/schema_generator.rb +64 -20
  110. data/lib/sequel/database/schema_methods.rb +212 -54
  111. data/lib/sequel/dataset/actions.rb +322 -85
  112. data/lib/sequel/dataset/features.rb +96 -8
  113. data/lib/sequel/dataset/graph.rb +38 -11
  114. data/lib/sequel/dataset/misc.rb +50 -23
  115. data/lib/sequel/dataset/mutation.rb +12 -7
  116. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  117. data/lib/sequel/dataset/query.rb +237 -44
  118. data/lib/sequel/dataset/sql.rb +698 -278
  119. data/lib/sequel/dataset.rb +8 -0
  120. data/lib/sequel/exceptions.rb +4 -0
  121. data/lib/sequel/extensions/_pretty_table.rb +79 -0
  122. data/lib/sequel/extensions/arbitrary_servers.rb +108 -0
  123. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  124. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +16 -34
  125. data/lib/sequel/extensions/migration.rb +97 -11
  126. data/lib/sequel/extensions/named_timezones.rb +5 -0
  127. data/lib/sequel/extensions/null_dataset.rb +90 -0
  128. data/lib/sequel/extensions/pg_array.rb +460 -0
  129. data/lib/sequel/extensions/pg_array_ops.rb +220 -0
  130. data/lib/sequel/extensions/pg_auto_parameterize.rb +169 -0
  131. data/lib/sequel/extensions/pg_hstore.rb +296 -0
  132. data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
  133. data/lib/sequel/extensions/pg_inet.rb +89 -0
  134. data/lib/sequel/extensions/pg_json.rb +178 -0
  135. data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
  136. data/lib/sequel/extensions/pretty_table.rb +5 -71
  137. data/lib/sequel/extensions/query.rb +1 -1
  138. data/lib/sequel/extensions/query_literals.rb +79 -0
  139. data/lib/sequel/extensions/schema_caching.rb +76 -0
  140. data/lib/sequel/extensions/schema_dumper.rb +251 -37
  141. data/lib/sequel/extensions/select_remove.rb +35 -0
  142. data/lib/sequel/extensions/server_block.rb +139 -0
  143. data/lib/sequel/extensions/sql_expr.rb +4 -110
  144. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  145. data/lib/sequel/extensions/to_dot.rb +95 -83
  146. data/lib/sequel/model/associations.rb +1187 -393
  147. data/lib/sequel/model/base.rb +578 -170
  148. data/lib/sequel/model/errors.rb +1 -1
  149. data/lib/sequel/model/exceptions.rb +5 -1
  150. data/lib/sequel/model/inflections.rb +1 -1
  151. data/lib/sequel/model.rb +30 -11
  152. data/lib/sequel/no_core_ext.rb +2 -0
  153. data/lib/sequel/plugins/active_model.rb +13 -1
  154. data/lib/sequel/plugins/association_pks.rb +22 -4
  155. data/lib/sequel/plugins/caching.rb +25 -18
  156. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  157. data/lib/sequel/plugins/composition.rb +44 -12
  158. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  159. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  160. data/lib/sequel/plugins/dirty.rb +214 -0
  161. data/lib/sequel/plugins/eager_each.rb +59 -0
  162. data/lib/sequel/plugins/force_encoding.rb +6 -6
  163. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  164. data/lib/sequel/plugins/identity_map.rb +123 -8
  165. data/lib/sequel/plugins/instance_filters.rb +10 -0
  166. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  167. data/lib/sequel/plugins/json_serializer.rb +58 -6
  168. data/lib/sequel/plugins/list.rb +12 -2
  169. data/lib/sequel/plugins/many_through_many.rb +79 -28
  170. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  171. data/lib/sequel/plugins/nested_attributes.rb +139 -63
  172. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  173. data/lib/sequel/plugins/prepared_statements.rb +167 -0
  174. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  175. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  176. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  177. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  178. data/lib/sequel/plugins/schema.rb +7 -2
  179. data/lib/sequel/plugins/serialization.rb +98 -49
  180. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  181. data/lib/sequel/plugins/sharding.rb +21 -54
  182. data/lib/sequel/plugins/single_table_inheritance.rb +5 -3
  183. data/lib/sequel/plugins/static_cache.rb +99 -0
  184. data/lib/sequel/plugins/subclasses.rb +10 -2
  185. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  186. data/lib/sequel/plugins/timestamps.rb +1 -1
  187. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  188. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  189. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  190. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  191. data/lib/sequel/sql.rb +526 -59
  192. data/lib/sequel/timezones.rb +57 -38
  193. data/lib/sequel/version.rb +1 -1
  194. data/spec/adapters/db2_spec.rb +146 -0
  195. data/spec/adapters/firebird_spec.rb +1 -1
  196. data/spec/adapters/mssql_spec.rb +142 -40
  197. data/spec/adapters/mysql_spec.rb +380 -234
  198. data/spec/adapters/oracle_spec.rb +67 -77
  199. data/spec/adapters/postgres_spec.rb +1050 -212
  200. data/spec/adapters/spec_helper.rb +8 -5
  201. data/spec/adapters/sqlite_spec.rb +139 -27
  202. data/spec/core/connection_pool_spec.rb +196 -93
  203. data/spec/core/database_spec.rb +879 -510
  204. data/spec/core/dataset_spec.rb +1415 -1086
  205. data/spec/core/expression_filters_spec.rb +554 -109
  206. data/spec/core/mock_adapter_spec.rb +449 -0
  207. data/spec/core/object_graph_spec.rb +86 -114
  208. data/spec/core/schema_generator_spec.rb +4 -4
  209. data/spec/core/schema_spec.rb +482 -38
  210. data/spec/core/spec_helper.rb +6 -48
  211. data/spec/extensions/active_model_spec.rb +13 -0
  212. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  213. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  214. data/spec/extensions/association_dependencies_spec.rb +15 -25
  215. data/spec/extensions/association_pks_spec.rb +66 -32
  216. data/spec/extensions/association_proxies_spec.rb +4 -4
  217. data/spec/extensions/caching_spec.rb +47 -51
  218. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  219. data/spec/extensions/columns_introspection_spec.rb +91 -0
  220. data/spec/extensions/composition_spec.rb +18 -13
  221. data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +119 -139
  222. data/spec/extensions/dataset_associations_spec.rb +199 -0
  223. data/spec/extensions/defaults_setter_spec.rb +64 -0
  224. data/spec/extensions/dirty_spec.rb +155 -0
  225. data/spec/extensions/eager_each_spec.rb +34 -0
  226. data/spec/extensions/force_encoding_spec.rb +4 -2
  227. data/spec/extensions/hook_class_methods_spec.rb +69 -92
  228. data/spec/extensions/identity_map_spec.rb +131 -22
  229. data/spec/extensions/inflector_spec.rb +0 -4
  230. data/spec/extensions/instance_filters_spec.rb +11 -21
  231. data/spec/extensions/instance_hooks_spec.rb +72 -0
  232. data/spec/extensions/json_serializer_spec.rb +49 -6
  233. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  234. data/spec/extensions/list_spec.rb +48 -39
  235. data/spec/extensions/many_through_many_spec.rb +283 -250
  236. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  237. data/spec/extensions/migration_spec.rb +140 -35
  238. data/spec/extensions/named_timezones_spec.rb +29 -11
  239. data/spec/extensions/nested_attributes_spec.rb +251 -89
  240. data/spec/extensions/null_dataset_spec.rb +85 -0
  241. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  242. data/spec/extensions/pg_array_ops_spec.rb +105 -0
  243. data/spec/extensions/pg_array_spec.rb +196 -0
  244. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  245. data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
  246. data/spec/extensions/pg_hstore_spec.rb +195 -0
  247. data/spec/extensions/pg_inet_spec.rb +44 -0
  248. data/spec/extensions/pg_json_spec.rb +101 -0
  249. data/spec/extensions/pg_statement_cache_spec.rb +209 -0
  250. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  251. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  252. data/spec/extensions/prepared_statements_spec.rb +87 -0
  253. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  254. data/spec/extensions/pretty_table_spec.rb +7 -6
  255. data/spec/extensions/query_literals_spec.rb +168 -0
  256. data/spec/extensions/query_spec.rb +6 -0
  257. data/spec/extensions/rcte_tree_spec.rb +50 -43
  258. data/spec/extensions/schema_caching_spec.rb +41 -0
  259. data/spec/extensions/schema_dumper_spec.rb +429 -22
  260. data/spec/extensions/schema_spec.rb +25 -21
  261. data/spec/extensions/select_remove_spec.rb +38 -0
  262. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  263. data/spec/extensions/serialization_spec.rb +85 -43
  264. data/spec/extensions/server_block_spec.rb +90 -0
  265. data/spec/extensions/sharding_spec.rb +69 -143
  266. data/spec/extensions/single_table_inheritance_spec.rb +23 -29
  267. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  268. data/spec/extensions/spec_helper.rb +22 -61
  269. data/spec/extensions/sql_expr_spec.rb +29 -60
  270. data/spec/extensions/static_cache_spec.rb +145 -0
  271. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  272. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  273. data/spec/extensions/timestamps_spec.rb +6 -6
  274. data/spec/extensions/to_dot_spec.rb +4 -7
  275. data/spec/extensions/touch_spec.rb +13 -14
  276. data/spec/extensions/tree_spec.rb +11 -26
  277. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  278. data/spec/extensions/update_primary_key_spec.rb +30 -24
  279. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  280. data/spec/extensions/validation_helpers_spec.rb +16 -35
  281. data/spec/extensions/xml_serializer_spec.rb +29 -4
  282. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  283. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  284. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  285. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  286. data/spec/integration/associations_test.rb +659 -21
  287. data/spec/integration/database_test.rb +9 -3
  288. data/spec/integration/dataset_test.rb +605 -91
  289. data/spec/integration/eager_loader_test.rb +19 -21
  290. data/spec/integration/migrator_test.rb +2 -3
  291. data/spec/integration/model_test.rb +81 -2
  292. data/spec/integration/plugin_test.rb +454 -143
  293. data/spec/integration/prepared_statement_test.rb +263 -128
  294. data/spec/integration/schema_test.rb +309 -43
  295. data/spec/integration/spec_helper.rb +40 -30
  296. data/spec/integration/timezone_test.rb +38 -12
  297. data/spec/integration/transaction_test.rb +183 -8
  298. data/spec/integration/type_test.rb +18 -4
  299. data/spec/model/association_reflection_spec.rb +201 -7
  300. data/spec/model/associations_spec.rb +1067 -661
  301. data/spec/model/base_spec.rb +329 -95
  302. data/spec/model/dataset_methods_spec.rb +7 -27
  303. data/spec/model/eager_loading_spec.rb +677 -676
  304. data/spec/model/hooks_spec.rb +349 -67
  305. data/spec/model/model_spec.rb +240 -168
  306. data/spec/model/plugins_spec.rb +24 -13
  307. data/spec/model/record_spec.rb +490 -232
  308. data/spec/model/spec_helper.rb +14 -71
  309. data/spec/model/validations_spec.rb +11 -0
  310. data/spec/rcov.opts +2 -0
  311. metadata +178 -80
@@ -1,9 +1,7 @@
1
1
  require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
2
 
3
3
  describe Sequel::Model, "#eager" do
4
- before(:each) do
5
- MODEL_DB.reset
6
-
4
+ before do
7
5
  class ::EagerAlbum < Sequel::Model(:albums)
8
6
  columns :id, :band_id
9
7
  many_to_one :band, :class=>'EagerBand', :key=>:band_id
@@ -48,63 +46,48 @@ describe Sequel::Model, "#eager" do
48
46
  many_to_many :bands, :class=>'EagerBand', :left_key=>:member_id, :right_key=>:band_id, :join_table=>:bm, :order =>:id
49
47
  end
50
48
 
51
- EagerAlbum.dataset.extend(Module.new {
52
- def columns
53
- [:id, :band_id]
54
- end
55
-
56
- def fetch_rows(sql)
57
- h = if sql =~ /101/
58
- {:id => 101, :band_id=> 101}
59
- else
60
- {:id => 1, :band_id=> 2}
61
- end
62
- h.merge!(:x_foreign_key_x=>4) if sql =~ /ag\.genre_id/
63
- @db << sql
64
- yield h
49
+ EagerAlbum.dataset.columns(:id, :band_id)
50
+ EagerAlbum.dataset._fetch = proc do |sql|
51
+ h = if sql =~ /101/
52
+ {:id => 101, :band_id=> 101}
53
+ else
54
+ {:id => 1, :band_id=> 2}
65
55
  end
66
- })
56
+ h[:x_foreign_key_x] = 4 if sql =~ /ag\.genre_id/
57
+ h
58
+ end
67
59
 
68
- EagerBand.dataset.extend(Module.new {
69
- def fetch_rows(sql)
60
+ EagerBand.dataset._fetch = proc do |sql|
61
+ case sql
62
+ when /id IN (101)/
63
+ # nothing
64
+ when /id > 100/
65
+ [{:id => 101}, {:id => 102}]
66
+ else
70
67
  h = {:id => 2}
71
- h.merge!(:x_foreign_key_x=>5) if sql =~ /bm\.member_id/
72
- @db << sql
73
- case sql
74
- when /id IN (101)/
75
- when /id > 100/
76
- yield({:id => 101})
77
- yield({:id => 102})
78
- else
79
- yield h
80
- end
68
+ h[:x_foreign_key_x] = 5 if sql =~ /bm\.member_id/
69
+ h
81
70
  end
82
- })
71
+ end
83
72
 
84
- EagerTrack.dataset.extend(Module.new {
85
- def fetch_rows(sql)
86
- @db << sql
87
- yield({:id => 3, :album_id => 1})
88
- end
89
- })
73
+ EagerTrack.dataset._fetch = {:id => 3, :album_id => 1}
90
74
 
91
- EagerGenre.dataset.extend(Module.new {
92
- def fetch_rows(sql)
93
- h = {:id => 4}
94
- h.merge!(:x_foreign_key_x=>1) if sql =~ /ag\.album_id/
95
- @db << sql
96
- yield h
97
- end
98
- })
75
+ EagerGenre.dataset._fetch = proc do |sql|
76
+ h = {:id => 4}
77
+ h[:x_foreign_key_x] = 1 if sql =~ /ag\.album_id/
78
+ h
79
+ end
99
80
 
100
- EagerBandMember.dataset.extend(Module.new {
101
- def fetch_rows(sql)
102
- h = {:id => 5}
103
- h.merge!(:x_foreign_key_x=>2) if sql =~ /bm\.band_id/
104
- @db << sql
105
- yield h
106
- end
107
- })
81
+ EagerBandMember.dataset._fetch = proc do |sql|
82
+ h = {:id => 5}
83
+ h[:x_foreign_key_x] = 2 if sql =~ /bm\.band_id/
84
+ h
85
+ end
86
+
87
+ MODEL_DB.reset
88
+ end
89
+ after do
90
+ [:EagerAlbum, :EagerBand, :EagerTrack, :EagerGenre, :EagerBandMember].each{|x| Object.send(:remove_const, x)}
108
91
  end
109
92
 
110
93
  it "should raise an error if called without a symbol or hash" do
@@ -113,15 +96,20 @@ describe Sequel::Model, "#eager" do
113
96
 
114
97
  it "should eagerly load a single many_to_one association" do
115
98
  a = EagerAlbum.eager(:band).all
116
- a.should be_a_kind_of(Array)
117
- a.size.should == 1
118
- a.first.should be_a_kind_of(EagerAlbum)
119
- a.first.values.should == {:id => 1, :band_id => 2}
120
99
  MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM bands WHERE (bands.id IN (2))']
121
- a = a.first
122
- a.band.should be_a_kind_of(EagerBand)
123
- a.band.values.should == {:id => 2}
124
- MODEL_DB.sqls.length.should == 2
100
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
101
+ a.first.band.should == EagerBand.load(:id=>2)
102
+ MODEL_DB.sqls.should == []
103
+ end
104
+
105
+ it "should eagerly load a single many_to_one association with the same name as the column" do
106
+ EagerAlbum.def_column_alias(:band_id_id, :band_id)
107
+ EagerAlbum.many_to_one :band_id, :key_column=>:band_id, :class=>EagerBand
108
+ a = EagerAlbum.eager(:band_id).all
109
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM bands WHERE (bands.id IN (2))']
110
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
111
+ a.first.band_id.should == EagerBand.load(:id=>2)
112
+ MODEL_DB.sqls.should == []
125
113
  end
126
114
 
127
115
  it "should eagerly load a single one_to_one association" do
@@ -130,269 +118,203 @@ describe Sequel::Model, "#eager" do
130
118
  a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
131
119
  MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE (tracks.album_id IN (1))']
132
120
  a.first.track.should == EagerTrack.load(:id => 3, :album_id=>1)
133
- MODEL_DB.sqls.length.should == 2
121
+ MODEL_DB.sqls.should == []
122
+ end
123
+
124
+ it "should eagerly load a single one_to_one association using the :distinct_on strategy" do
125
+ EagerTrack.dataset.meta_def(:supports_distinct_on?){true}
126
+ EagerAlbum.one_to_one :track, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>true
127
+ a = EagerAlbum.eager(:track).all
128
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
129
+ MODEL_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']
130
+ a.first.track.should == EagerTrack.load(:id => 3, :album_id=>1)
131
+ MODEL_DB.sqls.should == []
132
+ end
133
+
134
+ it "should eagerly load a single one_to_one association using the :window_function strategy" do
135
+ EagerTrack.dataset.meta_def(:supports_window_functions?){true}
136
+ EagerAlbum.one_to_one :track, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>true, :order=>:name
137
+ a = EagerAlbum.eager(:track).all
138
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
139
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM (SELECT *, row_number() OVER (PARTITION BY tracks.album_id ORDER BY name) AS x_sequel_row_number_x FROM tracks WHERE (tracks.album_id IN (1))) AS t1 WHERE (x_sequel_row_number_x = 1)']
140
+ a.first.track.should == EagerTrack.load(:id => 3, :album_id=>1)
141
+ MODEL_DB.sqls.should == []
142
+ end
143
+
144
+ it "should eagerly load a single one_to_one association using the :correlated_subquery strategy" do
145
+ EagerAlbum.one_to_one :track, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>:correlated_subquery, :order=>:name
146
+ a = EagerAlbum.eager(:track).all
147
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
148
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE ((tracks.album_id IN (1)) AND (tracks.id IN (SELECT t1.id FROM tracks AS t1 WHERE (t1.album_id = tracks.album_id) ORDER BY name LIMIT 1))) ORDER BY name']
149
+ a.first.track.should == EagerTrack.load(:id => 3, :album_id=>1)
150
+ MODEL_DB.sqls.should == []
151
+ end
152
+
153
+ it "should handle qualified order clauses when eagerly loading a single one_to_one association using the :correlated_subquery strategy" do
154
+ EagerAlbum.one_to_one :track, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>:correlated_subquery, :order=>[:tracks__name, :tracks__name.desc, :name.qualify(:tracks), :name.qualify(:t), 1]
155
+ a = EagerAlbum.eager(:track).all
156
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
157
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE ((tracks.album_id IN (1)) AND (tracks.id IN (SELECT t1.id FROM tracks AS t1 WHERE (t1.album_id = tracks.album_id) ORDER BY t1.name, t1.name DESC, t1.name, t.name, 1 LIMIT 1))) ORDER BY tracks.name, tracks.name DESC, tracks.name, t.name, 1']
158
+ a.first.track.should == EagerTrack.load(:id => 3, :album_id=>1)
159
+ MODEL_DB.sqls.should == []
160
+ end
161
+
162
+ it "should handle qualified composite keys when eagerly loading a single one_to_one association using the :correlated_subquery strategy" do
163
+ c1 = Class.new(EagerAlbum)
164
+ c2 = Class.new(EagerTrack)
165
+ c1.set_primary_key [:id, :band_id]
166
+ c2.set_primary_key [:id, :album_id]
167
+ c1.one_to_one :track, :class=>c2, :key=>[:album_id, :id], :eager_limit_strategy=>:correlated_subquery
168
+ c2.dataset._fetch = {:id => 2, :album_id=>1}
169
+ a = c1.eager(:track).all
170
+ a.should == [c1.load(:id => 1, :band_id => 2)]
171
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE (((tracks.album_id, tracks.id) IN ((1, 2))) AND ((tracks.id, tracks.album_id) IN (SELECT t1.id, t1.album_id FROM tracks AS t1 WHERE ((t1.album_id = tracks.album_id) AND (t1.id = tracks.id)) LIMIT 1)))']
172
+ a.first.track.should == c2.load(:id => 2, :album_id=>1)
173
+ MODEL_DB.sqls.should == []
134
174
  end
135
175
 
136
176
  it "should eagerly load a single one_to_many association" do
137
177
  a = EagerAlbum.eager(:tracks).all
138
- a.should be_a_kind_of(Array)
139
- a.size.should == 1
140
- a.first.should be_a_kind_of(EagerAlbum)
141
- a.first.values.should == {:id => 1, :band_id => 2}
178
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
142
179
  MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE (tracks.album_id IN (1))']
143
- a = a.first
144
- a.tracks.should be_a_kind_of(Array)
145
- a.tracks.size.should == 1
146
- a.tracks.first.should be_a_kind_of(EagerTrack)
147
- a.tracks.first.values.should == {:id => 3, :album_id=>1}
148
- MODEL_DB.sqls.length.should == 2
180
+ a.first.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
181
+ MODEL_DB.sqls.should == []
149
182
  end
150
183
 
151
184
  it "should eagerly load a single many_to_many association" do
152
185
  a = EagerAlbum.eager(:genres).all
153
- a.should be_a_kind_of(Array)
154
- a.size.should == 1
155
- a.first.should be_a_kind_of(EagerAlbum)
156
- a.first.values.should == {:id => 1, :band_id => 2}
186
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
157
187
  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)))"]
158
- a = a.first
159
- a.genres.should be_a_kind_of(Array)
160
- a.genres.size.should == 1
161
- a.genres.first.should be_a_kind_of(EagerGenre)
162
- a.genres.first.values.should == {:id => 4}
163
- MODEL_DB.sqls.length.should == 2
188
+ a.first.genres.should == [EagerGenre.load(:id=>4)]
189
+ MODEL_DB.sqls.should == []
190
+ end
191
+
192
+ it "should correctly handle a :select=>[] option to many_to_many" do
193
+ EagerAlbum.many_to_many :sgenres, :clone=>:genres, :select=>[]
194
+ a = EagerAlbum.eager(:sgenres).all
195
+ MODEL_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)))"]
196
+ end
197
+
198
+ it "should correctly handle an aliased join table in many_to_many" do
199
+ EagerAlbum.many_to_many :sgenres, :clone=>:genres, :join_table=>:ag___ga
200
+ a = EagerAlbum.eager(:sgenres).all
201
+ MODEL_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)))"]
164
202
  end
165
203
 
166
204
  it "should eagerly load multiple associations in a single call" do
167
205
  a = EagerAlbum.eager(:genres, :tracks, :band).all
168
- a.should be_a_kind_of(Array)
169
- a.size.should == 1
170
- a.first.should be_a_kind_of(EagerAlbum)
171
- a.first.values.should == {:id => 1, :band_id => 2}
172
- MODEL_DB.sqls.length.should == 4
173
- MODEL_DB.sqls[0].should == 'SELECT * FROM albums'
174
- MODEL_DB.sqls[1..-1].should(include('SELECT * FROM bands WHERE (bands.id IN (2))'))
175
- MODEL_DB.sqls[1..-1].should(include('SELECT * FROM tracks WHERE (tracks.album_id IN (1))'))
176
- 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)))'))
206
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
207
+ sqls = MODEL_DB.sqls
208
+ sqls.shift.should == 'SELECT * FROM albums'
209
+ sqls.sort.should == ['SELECT * FROM bands WHERE (bands.id IN (2))',
210
+ 'SELECT * FROM tracks WHERE (tracks.album_id IN (1))',
211
+ '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)))']
177
212
  a = a.first
178
- a.band.should be_a_kind_of(EagerBand)
179
- a.band.values.should == {:id => 2}
180
- a.tracks.should be_a_kind_of(Array)
181
- a.tracks.size.should == 1
182
- a.tracks.first.should be_a_kind_of(EagerTrack)
183
- a.tracks.first.values.should == {:id => 3, :album_id=>1}
184
- a.genres.should be_a_kind_of(Array)
185
- a.genres.size.should == 1
186
- a.genres.first.should be_a_kind_of(EagerGenre)
187
- a.genres.first.values.should == {:id => 4}
188
- MODEL_DB.sqls.length.should == 4
213
+ a.band.should == EagerBand.load(:id=>2)
214
+ a.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
215
+ a.genres.should == [EagerGenre.load(:id => 4)]
216
+ MODEL_DB.sqls.should == []
189
217
  end
190
218
 
191
219
  it "should eagerly load multiple associations in separate calls" do
192
220
  a = EagerAlbum.eager(:genres).eager(:tracks).eager(:band).all
193
- a.should be_a_kind_of(Array)
194
- a.size.should == 1
195
- a.first.should be_a_kind_of(EagerAlbum)
196
- a.first.values.should == {:id => 1, :band_id => 2}
197
- MODEL_DB.sqls.length.should == 4
198
- MODEL_DB.sqls[0].should == 'SELECT * FROM albums'
199
- MODEL_DB.sqls[1..-1].should(include('SELECT * FROM bands WHERE (bands.id IN (2))'))
200
- MODEL_DB.sqls[1..-1].should(include('SELECT * FROM tracks WHERE (tracks.album_id IN (1))'))
201
- 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)))'))
221
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
222
+ sqls = MODEL_DB.sqls
223
+ sqls.shift.should == 'SELECT * FROM albums'
224
+ sqls.sort.should == ['SELECT * FROM bands WHERE (bands.id IN (2))',
225
+ 'SELECT * FROM tracks WHERE (tracks.album_id IN (1))',
226
+ '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)))']
202
227
  a = a.first
203
- a.band.should be_a_kind_of(EagerBand)
204
- a.band.values.should == {:id => 2}
205
- a.tracks.should be_a_kind_of(Array)
206
- a.tracks.size.should == 1
207
- a.tracks.first.should be_a_kind_of(EagerTrack)
208
- a.tracks.first.values.should == {:id => 3, :album_id=>1}
209
- a.genres.should be_a_kind_of(Array)
210
- a.genres.size.should == 1
211
- a.genres.first.should be_a_kind_of(EagerGenre)
212
- a.genres.first.values.should == {:id => 4}
213
- MODEL_DB.sqls.length.should == 4
228
+ a.band.should == EagerBand.load(:id=>2)
229
+ a.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
230
+ a.genres.should == [EagerGenre.load(:id => 4)]
231
+ MODEL_DB.sqls.should == []
214
232
  end
215
233
 
216
234
  it "should allow cascading of eager loading for associations of associated models" do
217
235
  a = EagerTrack.eager(:album=>{:band=>:members}).all
218
- a.should be_a_kind_of(Array)
219
- a.size.should == 1
220
- a.first.should be_a_kind_of(EagerTrack)
221
- a.first.values.should == {:id => 3, :album_id => 1}
222
- MODEL_DB.sqls.length.should == 4
236
+ a.should == [EagerTrack.load(:id => 3, :album_id => 1)]
223
237
  MODEL_DB.sqls.should == ['SELECT * FROM tracks',
224
238
  'SELECT * FROM albums WHERE (albums.id IN (1))',
225
239
  'SELECT * FROM bands WHERE (bands.id IN (2))',
226
240
  "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)))"]
227
241
  a = a.first
228
- a.album.should be_a_kind_of(EagerAlbum)
229
- a.album.values.should == {:id => 1, :band_id => 2}
230
- a.album.band.should be_a_kind_of(EagerBand)
231
- a.album.band.values.should == {:id => 2}
232
- a.album.band.members.should be_a_kind_of(Array)
233
- a.album.band.members.size.should == 1
234
- a.album.band.members.first.should be_a_kind_of(EagerBandMember)
235
- a.album.band.members.first.values.should == {:id => 5}
236
- MODEL_DB.sqls.length.should == 4
242
+ a.album.should == EagerAlbum.load(:id => 1, :band_id => 2)
243
+ a.album.band.should == EagerBand.load(:id => 2)
244
+ a.album.band.members.should == [EagerBandMember.load(:id => 5)]
245
+ MODEL_DB.sqls.should == []
237
246
  end
238
247
 
239
248
  it "should cascade eagerly loading when the :eager association option is used" do
240
249
  a = EagerBand.eager(:albums).all
241
- a.should be_a_kind_of(Array)
242
- a.size.should == 1
243
- a.first.should be_a_kind_of(EagerBand)
244
- a.first.values.should == {:id => 2}
250
+ a.should == [EagerBand.load(:id=>2)]
245
251
  MODEL_DB.sqls.should == ['SELECT * FROM bands',
246
- 'SELECT * FROM albums WHERE (albums.band_id IN (2))',
247
- 'SELECT * FROM tracks WHERE (tracks.album_id IN (1))']
248
- a = a.first
249
- a.albums.should be_a_kind_of(Array)
250
- a.albums.size.should == 1
251
- a.albums.first.should be_a_kind_of(EagerAlbum)
252
- a.albums.first.values.should == {:id => 1, :band_id => 2}
253
- a = a.albums.first
254
- a.tracks.should be_a_kind_of(Array)
255
- a.tracks.size.should == 1
256
- a.tracks.first.should be_a_kind_of(EagerTrack)
257
- a.tracks.first.values.should == {:id => 3, :album_id => 1}
258
- MODEL_DB.sqls.length.should == 3
252
+ 'SELECT * FROM albums WHERE (albums.band_id IN (2))',
253
+ 'SELECT * FROM tracks WHERE (tracks.album_id IN (1))']
254
+ a.first.albums.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
255
+ a.first.albums.first.tracks.should == [EagerTrack.load(:id => 3, :album_id => 1)]
256
+ MODEL_DB.sqls.should == []
259
257
  end
260
258
 
261
259
  it "should respect :eager when lazily loading an association" do
262
260
  a = EagerBand.all
263
- a.should be_a_kind_of(Array)
264
- a.size.should == 1
265
- a.first.should be_a_kind_of(EagerBand)
266
- a.first.values.should == {:id => 2}
261
+ a.should == [EagerBand.load(:id=>2)]
267
262
  MODEL_DB.sqls.should == ['SELECT * FROM bands']
268
- a = a.first
269
- a.albums
270
- MODEL_DB.sqls.should == ['SELECT * FROM bands',
271
- 'SELECT * FROM albums WHERE (albums.band_id = 2)',
272
- 'SELECT * FROM tracks WHERE (tracks.album_id IN (1))']
273
- a.albums.should be_a_kind_of(Array)
274
- a.albums.size.should == 1
275
- a.albums.first.should be_a_kind_of(EagerAlbum)
276
- a.albums.first.values.should == {:id => 1, :band_id => 2}
277
- a = a.albums.first
278
- a.tracks.should be_a_kind_of(Array)
279
- a.tracks.size.should == 1
280
- a.tracks.first.should be_a_kind_of(EagerTrack)
281
- a.tracks.first.values.should == {:id => 3, :album_id => 1}
282
- MODEL_DB.sqls.length.should == 3
263
+ a = a.first.albums
264
+ MODEL_DB.sqls.should == ['SELECT * FROM albums WHERE (albums.band_id = 2)',
265
+ 'SELECT * FROM tracks WHERE (tracks.album_id IN (1))']
266
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
267
+ a.first.tracks.should == [EagerTrack.load(:id => 3, :album_id => 1)]
268
+ MODEL_DB.sqls.should == []
283
269
  end
284
270
 
285
271
  it "should cascade eagerly loading when the :eager_graph association option is used" do
286
- EagerAlbum.dataset.extend(Module.new {
287
- def fetch_rows(sql)
288
- @db << sql
289
- yield({:id=>1, :band_id=>2, :tracks_id=>3, :album_id=>1})
290
- end
291
- })
272
+ EagerAlbum.dataset._fetch = {:id=>1, :band_id=>2, :tracks_id=>3, :album_id=>1}
292
273
  a = EagerBand.eager(:graph_albums).all
293
- a.should be_a_kind_of(Array)
294
- a.size.should == 1
295
- a.first.should be_a_kind_of(EagerBand)
296
- a.first.values.should == {:id => 2}
274
+ a.should == [EagerBand.load(:id=>2)]
297
275
  MODEL_DB.sqls.should == ['SELECT * FROM bands',
298
- '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))']
299
- a = a.first
300
- a.graph_albums.should be_a_kind_of(Array)
301
- a.graph_albums.size.should == 1
302
- a.graph_albums.first.should be_a_kind_of(EagerAlbum)
303
- a.graph_albums.first.values.should == {:id => 1, :band_id => 2}
304
- a = a.graph_albums.first
305
- a.tracks.should be_a_kind_of(Array)
306
- a.tracks.size.should == 1
307
- a.tracks.first.should be_a_kind_of(EagerTrack)
308
- a.tracks.first.values.should == {:id => 3, :album_id => 1}
309
- MODEL_DB.sqls.length.should == 2
276
+ '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))']
277
+ a.first.graph_albums.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
278
+ a.first.graph_albums.first.tracks.should == [EagerTrack.load(:id => 3, :album_id => 1)]
279
+ MODEL_DB.sqls.should == []
310
280
  end
311
281
 
312
- it "should cascade eagerly loading when the :eager_graph association option is used with a many_to_many association" do
313
- EagerBandMember.dataset.extend(Module.new {
314
- def columns
315
- [:id]
316
- end
317
- def fetch_rows(sql)
318
- @db << sql
319
- yield({:id=>5, :bands_id=>2, :p_k=>6, :x_foreign_key_x=>2})
320
- yield({:id=>5, :bands_id=>3, :p_k=>6, :x_foreign_key_x=>2})
321
- end
322
- })
323
- a = EagerBand.eager(:graph_members).all
324
- a.should == [EagerBand.load(:id=>2)]
325
- MODEL_DB.sqls.should == ['SELECT * FROM bands',
326
- 'SELECT members.id, bands.id AS bands_id, bands.p_k, bm.band_id AS x_foreign_key_x FROM (SELECT members.* FROM members INNER JOIN bm ON ((bm.member_id = members.id) AND (bm.band_id IN (2)))) AS members LEFT OUTER JOIN bm AS bm_0 ON (bm_0.member_id = members.id) LEFT OUTER JOIN bands ON (bands.id = bm_0.band_id) ORDER BY bands.id']
327
- a = a.first
328
- a.graph_members.should == [EagerBandMember.load(:id=>5)]
329
- a.graph_members.first.bands.should == [EagerBand.load(:id=>2, :p_k=>6), EagerBand.load(:id=>3, :p_k=>6)]
330
- MODEL_DB.sqls.length.should == 2
282
+ it "should raise an Error when eager loading a many_to_many association with the :eager_graph option" do
283
+ proc{EagerBand.eager(:graph_members).all}.should raise_error(Sequel::Error)
331
284
  end
332
285
 
333
286
  it "should respect :eager_graph when lazily loading an association" do
334
287
  a = EagerBand.all
335
- a.should be_a_kind_of(Array)
336
- a.size.should == 1
337
- a.first.should be_a_kind_of(EagerBand)
338
- a.first.values.should == {:id => 2}
288
+ a.should == [EagerBand.load(:id=>2)]
339
289
  MODEL_DB.sqls.should == ['SELECT * FROM bands']
340
290
  a = a.first
341
- EagerAlbum.dataset.extend(Module.new {
342
- def fetch_rows(sql)
343
- @db << sql
344
- yield({:id=>1, :band_id=>2, :tracks_id=>3, :album_id=>1})
345
- end
346
- })
291
+ EagerAlbum.dataset._fetch = {:id=>1, :band_id=>2, :tracks_id=>3, :album_id=>1}
347
292
  a.graph_albums
348
- MODEL_DB.sqls.should == ['SELECT * FROM bands',
349
- '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)']
350
- a.graph_albums.should be_a_kind_of(Array)
351
- a.graph_albums.size.should == 1
352
- a.graph_albums.first.should be_a_kind_of(EagerAlbum)
353
- a.graph_albums.first.values.should == {:id => 1, :band_id => 2}
354
- a = a.graph_albums.first
355
- a.tracks.should be_a_kind_of(Array)
356
- a.tracks.size.should == 1
357
- a.tracks.first.should be_a_kind_of(EagerTrack)
358
- a.tracks.first.values.should == {:id => 3, :album_id => 1}
359
- MODEL_DB.sqls.length.should == 2
293
+ 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) WHERE (albums.band_id = 2)']
294
+ a.graph_albums.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
295
+ a.graph_albums.first.tracks.should == [EagerTrack.load(:id => 3, :album_id => 1)]
296
+ MODEL_DB.sqls.should == []
360
297
  end
361
298
 
362
299
  it "should respect :eager_graph when lazily loading a many_to_many association" do
363
- EagerBandMember.dataset.extend(Module.new {
364
- def columns
365
- [:id]
366
- end
367
- def fetch_rows(sql)
368
- @db << sql
369
- yield({:id=>5, :bands_id=>2, :p_k=>6})
370
- yield({:id=>5, :bands_id=>3, :p_k=>6})
371
- end
372
- })
300
+ ds = EagerBandMember.dataset
301
+ def ds.columns() [:id] end
302
+ ds._fetch = [{:id=>5, :bands_id=>2, :p_k=>6}, {:id=>5, :bands_id=>3, :p_k=>6}]
373
303
  a = EagerBand.load(:id=>2)
374
304
  a.graph_members.should == [EagerBandMember.load(:id=>5)]
375
305
  MODEL_DB.sqls.should == ['SELECT members.id, bands.id AS bands_id, bands.p_k FROM (SELECT members.* FROM members INNER JOIN bm ON ((bm.member_id = members.id) AND (bm.band_id = 2))) AS members LEFT OUTER JOIN bm AS bm_0 ON (bm_0.member_id = members.id) LEFT OUTER JOIN bands ON (bands.id = bm_0.band_id) ORDER BY bands.id']
376
306
  a.graph_members.first.bands.should == [EagerBand.load(:id=>2, :p_k=>6), EagerBand.load(:id=>3, :p_k=>6)]
377
- MODEL_DB.sqls.length.should == 1
307
+ MODEL_DB.sqls.should == []
378
308
  end
379
309
 
380
310
  it "should respect :conditions when eagerly loading" do
381
311
  EagerBandMember.many_to_many :good_bands, :clone=>:bands, :conditions=>{:a=>32}
382
312
  a = EagerBandMember.eager(:good_bands).all
383
- a.should be_a_kind_of(Array)
384
- a.size.should == 1
385
- a.first.should be_a_kind_of(EagerBandMember)
386
- a.first.values.should == {:id => 5}
313
+ a.should == [EagerBandMember.load(:id => 5)]
387
314
  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']
388
- a = a.first
389
- a.good_bands.should be_a_kind_of(Array)
390
- a.good_bands.size.should == 1
391
- a.good_bands.first.should be_a_kind_of(EagerBand)
392
- a.good_bands.first.values.should == {:id => 2}
393
- MODEL_DB.sqls.length.should == 2
315
+ a.first.good_bands.should == [EagerBand.load(:id => 2)]
316
+ MODEL_DB.sqls.should == []
394
317
 
395
- MODEL_DB.sqls.clear
396
318
  EagerBandMember.many_to_many :good_bands, :clone=>:bands, :conditions=>"x = 1"
397
319
  a = EagerBandMember.eager(:good_bands).all
398
320
  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']
@@ -400,63 +322,36 @@ describe Sequel::Model, "#eager" do
400
322
 
401
323
  it "should respect :order when eagerly loading" do
402
324
  a = EagerBandMember.eager(:bands).all
403
- a.should be_a_kind_of(Array)
404
- a.size.should == 1
405
- a.first.should be_a_kind_of(EagerBandMember)
406
- a.first.values.should == {:id => 5}
325
+ a.should == [EagerBandMember.load(:id => 5)]
407
326
  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']
408
- a = a.first
409
- a.bands.should be_a_kind_of(Array)
410
- a.bands.size.should == 1
411
- a.bands.first.should be_a_kind_of(EagerBand)
412
- a.bands.first.values.should == {:id => 2}
413
- MODEL_DB.sqls.length.should == 2
327
+ a.first.bands.should == [EagerBand.load(:id => 2)]
328
+ MODEL_DB.sqls.should == []
414
329
  end
415
330
 
416
331
  it "should populate the reciprocal many_to_one association when eagerly loading the one_to_many association" do
417
332
  a = EagerAlbum.eager(:tracks).all
418
- a.should be_a_kind_of(Array)
419
- a.size.should == 1
420
- a.first.should be_a_kind_of(EagerAlbum)
421
- a.first.values.should == {:id => 1, :band_id => 2}
333
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
422
334
  MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE (tracks.album_id IN (1))']
423
- a = a.first
424
- a.tracks.should be_a_kind_of(Array)
425
- a.tracks.size.should == 1
426
- a.tracks.first.should be_a_kind_of(EagerTrack)
427
- a.tracks.first.values.should == {:id => 3, :album_id=>1}
428
- a.tracks.first.album.should be_a_kind_of(EagerAlbum)
429
- a.tracks.first.album.should == a
430
- MODEL_DB.sqls.length.should == 2
335
+ a.first.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
336
+ a.first.tracks.first.album.should == a.first
337
+ MODEL_DB.sqls.should == []
431
338
  end
432
339
 
433
340
  it "should cache the negative lookup when eagerly loading a many_to_one association" do
434
341
  a = EagerAlbum.eager(:band).filter(:id=>101).all
435
- a.should be_a_kind_of(Array)
436
- a.size.should == 1
437
- a.first.should be_a_kind_of(EagerAlbum)
438
- a.first.values.should == {:id => 101, :band_id => 101}
342
+ a.should == [EagerAlbum.load(:id => 101, :band_id => 101)]
439
343
  MODEL_DB.sqls.should == ['SELECT * FROM albums WHERE (id = 101)', 'SELECT * FROM bands WHERE (bands.id IN (101))']
440
- a = a.first
441
- a.associations.fetch(:band, 2).should == nil
442
- a.band.should == nil
443
- MODEL_DB.sqls.length.should == 2
344
+ a.first.associations.fetch(:band, 2).should be_nil
345
+ a.first.band.should be_nil
346
+ MODEL_DB.sqls.should == []
444
347
  end
445
348
 
446
349
  it "should cache the negative lookup when eagerly loading a *_to_many associations" do
447
350
  a = EagerBand.eager(:albums).filter('id > 100').all
448
- a.should be_a_kind_of(Array)
449
- a.size.should == 2
450
- a.first.should be_a_kind_of(EagerBand)
451
- a.first.values.should == {:id => 101}
452
- a.last.values.should == {:id => 102}
351
+ a.should == [EagerBand.load(:id => 101), EagerBand.load(:id =>102)]
453
352
  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))"]
454
- a.first.associations[:albums].should be_a_kind_of(Array)
455
- a.first.albums.length.should == 1
456
- a.first.albums.first.should be_a_kind_of(EagerAlbum)
457
- a.last.associations[:albums].should == []
458
- a.last.albums.should == []
459
- MODEL_DB.sqls.length.should == 3
353
+ a.map{|b| b.associations[:albums]}.should == [[EagerAlbum.load({:band_id=>101, :id=>101})], []]
354
+ MODEL_DB.sqls.should == []
460
355
  end
461
356
 
462
357
  it "should use the association's block when eager loading by default" do
@@ -467,7 +362,6 @@ describe Sequel::Model, "#eager" do
467
362
  it "should use the eager_block option when eager loading if given" do
468
363
  EagerBand.eager(:good_albums).all
469
364
  MODEL_DB.sqls.should == ['SELECT * FROM bands', "SELECT * FROM albums WHERE ((albums.band_id IN (2)) AND (name = 'good'))"]
470
- MODEL_DB.sqls.clear
471
365
  EagerBand.eager(:good_albums=>:good_tracks).all
472
366
  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'))"]
473
367
  end
@@ -480,34 +374,31 @@ describe Sequel::Model, "#eager" do
480
374
  it "should respect the association's :select option" do
481
375
  EagerAlbum.eager(:band_name).all
482
376
  MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT id, name FROM bands WHERE (bands.id IN (2))"]
483
- MODEL_DB.sqls.clear
484
377
  EagerAlbum.eager(:track_names).all
485
378
  MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT id, name FROM tracks WHERE (tracks.album_id IN (1))"]
486
- MODEL_DB.sqls.clear
487
379
  EagerAlbum.eager(:genre_names).all
488
380
  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)))"]
489
381
  end
490
382
 
383
+ it "should respect many_to_one association's :qualify option" do
384
+ EagerAlbum.many_to_one :special_band, :class=>:EagerBand, :qualify=>false, :key=>:band_id
385
+ EagerBand.dataset._fetch = {:id=>2}
386
+ as = EagerAlbum.eager(:special_band).all
387
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM bands WHERE (id IN (2))"]
388
+ as.map{|a| a.special_band}.should == [EagerBand.load(:id=>2)]
389
+ MODEL_DB.sqls.should == []
390
+ end
391
+
491
392
  it "should respect the association's :primary_key option" do
492
393
  EagerAlbum.many_to_one :special_band, :class=>:EagerBand, :primary_key=>:p_k, :key=>:band_id
493
- EagerBand.dataset.extend(Module.new {
494
- def fetch_rows(sql)
495
- MODEL_DB.sqls << sql
496
- yield({:p_k=>2, :id=>1})
497
- end
498
- })
394
+ EagerBand.dataset._fetch = {:p_k=>2, :id=>1}
499
395
  as = EagerAlbum.eager(:special_band).all
500
396
  MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM bands WHERE (bands.p_k IN (2))"]
501
397
  as.length.should == 1
502
398
  as.first.special_band.should == EagerBand.load(:p_k=>2, :id=>1)
503
- MODEL_DB.sqls.clear
399
+
504
400
  EagerAlbum.one_to_many :special_tracks, :class=>:EagerTrack, :primary_key=>:band_id, :key=>:album_id
505
- EagerTrack.dataset.extend(Module.new {
506
- def fetch_rows(sql)
507
- MODEL_DB.sqls << sql
508
- yield({:album_id=>2, :id=>1})
509
- end
510
- })
401
+ EagerTrack.dataset._fetch = {:album_id=>2, :id=>1}
511
402
  as = EagerAlbum.eager(:special_tracks).all
512
403
  MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM tracks WHERE (tracks.album_id IN (2))"]
513
404
  as.length.should == 1
@@ -516,12 +407,7 @@ describe Sequel::Model, "#eager" do
516
407
 
517
408
  it "should respect the many_to_one association's composite keys" do
518
409
  EagerAlbum.many_to_one :special_band, :class=>:EagerBand, :primary_key=>[:id, :p_k], :key=>[:band_id, :id]
519
- EagerBand.dataset.extend(Module.new {
520
- def fetch_rows(sql)
521
- MODEL_DB.sqls << sql
522
- yield({:p_k=>1, :id=>2})
523
- end
524
- })
410
+ EagerBand.dataset._fetch = {:p_k=>1, :id=>2}
525
411
  as = EagerAlbum.eager(:special_band).all
526
412
  MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM bands WHERE ((bands.id, bands.p_k) IN ((2, 1)))"]
527
413
  as.length.should == 1
@@ -530,12 +416,7 @@ describe Sequel::Model, "#eager" do
530
416
 
531
417
  it "should respect the one_to_many association's composite keys" do
532
418
  EagerAlbum.one_to_many :special_tracks, :class=>:EagerTrack, :primary_key=>[:band_id, :id], :key=>[:id, :album_id]
533
- EagerTrack.dataset.extend(Module.new {
534
- def fetch_rows(sql)
535
- MODEL_DB.sqls << sql
536
- yield({:album_id=>1, :id=>2})
537
- end
538
- })
419
+ EagerTrack.dataset._fetch = {:album_id=>1, :id=>2}
539
420
  as = EagerAlbum.eager(:special_tracks).all
540
421
  MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM tracks WHERE ((tracks.id, tracks.album_id) IN ((2, 1)))"]
541
422
  as.length.should == 1
@@ -544,13 +425,7 @@ describe Sequel::Model, "#eager" do
544
425
 
545
426
  it "should respect many_to_many association's composite keys" do
546
427
  EagerAlbum.many_to_many :special_genres, :class=>:EagerGenre, :left_primary_key=>[:band_id, :id], :left_key=>[:l1, :l2], :right_primary_key=>[:xxx, :id], :right_key=>[:r1, :r2], :join_table=>:ag
547
- EagerGenre.dataset.extend(Module.new {
548
- def fetch_rows(sql)
549
- MODEL_DB.sqls << sql
550
- yield({:x_foreign_key_0_x=>2, :x_foreign_key_1_x=>1, :id=>5})
551
- yield({:x_foreign_key_0_x=>2, :x_foreign_key_1_x=>1, :id=>6})
552
- end
553
- })
428
+ EagerGenre.dataset._fetch = [{:x_foreign_key_0_x=>2, :x_foreign_key_1_x=>1, :id=>5}, {:x_foreign_key_0_x=>2, :x_foreign_key_1_x=>1, :id=>6}]
554
429
  as = EagerAlbum.eager(:special_genres).all
555
430
  MODEL_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))))"]
556
431
  as.length.should == 1
@@ -559,19 +434,116 @@ describe Sequel::Model, "#eager" do
559
434
 
560
435
  it "should respect many_to_many association's :left_primary_key and :right_primary_key options" do
561
436
  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
562
- EagerGenre.dataset.extend(Module.new {
563
- def fetch_rows(sql)
564
- MODEL_DB.sqls << sql
565
- yield({:x_foreign_key_x=>2, :id=>5})
566
- yield({:x_foreign_key_x=>2, :id=>6})
567
- end
568
- })
437
+ EagerGenre.dataset._fetch = [{:x_foreign_key_x=>2, :id=>5}, {:x_foreign_key_x=>2, :id=>6}]
569
438
  as = EagerAlbum.eager(:special_genres).all
570
439
  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)))"]
571
440
  as.length.should == 1
572
441
  as.first.special_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
573
442
  end
574
443
 
444
+ it "should respect the :limit option on a one_to_many association" do
445
+ EagerAlbum.one_to_many :first_two_tracks, :class=>:EagerTrack, :key=>:album_id, :limit=>2
446
+ EagerTrack.dataset._fetch = [{:album_id=>1, :id=>2}, {:album_id=>1, :id=>3}, {:album_id=>1, :id=>4}]
447
+ as = EagerAlbum.eager(:first_two_tracks).all
448
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM tracks WHERE (tracks.album_id IN (1))"]
449
+ as.length.should == 1
450
+ as.first.first_two_tracks.should == [EagerTrack.load(:album_id=>1, :id=>2), EagerTrack.load(:album_id=>1, :id=>3)]
451
+
452
+ MODEL_DB.reset
453
+ EagerAlbum.one_to_many :first_two_tracks, :class=>:EagerTrack, :key=>:album_id, :limit=>[2,1]
454
+ as = EagerAlbum.eager(:first_two_tracks).all
455
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM tracks WHERE (tracks.album_id IN (1))"]
456
+ as.length.should == 1
457
+ as.first.first_two_tracks.should == [EagerTrack.load(:album_id=>1, :id=>3), EagerTrack.load(:album_id=>1, :id=>4)]
458
+ end
459
+
460
+ it "should respect the :limit option on a one_to_many association using the :window_function strategy" do
461
+ EagerTrack.dataset.meta_def(:supports_window_functions?){true}
462
+ EagerAlbum.one_to_many :tracks, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>true, :order=>:name, :limit=>2
463
+ a = EagerAlbum.eager(:tracks).all
464
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
465
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM (SELECT *, row_number() OVER (PARTITION BY tracks.album_id ORDER BY name) AS x_sequel_row_number_x FROM tracks WHERE (tracks.album_id IN (1))) AS t1 WHERE (x_sequel_row_number_x <= 2)']
466
+ a.first.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
467
+ MODEL_DB.sqls.should == []
468
+ end
469
+
470
+ it "should respect the :limit option with an offset on a one_to_many association using the :window_function strategy" do
471
+ EagerTrack.dataset.meta_def(:supports_window_functions?){true}
472
+ EagerAlbum.one_to_many :tracks, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>true, :order=>:name, :limit=>[2, 1]
473
+ a = EagerAlbum.eager(:tracks).all
474
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
475
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM (SELECT *, row_number() OVER (PARTITION BY tracks.album_id ORDER BY name) AS x_sequel_row_number_x FROM tracks WHERE (tracks.album_id IN (1))) AS t1 WHERE ((x_sequel_row_number_x >= 2) AND (x_sequel_row_number_x < 4))']
476
+ a.first.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
477
+ MODEL_DB.sqls.should == []
478
+ end
479
+
480
+ it "should respect the :limit option on a one_to_many association using the :correlated_subquery strategy" do
481
+ EagerAlbum.one_to_many :tracks, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>:correlated_subquery, :order=>:name, :limit=>2
482
+ a = EagerAlbum.eager(:tracks).all
483
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
484
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE ((tracks.album_id IN (1)) AND (tracks.id IN (SELECT t1.id FROM tracks AS t1 WHERE (t1.album_id = tracks.album_id) ORDER BY name LIMIT 2))) ORDER BY name']
485
+ a.first.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
486
+ MODEL_DB.sqls.should == []
487
+ end
488
+
489
+ it "should respect the :limit option with an offset on a one_to_many association using the :correlated_subquery strategy" do
490
+ EagerAlbum.one_to_many :tracks, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>:correlated_subquery, :order=>:name, :limit=>[2, 1]
491
+ a = EagerAlbum.eager(:tracks).all
492
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
493
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE ((tracks.album_id IN (1)) AND (tracks.id IN (SELECT t1.id FROM tracks AS t1 WHERE (t1.album_id = tracks.album_id) ORDER BY name LIMIT 2 OFFSET 1))) ORDER BY name']
494
+ a.first.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
495
+ MODEL_DB.sqls.should == []
496
+ end
497
+
498
+ it "should respect the limit option on a many_to_many association" do
499
+ EagerAlbum.many_to_many :first_two_genres, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :limit=>2
500
+ EagerGenre.dataset._fetch = [{:x_foreign_key_x=>2, :id=>5}, {:x_foreign_key_x=>2, :id=>6}, {:x_foreign_key_x=>2, :id=>7}]
501
+ as = EagerAlbum.eager(:first_two_genres).all
502
+ 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 (2)))"]
503
+ as.length.should == 1
504
+ as.first.first_two_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
505
+
506
+ EagerGenre.dataset._fetch = [{:x_foreign_key_x=>2, :id=>5}, {:x_foreign_key_x=>2, :id=>6}, {:x_foreign_key_x=>2, :id=>7}]
507
+ EagerAlbum.many_to_many :first_two_genres, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :limit=>[2, 1]
508
+ as = EagerAlbum.eager(:first_two_genres).all
509
+ 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 (2)))"]
510
+ as.length.should == 1
511
+ as.first.first_two_genres.should == [EagerGenre.load(:id=>6), EagerGenre.load(:id=>7)]
512
+ end
513
+
514
+ it "should respect the limit option on a many_to_many association using the :window_function strategy" do
515
+ EagerGenre.dataset.meta_def(:supports_window_functions?){true}
516
+ EagerAlbum.many_to_many :first_two_genres, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :eager_limit_strategy=>true, :limit=>2, :order=>:name
517
+ EagerGenre.dataset._fetch = [{:x_foreign_key_x=>2, :id=>5}, {:x_foreign_key_x=>2, :id=>6}]
518
+ as = EagerAlbum.eager(:first_two_genres).all
519
+ MODEL_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 ORDER BY name) AS x_sequel_row_number_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (2)))) AS t1 WHERE (x_sequel_row_number_x <= 2)"]
520
+ as.length.should == 1
521
+ as.first.first_two_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
522
+
523
+ EagerGenre.dataset._fetch = [{:x_foreign_key_x=>2, :id=>5}, {:x_foreign_key_x=>2, :id=>6}]
524
+ EagerAlbum.many_to_many :first_two_genres, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :eager_limit_strategy=>true, :limit=>[2, 1], :order=>:name
525
+ as = EagerAlbum.eager(:first_two_genres).all
526
+ MODEL_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 ORDER BY name) AS x_sequel_row_number_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (2)))) AS t1 WHERE ((x_sequel_row_number_x >= 2) AND (x_sequel_row_number_x < 4))"]
527
+ as.length.should == 1
528
+ as.first.first_two_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
529
+ end
530
+
531
+ it "should respect the limit option on a many_to_many association using the :correlated_subquery strategy" do
532
+ EagerAlbum.many_to_many :first_two_genres, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :eager_limit_strategy=>:correlated_subquery, :limit=>2, :order=>:name
533
+ EagerGenre.dataset._fetch = [{:x_foreign_key_x=>2, :id=>5}, {:x_foreign_key_x=>2, :id=>6}]
534
+ as = EagerAlbum.eager(:first_two_genres).all
535
+ 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 (2))) WHERE (genres.id IN (SELECT t1.id FROM genres AS t1 INNER JOIN ag AS t2 ON ((t2.genre_id = t1.id) AND (t2.album_id = ag.album_id)) ORDER BY name LIMIT 2)) ORDER BY name"]
536
+ as.length.should == 1
537
+ as.first.first_two_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
538
+
539
+ EagerGenre.dataset._fetch = [{:x_foreign_key_x=>2, :id=>5}, {:x_foreign_key_x=>2, :id=>6}]
540
+ EagerAlbum.many_to_many :first_two_genres, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :eager_limit_strategy=>:correlated_subquery, :limit=>[2, 1], :order=>:name
541
+ as = EagerAlbum.eager(:first_two_genres).all
542
+ 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 (2))) WHERE (genres.id IN (SELECT t1.id FROM genres AS t1 INNER JOIN ag AS t2 ON ((t2.genre_id = t1.id) AND (t2.album_id = ag.album_id)) ORDER BY name LIMIT 2 OFFSET 1)) ORDER BY name"]
543
+ as.length.should == 1
544
+ as.first.first_two_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
545
+ end
546
+
575
547
  it "should use the :eager_loader association option when eager loading" do
576
548
  EagerAlbum.many_to_one :special_band, :eager_loader=>(proc do |key_hash, records, assocs|
577
549
  item = EagerBand.filter(:album_id=>records.collect{|r| [r.pk, r.pk*2]}.flatten).order(:name).first
@@ -586,27 +558,17 @@ describe Sequel::Model, "#eager" do
586
558
  records.each{|r| r.associations[:special_genres] = items}
587
559
  end)
588
560
  a = EagerAlbum.eager(:special_genres, :special_tracks, :special_band).all
589
- a.should be_a_kind_of(Array)
590
- a.size.should == 1
591
- a.first.should be_a_kind_of(EagerAlbum)
592
- a.first.values.should == {:id => 1, :band_id => 2}
593
- MODEL_DB.sqls.length.should == 4
594
- MODEL_DB.sqls[0].should == 'SELECT * FROM albums'
595
- MODEL_DB.sqls[1..-1].should(include('SELECT * FROM bands WHERE (album_id IN (1, 2)) ORDER BY name LIMIT 1'))
596
- MODEL_DB.sqls[1..-1].should(include('SELECT * FROM tracks WHERE (album_id IN (1, 2))'))
597
- MODEL_DB.sqls[1..-1].should(include('SELECT * FROM genres INNER JOIN ag USING (genre_id) WHERE (album_id IN (1))'))
561
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
562
+ sqls = MODEL_DB.sqls
563
+ sqls.shift.should == 'SELECT * FROM albums'
564
+ sqls.sort.should == ['SELECT * FROM bands WHERE (album_id IN (1, 2)) ORDER BY name LIMIT 1',
565
+ 'SELECT * FROM genres INNER JOIN ag USING (genre_id) WHERE (album_id IN (1))',
566
+ 'SELECT * FROM tracks WHERE (album_id IN (1, 2))']
598
567
  a = a.first
599
- a.special_band.should be_a_kind_of(EagerBand)
600
- a.special_band.values.should == {:id => 2}
601
- a.special_tracks.should be_a_kind_of(Array)
602
- a.special_tracks.size.should == 1
603
- a.special_tracks.first.should be_a_kind_of(EagerTrack)
604
- a.special_tracks.first.values.should == {:id => 3, :album_id=>1}
605
- a.special_genres.should be_a_kind_of(Array)
606
- a.special_genres.size.should == 1
607
- a.special_genres.first.should be_a_kind_of(EagerGenre)
608
- a.special_genres.first.values.should == {:id => 4}
609
- MODEL_DB.sqls.length.should == 4
568
+ a.special_band.should == EagerBand.load(:id => 2)
569
+ a.special_tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
570
+ a.special_genres.should == [EagerGenre.load(:id => 4)]
571
+ MODEL_DB.sqls.should == []
610
572
  end
611
573
 
612
574
  it "should raise an error if you use an :eager_loader proc with the wrong arity" do
@@ -626,13 +588,7 @@ describe Sequel::Model, "#eager" do
626
588
 
627
589
  it "should respect :uniq option when eagerly loading many_to_many associations" do
628
590
  EagerAlbum.many_to_many :al_genres, :class=>'EagerGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :uniq=>true
629
- EagerGenre.dataset.extend(Module.new {
630
- def fetch_rows(sql)
631
- MODEL_DB.sqls << sql
632
- yield({:x_foreign_key_x=>1, :id=>8})
633
- yield({:x_foreign_key_x=>1, :id=>8})
634
- end
635
- })
591
+ EagerGenre.dataset._fetch = [{:x_foreign_key_x=>1, :id=>8}, {:x_foreign_key_x=>1, :id=>8}]
636
592
  a = EagerAlbum.eager(:al_genres).all.first
637
593
  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)))"]
638
594
  a.should == EagerAlbum.load(:id => 1, :band_id => 2)
@@ -646,26 +602,103 @@ describe Sequel::Model, "#eager" do
646
602
  a.should == EagerAlbum.load(:id => 1, :band_id => 2)
647
603
  a.al_genres.should == [EagerGenre.load(:id=>4)]
648
604
  end
649
- end
650
605
 
651
- describe Sequel::Model, "#eager_graph" do
652
- after(:all) do
653
- class ::MockDataset
654
- alias clone orig_clone
655
- end
606
+ it "should eagerly load a many_to_one association with custom eager block" do
607
+ a = EagerAlbum.eager(:band => proc {|ds| ds.select(:id, :name)}).all
608
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
609
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT id, name FROM bands WHERE (bands.id IN (2))']
610
+ a.first.band.should == EagerBand.load(:id => 2)
611
+ MODEL_DB.sqls.should == []
656
612
  end
657
613
 
658
- before(:all) do
659
- class ::MockDataset
660
- alias orig_clone clone
661
- def clone(opts = {})
662
- c = super()
663
- c.opts = @opts.merge(opts)
664
- c.instance_variable_set(:@columns, (@columns.dup if @columns))
665
- c
666
- end
667
- end
614
+ it "should eagerly load a one_to_one association with custom eager block" do
615
+ EagerAlbum.one_to_one :track, :class=>'EagerTrack', :key=>:album_id
616
+ a = EagerAlbum.eager(:track => proc {|ds| ds.select(:id)}).all
617
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
618
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT id FROM tracks WHERE (tracks.album_id IN (1))']
619
+ a.first.track.should == EagerTrack.load(:id => 3, :album_id=>1)
620
+ MODEL_DB.sqls.should == []
621
+ end
622
+
623
+ it "should eagerly load a one_to_many association with custom eager block" do
624
+ a = EagerAlbum.eager(:tracks => proc {|ds| ds.select(:id)}).all
625
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
626
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT id FROM tracks WHERE (tracks.album_id IN (1))']
627
+ a.first.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
628
+ MODEL_DB.sqls.should == []
629
+ end
630
+
631
+ it "should eagerly load a many_to_many association with custom eager block" do
632
+ a = EagerAlbum.eager(:genres => proc {|ds| ds.select(:name)}).all
633
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
634
+ MODEL_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)))"]
635
+ a.first.genres.should == [EagerGenre.load(:id => 4)]
636
+ MODEL_DB.sqls.should == []
637
+ end
638
+
639
+ it "should allow cascading of eager loading within a custom eager block" do
640
+ a = EagerTrack.eager(:album => proc {|ds| ds.eager(:band => :members)}).all
641
+ a.should == [EagerTrack.load(:id => 3, :album_id => 1)]
642
+ MODEL_DB.sqls.should == ['SELECT * FROM tracks',
643
+ 'SELECT * FROM albums WHERE (albums.id IN (1))',
644
+ 'SELECT * FROM bands WHERE (bands.id IN (2))',
645
+ "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)))"]
646
+ a = a.first
647
+ a.album.should == EagerAlbum.load(:id => 1, :band_id => 2)
648
+ a.album.band.should == EagerBand.load(:id => 2)
649
+ a.album.band.members.should == [EagerBandMember.load(:id => 5)]
650
+ MODEL_DB.sqls.should == []
651
+ end
652
+
653
+ it "should allow cascading of eager loading with custom callback with hash value" do
654
+ a = EagerTrack.eager(:album=>{proc{|ds| ds.select(:id, :band_id)}=>{:band => :members}}).all
655
+ a.should == [EagerTrack.load(:id => 3, :album_id => 1)]
656
+ MODEL_DB.sqls.should == ['SELECT * FROM tracks',
657
+ 'SELECT id, band_id FROM albums WHERE (albums.id IN (1))',
658
+ 'SELECT * FROM bands WHERE (bands.id IN (2))',
659
+ "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)))"]
660
+ a = a.first
661
+ a.album.should == EagerAlbum.load(:id => 1, :band_id => 2)
662
+ a.album.band.should == EagerBand.load(:id => 2)
663
+ a.album.band.members.should == [EagerBandMember.load(:id => 5)]
664
+ MODEL_DB.sqls.should == []
665
+ end
666
+
667
+ it "should allow cascading of eager loading with custom callback with symbol value" do
668
+ a = EagerTrack.eager(:album=>{proc{|ds| ds.select(:id, :band_id)}=>:band}).all
669
+ a.should == [EagerTrack.load(:id => 3, :album_id => 1)]
670
+ MODEL_DB.sqls.should == ['SELECT * FROM tracks',
671
+ 'SELECT id, band_id FROM albums WHERE (albums.id IN (1))',
672
+ 'SELECT * FROM bands WHERE (bands.id IN (2))']
673
+ a = a.first
674
+ a.album.should == EagerAlbum.load(:id => 1, :band_id => 2)
675
+ a.album.band.should == EagerBand.load(:id => 2)
676
+ MODEL_DB.sqls.should == []
677
+ end
678
+
679
+ it "should allow cascading of eager loading with custom callback with array value" do
680
+ a = EagerTrack.eager(:album=>{proc{|ds| ds.select(:id, :band_id)}=>[:band, :band_name]}).all
681
+ a.should == [EagerTrack.load(:id => 3, :album_id => 1)]
682
+ sqls = MODEL_DB.sqls
683
+ sqls.slice!(0..1).should == ['SELECT * FROM tracks',
684
+ 'SELECT id, band_id FROM albums WHERE (albums.id IN (1))']
685
+ sqls.sort.should == ['SELECT * FROM bands WHERE (bands.id IN (2))',
686
+ 'SELECT id, name FROM bands WHERE (bands.id IN (2))']
687
+ a = a.first
688
+ a.album.should == EagerAlbum.load(:id => 1, :band_id => 2)
689
+ a.album.band.should == EagerBand.load(:id => 2)
690
+ a.album.band_name.should == EagerBand.load(:id => 2)
691
+ MODEL_DB.sqls.should == []
692
+ end
668
693
 
694
+ it "should call both association and custom eager blocks" do
695
+ EagerBand.eager(:good_albums => proc {|ds| ds.select(:name)}).all
696
+ MODEL_DB.sqls.should == ['SELECT * FROM bands', "SELECT name FROM albums WHERE ((albums.band_id IN (2)) AND (name = 'good'))"]
697
+ end
698
+ end
699
+
700
+ describe Sequel::Model, "#eager_graph" do
701
+ before(:all) do
669
702
  class ::GraphAlbum < Sequel::Model(:albums)
670
703
  dataset.opts[:from] = [:albums]
671
704
  columns :id, :band_id
@@ -702,6 +735,9 @@ describe Sequel::Model, "#eager_graph" do
702
735
  many_to_many :bands, :class=>'GraphBand', :left_key=>:member_id, :right_key=>:band_id, :join_table=>:bm
703
736
  end
704
737
  end
738
+ after(:all) do
739
+ [:GraphAlbum, :GraphBand, :GraphTrack, :GraphGenre, :GraphBandMember].each{|x| Object.send(:remove_const, x)}
740
+ end
705
741
 
706
742
  it "should raise an error if called without a symbol or hash" do
707
743
  proc{GraphAlbum.eager_graph(Object.new)}.should raise_error(Sequel::Error)
@@ -710,35 +746,43 @@ describe Sequel::Model, "#eager_graph" do
710
746
  it "should not split results and assign associations if ungraphed is called" do
711
747
  ds = GraphAlbum.eager_graph(:band).ungraphed
712
748
  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)'
713
- def ds.fetch_rows(sql, &block)
714
- yield({:id=>1, :band_id=>2, :band_id_0=>2, :vocalist_id=>3})
715
- end
749
+ ds._fetch = {:id=>1, :band_id=>2, :band_id_0=>2, :vocalist_id=>3}
716
750
  ds.all.should == [GraphAlbum.load(:id=>1, :band_id=>2, :band_id_0=>2, :vocalist_id=>3)]
717
751
  end
718
752
 
753
+ it "should not modify existing dataset" do
754
+ ds1 = GraphAlbum.dataset
755
+ ds2 = ds1.eager_graph(:band)
756
+ proc{ds1.eager_graph(:band)}.should_not raise_error
757
+ proc{ds2.eager_graph(:tracks)}.should_not raise_error
758
+ proc{ds2.eager_graph(:tracks)}.should_not raise_error
759
+ end
760
+
719
761
  it "should eagerly load a single many_to_one association" do
720
762
  ds = GraphAlbum.eager_graph(:band)
721
763
  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)'
722
- def ds.fetch_rows(sql, &block)
723
- yield({:id=>1, :band_id=>2, :band_id_0=>2, :vocalist_id=>3})
724
- end
764
+ ds._fetch = {:id=>1, :band_id=>2, :band_id_0=>2, :vocalist_id=>3}
725
765
  a = ds.all
726
- a.should be_a_kind_of(Array)
727
- a.size.should == 1
728
- a.first.should be_a_kind_of(GraphAlbum)
729
- a.first.values.should == {:id => 1, :band_id => 2}
730
- a = a.first
731
- a.band.should be_a_kind_of(GraphBand)
732
- a.band.values.should == {:id => 2, :vocalist_id=>3}
766
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
767
+ a.first.band.should == GraphBand.load(:id => 2, :vocalist_id=>3)
768
+ end
769
+
770
+ it "should eagerly load a single many_to_one association with the same name as a column" do
771
+ GraphAlbum.def_column_alias(:band_id_id, :band_id)
772
+ GraphAlbum.many_to_one :band_id, :key_column=>:band_id, :class=>GraphBand
773
+ ds = GraphAlbum.eager_graph(:band_id)
774
+ ds.sql.should == 'SELECT albums.id, albums.band_id, band_id.id AS band_id_id, band_id.vocalist_id FROM albums LEFT OUTER JOIN bands AS band_id ON (band_id.id = albums.band_id)'
775
+ ds._fetch = {:id=>1, :band_id=>2, :band_id_id=>2, :vocalist_id=>3}
776
+ a = ds.all
777
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
778
+ a.first.band_id.should == GraphBand.load(:id => 2, :vocalist_id=>3)
733
779
  end
734
780
 
735
781
  it "should eagerly load a single one_to_one association" do
736
782
  GraphAlbum.one_to_one :track, :class=>'GraphTrack', :key=>:album_id
737
783
  ds = GraphAlbum.eager_graph(:track)
738
784
  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)'
739
- def ds.fetch_rows(sql, &block)
740
- yield({:id=>1, :band_id=>2, :track_id=>3, :album_id=>1})
741
- end
785
+ ds._fetch = {:id=>1, :band_id=>2, :track_id=>3, :album_id=>1}
742
786
  a = ds.all
743
787
  a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
744
788
  a.first.track.should == GraphTrack.load(:id => 3, :album_id=>1)
@@ -747,130 +791,88 @@ describe Sequel::Model, "#eager_graph" do
747
791
  it "should eagerly load a single one_to_many association" do
748
792
  ds = GraphAlbum.eager_graph(:tracks)
749
793
  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)'
750
- def ds.fetch_rows(sql, &block)
751
- yield({:id=>1, :band_id=>2, :tracks_id=>3, :album_id=>1})
752
- end
794
+ ds._fetch = {:id=>1, :band_id=>2, :tracks_id=>3, :album_id=>1}
753
795
  a = ds.all
754
- a.should be_a_kind_of(Array)
755
- a.size.should == 1
756
- a.first.should be_a_kind_of(GraphAlbum)
757
- a.first.values.should == {:id => 1, :band_id => 2}
758
- a = a.first
759
- a.tracks.should be_a_kind_of(Array)
760
- a.tracks.size.should == 1
761
- a.tracks.first.should be_a_kind_of(GraphTrack)
762
- a.tracks.first.values.should == {:id => 3, :album_id=>1}
796
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
797
+ a.first.tracks.should == [GraphTrack.load(:id => 3, :album_id=>1)]
763
798
  end
764
799
 
765
800
  it "should eagerly load a single many_to_many association" do
766
801
  ds = GraphAlbum.eager_graph(:genres)
767
802
  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)'
768
- def ds.fetch_rows(sql, &block)
769
- yield({:id=>1, :band_id=>2, :genres_id=>4})
770
- end
803
+ ds._fetch = {:id=>1, :band_id=>2, :genres_id=>4}
771
804
  a = ds.all
772
- a.should be_a_kind_of(Array)
773
- a.size.should == 1
774
- a.first.should be_a_kind_of(GraphAlbum)
775
- a.first.values.should == {:id => 1, :band_id => 2}
776
- a = a.first
777
- a.genres.should be_a_kind_of(Array)
778
- a.genres.size.should == 1
779
- a.genres.first.should be_a_kind_of(GraphGenre)
780
- a.genres.first.values.should == {:id => 4}
805
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
806
+ a.first.genres.should == [GraphGenre.load(:id => 4)]
781
807
  end
782
808
 
809
+ it "should correctly handle an aliased join table in many_to_many" do
810
+ c = Class.new(GraphAlbum)
811
+ c.many_to_many :genres, :clone=>:genres, :join_table=>:ag___ga
812
+ 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)'
813
+
814
+ c.many_to_many :genres, :clone=>:genres, :join_table=>:ag___albums
815
+ 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)'
816
+
817
+ c.many_to_many :genres, :clone=>:genres, :join_table=>:ag___genres
818
+ 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)'
819
+ end
820
+
783
821
  it "should eagerly load multiple associations in a single call" do
784
822
  ds = GraphAlbum.eager_graph(:genres, :tracks, :band)
785
823
  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)'
786
- def ds.fetch_rows(sql, &block)
787
- yield({:id=>1, :band_id=>2, :genres_id=>4, :tracks_id=>3, :album_id=>1, :band_id_0=>2, :vocalist_id=>6})
788
- end
824
+ ds._fetch = {:id=>1, :band_id=>2, :genres_id=>4, :tracks_id=>3, :album_id=>1, :band_id_0=>2, :vocalist_id=>6}
789
825
  a = ds.all
790
- a.should be_a_kind_of(Array)
791
- a.size.should == 1
792
- a.first.should be_a_kind_of(GraphAlbum)
793
- a.first.values.should == {:id => 1, :band_id => 2}
826
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
794
827
  a = a.first
795
- a.band.should be_a_kind_of(GraphBand)
796
- a.band.values.should == {:id => 2, :vocalist_id=>6}
797
- a.tracks.should be_a_kind_of(Array)
798
- a.tracks.size.should == 1
799
- a.tracks.first.should be_a_kind_of(GraphTrack)
800
- a.tracks.first.values.should == {:id => 3, :album_id=>1}
801
- a.genres.should be_a_kind_of(Array)
802
- a.genres.size.should == 1
803
- a.genres.first.should be_a_kind_of(GraphGenre)
804
- a.genres.first.values.should == {:id => 4}
828
+ a.band.should == GraphBand.load(:id => 2, :vocalist_id=>6)
829
+ a.tracks.should == [GraphTrack.load({:id => 3, :album_id=>1})]
830
+ a.genres.should == [GraphGenre.load(:id => 4)]
805
831
  end
806
832
 
807
833
  it "should eagerly load multiple associations in separate calls" do
808
834
  ds = GraphAlbum.eager_graph(:genres).eager_graph(:tracks).eager_graph(:band)
809
835
  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)'
810
- def ds.fetch_rows(sql, &block)
811
- yield({:id=>1, :band_id=>2, :genres_id=>4, :tracks_id=>3, :album_id=>1, :band_id_0=>2, :vocalist_id=>6})
812
- end
836
+ ds._fetch = {:id=>1, :band_id=>2, :genres_id=>4, :tracks_id=>3, :album_id=>1, :band_id_0=>2, :vocalist_id=>6}
813
837
  a = ds.all
814
- a.should be_a_kind_of(Array)
815
- a.size.should == 1
816
- a.first.should be_a_kind_of(GraphAlbum)
817
- a.first.values.should == {:id => 1, :band_id => 2}
838
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
818
839
  a = a.first
819
- a.band.should be_a_kind_of(GraphBand)
820
- a.band.values.should == {:id => 2, :vocalist_id=>6}
821
- a.tracks.should be_a_kind_of(Array)
822
- a.tracks.size.should == 1
823
- a.tracks.first.should be_a_kind_of(GraphTrack)
824
- a.tracks.first.values.should == {:id => 3, :album_id=>1}
825
- a.genres.should be_a_kind_of(Array)
826
- a.genres.size.should == 1
827
- a.genres.first.should be_a_kind_of(GraphGenre)
828
- a.genres.first.values.should == {:id => 4}
840
+ a.band.should == GraphBand.load(:id => 2, :vocalist_id=>6)
841
+ a.tracks.should == [GraphTrack.load({:id => 3, :album_id=>1})]
842
+ a.genres.should == [GraphGenre.load(:id => 4)]
829
843
  end
830
844
 
831
845
  it "should allow cascading of eager loading for associations of associated models" do
832
846
  ds = GraphTrack.eager_graph(:album=>{:band=>:members})
833
847
  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)'
834
- def ds.fetch_rows(sql, &block)
835
- yield({:id=>3, :album_id=>1, :album_id_0=>1, :band_id=>2, :members_id=>5, :band_id_0=>2, :vocalist_id=>6})
836
- end
848
+ ds._fetch = {:id=>3, :album_id=>1, :album_id_0=>1, :band_id=>2, :members_id=>5, :band_id_0=>2, :vocalist_id=>6}
837
849
  a = ds.all
838
- a.should be_a_kind_of(Array)
839
- a.size.should == 1
840
- a.first.should be_a_kind_of(GraphTrack)
841
- a.first.values.should == {:id => 3, :album_id => 1}
850
+ a.should == [GraphTrack.load(:id => 3, :album_id => 1)]
842
851
  a = a.first
843
- a.album.should be_a_kind_of(GraphAlbum)
844
- a.album.values.should == {:id => 1, :band_id => 2}
845
- a.album.band.should be_a_kind_of(GraphBand)
846
- a.album.band.values.should == {:id => 2, :vocalist_id=>6}
847
- a.album.band.members.should be_a_kind_of(Array)
848
- a.album.band.members.size.should == 1
849
- a.album.band.members.first.should be_a_kind_of(GraphBandMember)
850
- a.album.band.members.first.values.should == {:id => 5}
852
+ a.album.should == GraphAlbum.load(:id => 1, :band_id => 2)
853
+ a.album.band.should == GraphBand.load(:id => 2, :vocalist_id=>6)
854
+ a.album.band.members.should == [GraphBandMember.load(:id => 5)]
851
855
  end
852
856
 
853
857
  it "should allow cascading of eager loading for multiple *_to_many associations, eliminating duplicates caused by cartesian products" do
854
858
  ds = GraphBand.eager_graph({:albums=>:tracks}, :members)
855
859
  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)'
856
- def ds.fetch_rows(sql, &block)
857
- yield({:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>4, :album_id=>3, :members_id=>5})
858
- yield({:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>4, :album_id=>3, :members_id=>6})
859
- yield({:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>5, :album_id=>3, :members_id=>5})
860
- yield({:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>5, :album_id=>3, :members_id=>6})
861
- yield({:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>6, :album_id=>4, :members_id=>5})
862
- yield({:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>6, :album_id=>4, :members_id=>6})
863
- yield({:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>7, :album_id=>4, :members_id=>5})
864
- yield({:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>7, :album_id=>4, :members_id=>6})
865
- yield({:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>8, :album_id=>5, :members_id=>5})
866
- yield({:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>8, :album_id=>5, :members_id=>6})
867
- yield({:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>9, :album_id=>5, :members_id=>5})
868
- yield({:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>9, :album_id=>5, :members_id=>6})
869
- yield({:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>1, :album_id=>6, :members_id=>5})
870
- yield({:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>1, :album_id=>6, :members_id=>6})
871
- yield({:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>2, :album_id=>6, :members_id=>5})
872
- yield({:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>2, :album_id=>6, :members_id=>6})
873
- end
860
+ ds._fetch = [{:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>4, :album_id=>3, :members_id=>5},
861
+ {:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>4, :album_id=>3, :members_id=>6},
862
+ {:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>5, :album_id=>3, :members_id=>5},
863
+ {:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>5, :album_id=>3, :members_id=>6},
864
+ {:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>6, :album_id=>4, :members_id=>5},
865
+ {:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>6, :album_id=>4, :members_id=>6},
866
+ {:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>7, :album_id=>4, :members_id=>5},
867
+ {:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>7, :album_id=>4, :members_id=>6},
868
+ {:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>8, :album_id=>5, :members_id=>5},
869
+ {:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>8, :album_id=>5, :members_id=>6},
870
+ {:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>9, :album_id=>5, :members_id=>5},
871
+ {:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>9, :album_id=>5, :members_id=>6},
872
+ {:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>1, :album_id=>6, :members_id=>5},
873
+ {:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>1, :album_id=>6, :members_id=>6},
874
+ {:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>2, :album_id=>6, :members_id=>5},
875
+ {:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>2, :album_id=>6, :members_id=>6}]
874
876
  a = ds.all
875
877
  a.should == [GraphBand.load(:id=>1, :vocalist_id=>2), GraphBand.load(:id=>2, :vocalist_id=>2)]
876
878
  members = a.map{|x| x.members}
@@ -885,85 +887,49 @@ describe Sequel::Model, "#eager_graph" do
885
887
  MODEL_DB.reset
886
888
  ds = GraphAlbum.eager_graph(:tracks)
887
889
  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)'
888
- def ds.fetch_rows(sql, &block)
889
- @db << sql
890
- yield({:id=>1, :band_id=>2, :tracks_id=>3, :album_id=>1})
891
- end
890
+ ds._fetch = {:id=>1, :band_id=>2, :tracks_id=>3, :album_id=>1}
892
891
  a = ds.all
893
- a.should be_a_kind_of(Array)
894
- a.size.should == 1
895
- a.first.should be_a_kind_of(GraphAlbum)
896
- a.first.values.should == {:id => 1, :band_id => 2}
892
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
897
893
  a = a.first
898
- a.tracks.should be_a_kind_of(Array)
899
- a.tracks.size.should == 1
900
- a.tracks.first.should be_a_kind_of(GraphTrack)
901
- a.tracks.first.values.should == {:id => 3, :album_id=>1}
902
- a.tracks.first.album.should be_a_kind_of(GraphAlbum)
894
+ a.tracks.should == [GraphTrack.load(:id => 3, :album_id=>1)]
903
895
  a.tracks.first.album.should == a
904
- MODEL_DB.sqls.length.should == 1
896
+ 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)']
905
897
  end
906
898
 
907
899
  it "should eager load multiple associations from the same table" do
908
900
  ds = GraphBand.eager_graph(:vocalist, :members)
909
901
  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)'
910
- def ds.fetch_rows(sql, &block)
911
- yield({:id=>2, :vocalist_id=>6, :vocalist_id_0=>6, :members_id=>5})
912
- end
902
+ ds._fetch = {:id=>2, :vocalist_id=>6, :vocalist_id_0=>6, :members_id=>5}
913
903
  a = ds.all
914
- a.should be_a_kind_of(Array)
915
- a.size.should == 1
916
- a.first.should be_a_kind_of(GraphBand)
917
- a.first.values.should == {:id => 2, :vocalist_id => 6}
904
+ a.should == [GraphBand.load(:id => 2, :vocalist_id => 6)]
918
905
  a = a.first
919
- a.vocalist.should be_a_kind_of(GraphBandMember)
920
- a.vocalist.values.should == {:id => 6}
921
- a.members.should be_a_kind_of(Array)
922
- a.members.size.should == 1
923
- a.members.first.should be_a_kind_of(GraphBandMember)
924
- a.members.first.values.should == {:id => 5}
906
+ a.vocalist.should == GraphBandMember.load(:id => 6)
907
+ a.members.should == [GraphBandMember.load(:id => 5)]
925
908
  end
926
909
 
927
- it "should give you a graph of tables when called without .all" do
910
+ it "should give you a plain hash when called without .all" do
928
911
  ds = GraphAlbum.eager_graph(:band)
929
912
  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)'
930
- def ds.fetch_rows(sql, &block)
931
- yield({:id=>1, :band_id=>2, :band_id_0=>2, :vocalist_id=>3})
932
- end
933
- ds.first.should == {:albums=>GraphAlbum.load(:id => 1, :band_id => 2), :band=>GraphBand.load(:id => 2, :vocalist_id=>3)}
913
+ ds._fetch = {:id=>1, :band_id=>2, :band_id_0=>2, :vocalist_id=>3}
914
+ ds.first.should == {:id=>1, :band_id=>2, :band_id_0=>2, :vocalist_id=>3}
934
915
  end
935
916
 
936
917
  it "should not drop any associated objects if the graph could not be a cartesian product" do
937
918
  ds = GraphBand.eager_graph(:members, :vocalist)
938
919
  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)'
939
- def ds.fetch_rows(sql, &block)
940
- yield({:id=>2, :vocalist_id=>6, :members_id=>5, :vocalist_id_0=>6})
941
- yield({:id=>2, :vocalist_id=>6, :members_id=>5, :vocalist_id_0=>6})
942
- end
920
+ ds._fetch = [{:id=>2, :vocalist_id=>6, :members_id=>5, :vocalist_id_0=>6}, {:id=>2, :vocalist_id=>6, :members_id=>5, :vocalist_id_0=>6}]
943
921
  a = ds.all
944
- a.should be_a_kind_of(Array)
945
- a.size.should == 1
946
- a.first.should be_a_kind_of(GraphBand)
947
- a.first.values.should == {:id => 2, :vocalist_id => 6}
922
+ a.should == [GraphBand.load(:id => 2, :vocalist_id => 6)]
948
923
  a = a.first
949
- a.vocalist.should be_a_kind_of(GraphBandMember)
950
- a.vocalist.values.should == {:id => 6}
951
- a.members.should be_a_kind_of(Array)
952
- a.members.size.should == 2
953
- a.members.first.should be_a_kind_of(GraphBandMember)
954
- a.members.first.values.should == {:id => 5}
955
- a.members.last.should be_a_kind_of(GraphBandMember)
956
- a.members.last.values.should == {:id => 5}
924
+ a.vocalist.should == GraphBandMember.load(:id => 6)
925
+ a.members.should == [GraphBandMember.load(:id => 5), GraphBandMember.load(:id => 5)]
957
926
  end
958
927
 
959
928
  it "should respect the :cartesian_product_number option" do
960
929
  GraphBand.many_to_one :other_vocalist, :class=>'GraphBandMember', :key=>:vocalist_id, :cartesian_product_number=>1
961
930
  ds = GraphBand.eager_graph(:members, :other_vocalist)
962
931
  ds.sql.should == 'SELECT bands.id, bands.vocalist_id, members.id AS members_id, other_vocalist.id AS other_vocalist_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 members AS other_vocalist ON (other_vocalist.id = bands.vocalist_id)'
963
- def ds.fetch_rows(sql, &block)
964
- yield({:id=>2, :vocalist_id=>6, :members_id=>5, :other_vocalist_id=>6})
965
- yield({:id=>2, :vocalist_id=>6, :members_id=>5, :other_vocalist_id=>6})
966
- end
932
+ ds._fetch = [{:id=>2, :vocalist_id=>6, :members_id=>5, :other_vocalist_id=>6}, {:id=>2, :vocalist_id=>6, :members_id=>5, :other_vocalist_id=>6}]
967
933
  a = ds.all
968
934
  a.should == [GraphBand.load(:id=>2, :vocalist_id => 6)]
969
935
  a.first.other_vocalist.should == GraphBandMember.load(:id=>6)
@@ -973,57 +939,28 @@ describe Sequel::Model, "#eager_graph" do
973
939
  it "should drop duplicate items that occur in sequence if the graph could be a cartesian product" do
974
940
  ds = GraphBand.eager_graph(:members, :genres)
975
941
  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)'
976
- def ds.fetch_rows(sql, &block)
977
- yield({:id=>2, :vocalist_id=>6, :members_id=>5, :genres_id=>7})
978
- yield({:id=>2, :vocalist_id=>6, :members_id=>5, :genres_id=>8})
979
- yield({:id=>2, :vocalist_id=>6, :members_id=>6, :genres_id=>7})
980
- yield({:id=>2, :vocalist_id=>6, :members_id=>6, :genres_id=>8})
981
- end
942
+ ds._fetch = [{:id=>2, :vocalist_id=>6, :members_id=>5, :genres_id=>7},
943
+ {:id=>2, :vocalist_id=>6, :members_id=>5, :genres_id=>8},
944
+ {:id=>2, :vocalist_id=>6, :members_id=>6, :genres_id=>7},
945
+ {:id=>2, :vocalist_id=>6, :members_id=>6, :genres_id=>8}]
982
946
  a = ds.all
983
- a.should be_a_kind_of(Array)
984
- a.size.should == 1
985
- a.first.should be_a_kind_of(GraphBand)
986
- a.first.values.should == {:id => 2, :vocalist_id => 6}
947
+ a.should == [GraphBand.load(:id => 2, :vocalist_id => 6)]
987
948
  a = a.first
988
- a.members.should be_a_kind_of(Array)
989
- a.members.size.should == 2
990
- a.members.first.should be_a_kind_of(GraphBandMember)
991
- a.members.first.values.should == {:id => 5}
992
- a.members.last.should be_a_kind_of(GraphBandMember)
993
- a.members.last.values.should == {:id => 6}
994
- a.genres.size.should == 2
995
- a.genres.first.should be_a_kind_of(GraphGenre)
996
- a.genres.first.values.should == {:id => 7}
997
- a.genres.last.should be_a_kind_of(GraphGenre)
998
- a.genres.last.values.should == {:id => 8}
949
+ a.members.should == [GraphBandMember.load(:id => 5), GraphBandMember.load(:id => 6)]
950
+ a.genres.should == [GraphGenre.load(:id => 7), GraphGenre.load(:id => 8)]
999
951
  end
1000
952
 
1001
953
  it "should be able to be used in combination with #eager" do
1002
954
  MODEL_DB.reset
1003
955
  ds = GraphAlbum.eager_graph(:tracks).eager(:genres)
1004
- def ds.fetch_rows(sql, &block)
1005
- @db << sql
1006
- yield({:id=>1, :band_id=>2, :tracks_id=>3, :album_id=>1})
1007
- end
956
+ ds._fetch = {:id=>1, :band_id=>2, :tracks_id=>3, :album_id=>1}
1008
957
  ds2 = GraphGenre.dataset
1009
- def ds2.fetch_rows(sql, &block)
1010
- @db << sql
1011
- yield({:id=>6, :x_foreign_key_x=>1})
1012
- end
958
+ ds2._fetch = {:id=>6, :x_foreign_key_x=>1}
1013
959
  a = ds.all
1014
- a.should be_a_kind_of(Array)
1015
- a.size.should == 1
1016
- a.first.should be_a_kind_of(GraphAlbum)
1017
- a.first.values.should == {:id => 1, :band_id => 2}
960
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1018
961
  a = a.first
1019
- a.tracks.should be_a_kind_of(Array)
1020
- a.tracks.size.should == 1
1021
- a.tracks.first.should be_a_kind_of(GraphTrack)
1022
- a.tracks.first.values.should == {:id=>3, :album_id=>1}
1023
- a.genres.should be_a_kind_of(Array)
1024
- a.genres.size.should == 1
1025
- a.genres.first.should be_a_kind_of(GraphGenre)
1026
- a.genres.first.values.should == {:id=>6}
962
+ a.tracks.should == [GraphTrack.load(:id=>3, :album_id=>1)]
963
+ a.genres.should == [GraphGenre.load(:id => 6)]
1027
964
  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)',
1028
965
  "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)))"]
1029
966
  end
@@ -1031,110 +968,65 @@ describe Sequel::Model, "#eager_graph" do
1031
968
  it "should handle no associated records for a single many_to_one association" do
1032
969
  ds = GraphAlbum.eager_graph(:band)
1033
970
  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)'
1034
- def ds.fetch_rows(sql, &block)
1035
- yield({:id=>1, :band_id=>2, :band_id_0=>nil, :vocalist_id=>nil})
1036
- end
971
+ ds._fetch = {:id=>1, :band_id=>2, :band_id_0=>nil, :vocalist_id=>nil}
1037
972
  a = ds.all
1038
- a.should be_a_kind_of(Array)
1039
- a.size.should == 1
1040
- a.first.should be_a_kind_of(GraphAlbum)
1041
- a.first.values.should == {:id => 1, :band_id => 2}
1042
- a.first.associations.fetch(:band, 2).should == nil
973
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
974
+ a.first.band.should == nil
1043
975
  end
1044
976
 
1045
977
  it "should handle no associated records for a single one_to_many association" do
1046
978
  ds = GraphAlbum.eager_graph(:tracks)
1047
979
  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)'
1048
- def ds.fetch_rows(sql, &block)
1049
- yield({:id=>1, :band_id=>2, :tracks_id=>nil, :album_id=>nil})
1050
- end
980
+ ds._fetch = {:id=>1, :band_id=>2, :tracks_id=>nil, :album_id=>nil}
1051
981
  a = ds.all
1052
- a.should be_a_kind_of(Array)
1053
- a.size.should == 1
1054
- a.first.should be_a_kind_of(GraphAlbum)
1055
- a.first.values.should == {:id => 1, :band_id => 2}
982
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1056
983
  a.first.tracks.should == []
1057
984
  end
1058
985
 
1059
986
  it "should handle no associated records for a single many_to_many association" do
1060
987
  ds = GraphAlbum.eager_graph(:genres)
1061
988
  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)'
1062
- def ds.fetch_rows(sql, &block)
1063
- yield({:id=>1, :band_id=>2, :genres_id=>nil})
1064
- end
989
+ ds._fetch = {:id=>1, :band_id=>2, :genres_id=>nil}
1065
990
  a = ds.all
1066
- a.should be_a_kind_of(Array)
1067
- a.size.should == 1
1068
- a.first.should be_a_kind_of(GraphAlbum)
1069
- a.first.values.should == {:id => 1, :band_id => 2}
991
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1070
992
  a.first.genres.should == []
1071
993
  end
1072
994
 
1073
995
  it "should handle missing associated records when loading multiple associations" do
1074
996
  ds = GraphAlbum.eager_graph(:genres, :tracks, :band)
1075
997
  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)'
1076
- def ds.fetch_rows(sql, &block)
1077
- yield({:id=>1, :band_id=>2, :genres_id=>nil, :tracks_id=>3, :album_id=>1, :band_id_0=>nil, :vocalist_id=>nil})
1078
- yield({:id=>1, :band_id=>2, :genres_id=>nil, :tracks_id=>4, :album_id=>1, :band_id_0=>nil, :vocalist_id=>nil})
1079
- yield({:id=>1, :band_id=>2, :genres_id=>nil, :tracks_id=>5, :album_id=>1, :band_id_0=>nil, :vocalist_id=>nil})
1080
- yield({:id=>1, :band_id=>2, :genres_id=>nil, :tracks_id=>6, :album_id=>1, :band_id_0=>nil, :vocalist_id=>nil})
1081
- end
998
+ ds._fetch = [{:id=>1, :band_id=>2, :genres_id=>nil, :tracks_id=>3, :album_id=>1, :band_id_0=>nil, :vocalist_id=>nil},
999
+ {:id=>1, :band_id=>2, :genres_id=>nil, :tracks_id=>4, :album_id=>1, :band_id_0=>nil, :vocalist_id=>nil},
1000
+ {:id=>1, :band_id=>2, :genres_id=>nil, :tracks_id=>5, :album_id=>1, :band_id_0=>nil, :vocalist_id=>nil},
1001
+ {:id=>1, :band_id=>2, :genres_id=>nil, :tracks_id=>6, :album_id=>1, :band_id_0=>nil, :vocalist_id=>nil}]
1082
1002
  a = ds.all
1083
- a.should be_a_kind_of(Array)
1084
- a.size.should == 1
1085
- a.first.should be_a_kind_of(GraphAlbum)
1086
- a.first.values.should == {:id => 1, :band_id => 2}
1003
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1087
1004
  a = a.first
1088
- a.tracks.should be_a_kind_of(Array)
1089
- a.tracks.size.should == 4
1090
- a.tracks.first.should be_a_kind_of(GraphTrack)
1091
- a.tracks.collect{|x|x[:id]}.should == [3,4,5,6]
1092
- a.associations.fetch(:band, 2).should == nil
1005
+ a.tracks.should == [GraphTrack.load(:id => 3, :album_id => 1), GraphTrack.load(:id => 4, :album_id => 1), GraphTrack.load(:id => 5, :album_id => 1), GraphTrack.load(:id => 6, :album_id => 1)]
1006
+ a.band.should == nil
1093
1007
  a.genres.should == []
1094
1008
  end
1095
1009
 
1096
1010
  it "should handle missing associated records when cascading eager loading for associations of associated models" do
1097
1011
  ds = GraphTrack.eager_graph(:album=>{:band=>:members})
1098
1012
  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)'
1099
- def ds.fetch_rows(sql, &block)
1100
- yield({:id=>2, :album_id=>2, :album_id_0=>nil, :band_id=>nil, :members_id=>nil, :band_id_0=>nil, :vocalist_id=>nil})
1101
- yield({:id=>3, :album_id=>3, :album_id_0=>3, :band_id=>3, :members_id=>nil, :band_id_0=>nil, :vocalist_id=>nil})
1102
- yield({:id=>4, :album_id=>4, :album_id_0=>4, :band_id=>2, :members_id=>nil, :band_id_0=>2, :vocalist_id=>6})
1103
- yield({:id=>5, :album_id=>1, :album_id_0=>1, :band_id=>4, :members_id=>5, :band_id_0=>4, :vocalist_id=>8})
1104
- yield({:id=>5, :album_id=>1, :album_id_0=>1, :band_id=>4, :members_id=>6, :band_id_0=>4, :vocalist_id=>8})
1105
- end
1013
+ ds._fetch = [{:id=>2, :album_id=>2, :album_id_0=>nil, :band_id=>nil, :members_id=>nil, :band_id_0=>nil, :vocalist_id=>nil},
1014
+ {:id=>3, :album_id=>3, :album_id_0=>3, :band_id=>3, :members_id=>nil, :band_id_0=>nil, :vocalist_id=>nil},
1015
+ {:id=>4, :album_id=>4, :album_id_0=>4, :band_id=>2, :members_id=>nil, :band_id_0=>2, :vocalist_id=>6},
1016
+ {:id=>5, :album_id=>1, :album_id_0=>1, :band_id=>4, :members_id=>5, :band_id_0=>4, :vocalist_id=>8},
1017
+ {:id=>5, :album_id=>1, :album_id_0=>1, :band_id=>4, :members_id=>6, :band_id_0=>4, :vocalist_id=>8}]
1106
1018
  a = ds.all
1107
- a.should be_a_kind_of(Array)
1108
- a.size.should == 4
1109
- a.first.should be_a_kind_of(GraphTrack)
1110
- a.collect{|x|x[:id]}.should == [2,3,4,5]
1111
- a[0].associations.fetch(:album, 2).should == nil
1112
- a[1].album.should be_a_kind_of(GraphAlbum)
1113
- a[1].album.values.should == {:id => 3, :band_id => 3}
1114
- a[1].album.associations.fetch(:band, 2).should == nil
1115
- a[2].album.should be_a_kind_of(GraphAlbum)
1116
- a[2].album.values.should == {:id => 4, :band_id => 2}
1117
- a[2].album.band.should be_a_kind_of(GraphBand)
1118
- a[2].album.band.values.should == {:id => 2, :vocalist_id=>6}
1119
- a[2].album.band.members.should == []
1120
- a[3].album.should be_a_kind_of(GraphAlbum)
1121
- a[3].album.values.should == {:id => 1, :band_id => 4}
1122
- a[3].album.band.should be_a_kind_of(GraphBand)
1123
- a[3].album.band.values.should == {:id => 4, :vocalist_id=>8}
1124
- a[3].album.band.members.size.should == 2
1125
- a[3].album.band.members.first.should be_a_kind_of(GraphBandMember)
1126
- a[3].album.band.members.first.values.should == {:id => 5}
1127
- a[3].album.band.members.last.should be_a_kind_of(GraphBandMember)
1128
- a[3].album.band.members.last.values.should == {:id => 6}
1019
+ a.should == [GraphTrack.load(:id => 2, :album_id => 2), GraphTrack.load(:id => 3, :album_id => 3), GraphTrack.load(:id => 4, :album_id => 4), GraphTrack.load(:id => 5, :album_id => 1)]
1020
+ a.map{|x| x.album}.should == [nil, GraphAlbum.load(:id => 3, :band_id => 3), GraphAlbum.load(:id => 4, :band_id => 2), GraphAlbum.load(:id => 1, :band_id => 4)]
1021
+ a.map{|x| x.album.band if x.album}.should == [nil, nil, GraphBand.load(:id => 2, :vocalist_id=>6), GraphBand.load(:id => 4, :vocalist_id=>8)]
1022
+ a.map{|x| x.album.band.members if x.album && x.album.band}.should == [nil, nil, [], [GraphBandMember.load(:id => 5), GraphBandMember.load(:id => 6)]]
1129
1023
  end
1130
1024
 
1131
1025
  it "should respect the association's :primary_key option" do
1132
1026
  GraphAlbum.many_to_one :inner_band, :class=>'GraphBand', :key=>:band_id, :primary_key=>:vocalist_id
1133
1027
  ds = GraphAlbum.eager_graph(:inner_band)
1134
1028
  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)'
1135
- def ds.fetch_rows(sql, &block)
1136
- yield({:id=>3, :band_id=>2, :inner_band_id=>5, :vocalist_id=>2})
1137
- end
1029
+ ds._fetch = {:id=>3, :band_id=>2, :inner_band_id=>5, :vocalist_id=>2}
1138
1030
  as = ds.all
1139
1031
  as.should == [GraphAlbum.load(:id=>3, :band_id=>2)]
1140
1032
  as.first.inner_band.should == GraphBand.load(:id=>5, :vocalist_id=>2)
@@ -1142,10 +1034,7 @@ describe Sequel::Model, "#eager_graph" do
1142
1034
  GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :primary_key=>:band_id
1143
1035
  ds = GraphAlbum.eager_graph(:right_tracks)
1144
1036
  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)'
1145
- def ds.fetch_rows(sql, &block)
1146
- yield({:id=>3, :band_id=>2, :right_tracks_id=>5, :album_id=>2})
1147
- yield({:id=>3, :band_id=>2, :right_tracks_id=>6, :album_id=>2})
1148
- end
1037
+ ds._fetch = [{:id=>3, :band_id=>2, :right_tracks_id=>5, :album_id=>2}, {:id=>3, :band_id=>2, :right_tracks_id=>6, :album_id=>2}]
1149
1038
  as = ds.all
1150
1039
  as.should == [GraphAlbum.load(:id=>3, :band_id=>2)]
1151
1040
  as.first.right_tracks.should == [GraphTrack.load(:id=>5, :album_id=>2), GraphTrack.load(:id=>6, :album_id=>2)]
@@ -1155,9 +1044,7 @@ describe Sequel::Model, "#eager_graph" do
1155
1044
  GraphAlbum.many_to_one :inner_band, :class=>'GraphBand', :key=>[:band_id, :id], :primary_key=>[:vocalist_id, :id]
1156
1045
  ds = GraphAlbum.eager_graph(:inner_band)
1157
1046
  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) AND (inner_band.id = albums.id))'
1158
- def ds.fetch_rows(sql, &block)
1159
- yield({:id=>3, :band_id=>2, :inner_band_id=>3, :vocalist_id=>2})
1160
- end
1047
+ ds._fetch = {:id=>3, :band_id=>2, :inner_band_id=>3, :vocalist_id=>2}
1161
1048
  as = ds.all
1162
1049
  as.should == [GraphAlbum.load(:id=>3, :band_id=>2)]
1163
1050
  as.first.inner_band.should == GraphBand.load(:id=>3, :vocalist_id=>2)
@@ -1167,9 +1054,7 @@ describe Sequel::Model, "#eager_graph" do
1167
1054
  GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>[:album_id, :id], :primary_key=>[:band_id, :id]
1168
1055
  ds = GraphAlbum.eager_graph(:right_tracks)
1169
1056
  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) AND (right_tracks.id = albums.id))'
1170
- def ds.fetch_rows(sql, &block)
1171
- yield({:id=>3, :band_id=>2, :right_tracks_id=>3, :album_id=>2})
1172
- end
1057
+ ds._fetch = {:id=>3, :band_id=>2, :right_tracks_id=>3, :album_id=>2}
1173
1058
  as = ds.all
1174
1059
  as.should == [GraphAlbum.load(:id=>3, :band_id=>2)]
1175
1060
  as.first.right_tracks.should == [GraphTrack.load(:id=>3, :album_id=>2)]
@@ -1179,10 +1064,7 @@ describe Sequel::Model, "#eager_graph" do
1179
1064
  GraphAlbum.many_to_many :sbands, :class=>'GraphBand', :left_key=>[:l1, :l2], :left_primary_key=>[:band_id, :id], :right_key=>[:r1, :r2], :right_primary_key=>[:vocalist_id, :id], :join_table=>:b
1180
1065
  ds = GraphAlbum.eager_graph(:sbands)
1181
1066
  ds.sql.should == 'SELECT albums.id, albums.band_id, sbands.id AS sbands_id, sbands.vocalist_id FROM albums LEFT OUTER JOIN b ON ((b.l1 = albums.band_id) AND (b.l2 = albums.id)) LEFT OUTER JOIN bands AS sbands ON ((sbands.vocalist_id = b.r1) AND (sbands.id = b.r2))'
1182
- def ds.fetch_rows(sql, &block)
1183
- yield({:id=>3, :band_id=>2, :sbands_id=>5, :vocalist_id=>6})
1184
- yield({:id=>3, :band_id=>2, :sbands_id=>6, :vocalist_id=>22})
1185
- end
1067
+ ds._fetch = [{:id=>3, :band_id=>2, :sbands_id=>5, :vocalist_id=>6}, {:id=>3, :band_id=>2, :sbands_id=>6, :vocalist_id=>22}]
1186
1068
  as = ds.all
1187
1069
  as.should == [GraphAlbum.load(:id=>3, :band_id=>2)]
1188
1070
  as.first.sbands.should == [GraphBand.load(:id=>5, :vocalist_id=>6), GraphBand.load(:id=>6, :vocalist_id=>22)]
@@ -1192,15 +1074,31 @@ describe Sequel::Model, "#eager_graph" do
1192
1074
  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
1193
1075
  ds = GraphAlbum.eager_graph(:inner_genres)
1194
1076
  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)'
1195
- def ds.fetch_rows(sql, &block)
1196
- yield({:id=>3, :band_id=>2, :inner_genres_id=>5, :xxx=>12})
1197
- yield({:id=>3, :band_id=>2, :inner_genres_id=>6, :xxx=>22})
1198
- end
1077
+ ds._fetch = [{:id=>3, :band_id=>2, :inner_genres_id=>5, :xxx=>12}, {:id=>3, :band_id=>2, :inner_genres_id=>6, :xxx=>22}]
1199
1078
  as = ds.all
1200
1079
  as.should == [GraphAlbum.load(:id=>3, :band_id=>2)]
1201
1080
  as.first.inner_genres.should == [GraphGenre.load(:id=>5), GraphGenre.load(:id=>6)]
1202
1081
  end
1203
1082
 
1083
+ it "should respect composite primary keys for classes when eager loading" do
1084
+ c1 = Class.new(GraphAlbum)
1085
+ c2 = Class.new(GraphBand)
1086
+ c1.set_primary_key [:band_id, :id]
1087
+ c2.set_primary_key [:vocalist_id, :id]
1088
+ c1.many_to_many :sbands, :class=>c2, :left_key=>[:l1, :l2], :right_key=>[:r1, :r2], :join_table=>:b
1089
+ c2.one_to_many :salbums, :class=>c1, :key=>[:band_id, :id]
1090
+ ds = c1.eager_graph(:sbands=>:salbums)
1091
+ ds.sql.should == 'SELECT albums.id, albums.band_id, sbands.id AS sbands_id, sbands.vocalist_id, salbums.id AS salbums_id, salbums.band_id AS salbums_band_id FROM albums LEFT OUTER JOIN b ON ((b.l1 = albums.band_id) AND (b.l2 = albums.id)) LEFT OUTER JOIN bands AS sbands ON ((sbands.vocalist_id = b.r1) AND (sbands.id = b.r2)) LEFT OUTER JOIN albums AS salbums ON ((salbums.band_id = sbands.vocalist_id) AND (salbums.id = sbands.id))'
1092
+ ds._fetch = [{:id=>3, :band_id=>2, :sbands_id=>5, :vocalist_id=>6, :salbums_id=>7, :salbums_band_id=>8},
1093
+ {:id=>3, :band_id=>2, :sbands_id=>5, :vocalist_id=>6, :salbums_id=>9, :salbums_band_id=>10},
1094
+ {:id=>3, :band_id=>2, :sbands_id=>6, :vocalist_id=>22, :salbums_id=>nil, :salbums_band_id=>nil},
1095
+ {:id=>7, :band_id=>8, :sbands_id=>nil, :vocalist_id=>nil, :salbums_id=>nil, :salbums_band_id=>nil}]
1096
+ as = ds.all
1097
+ as.should == [c1.load(:id=>3, :band_id=>2), c1.load(:id=>7, :band_id=>8)]
1098
+ as.map{|x| x.sbands}.should == [[c2.load(:id=>5, :vocalist_id=>6), c2.load(:id=>6, :vocalist_id=>22)], []]
1099
+ as.map{|x| x.sbands.map{|y| y.salbums}}.should == [[[c1.load(:id=>7, :band_id=>8), c1.load(:id=>9, :band_id=>10)], []], []]
1100
+ end
1101
+
1204
1102
  it "should respect the association's :graph_select option" do
1205
1103
  GraphAlbum.many_to_one :inner_band, :class=>'GraphBand', :key=>:band_id, :graph_select=>:vocalist_id
1206
1104
  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)'
@@ -1212,6 +1110,14 @@ describe Sequel::Model, "#eager_graph" do
1212
1110
  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)'
1213
1111
  end
1214
1112
 
1113
+ it "should respect the association's :graph_alias_base option" do
1114
+ GraphAlbum.many_to_one :inner_band, :class=>'GraphBand', :key=>:band_id, :graph_alias_base=>:foo
1115
+ ds = GraphAlbum.eager_graph(:inner_band)
1116
+ ds.sql.should == 'SELECT albums.id, albums.band_id, foo.id AS foo_id, foo.vocalist_id FROM albums LEFT OUTER JOIN bands AS foo ON (foo.id = albums.band_id)'
1117
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :graph_alias_base=>:foo
1118
+ ds.eager_graph(:right_tracks).sql.should == 'SELECT albums.id, albums.band_id, foo.id AS foo_id, foo.vocalist_id, foo_0.id AS foo_0_id, foo_0.album_id FROM albums LEFT OUTER JOIN bands AS foo ON (foo.id = albums.band_id) LEFT OUTER JOIN tracks AS foo_0 ON (foo_0.album_id = albums.id)'
1119
+ end
1120
+
1215
1121
  it "should respect the association's :graph_join_type option" do
1216
1122
  GraphAlbum.many_to_one :inner_band, :class=>'GraphBand', :key=>:band_id, :graph_join_type=>:inner
1217
1123
  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)'
@@ -1284,13 +1190,17 @@ describe Sequel::Model, "#eager_graph" do
1284
1190
  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)}
1285
1191
  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)"
1286
1192
 
1287
- 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)}
1193
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :eager_grapher=>proc{|eo| eo[:self].graph(GraphTrack, nil, :join_type=>:natural, :table_alias=>eo[:table_alias])}
1288
1194
  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'
1289
1195
 
1290
1196
  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)}
1291
1197
  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)"
1292
1198
  end
1293
1199
 
1200
+ it "should raise an error if you use an :eager_grapher proc with the wrong arity" do
1201
+ proc{GraphAlbum.many_to_one :special_band, :eager_grapher=>proc{|a, b|}}.should raise_error(Sequel::Error)
1202
+ end
1203
+
1294
1204
  it "should respect the association's :graph_only_conditions option" do
1295
1205
  GraphAlbum.many_to_one :active_band, :class=>'GraphBand', :key=>:band_id, :graph_only_conditions=>{:active=>true}
1296
1206
  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)"
@@ -1360,13 +1270,12 @@ describe Sequel::Model, "#eager_graph" do
1360
1270
  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
1361
1271
  ds = GraphAlbum.eager_graph(:inner_genres)
1362
1272
  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)'
1363
- def ds.fetch_rows(sql, &block)
1364
- yield({:id=>3, :band_id=>2, :inner_genres_id=>5, :xxx=>12})
1365
- yield({:id=>3, :band_id=>2, :inner_genres_id=>6, :xxx=>22})
1366
- end
1273
+ ds._fetch = [{:id=>3, :band_id=>2, :inner_genres_id=>5, :xxx=>12}, {:id=>3, :band_id=>2, :inner_genres_id=>6, :xxx=>22}]
1367
1274
  as = ds.all
1368
1275
  as.should == [GraphAlbum.load(:id=>3, :band_id=>2)]
1369
1276
  as.first.inner_genres.should == [GraphGenre.load(:id=>5), GraphGenre.load(:id=>6)]
1277
+ GraphAlbum.set_primary_key :id
1278
+ GraphGenre.set_primary_key :id
1370
1279
  end
1371
1280
 
1372
1281
  it "should handle eager loading with schemas and aliases of different types" do
@@ -1386,10 +1295,102 @@ describe Sequel::Model, "#eager_graph" do
1386
1295
  it "should eagerly load schema qualified tables correctly with joins" do
1387
1296
  c1 = Class.new(GraphAlbum)
1388
1297
  c2 = Class.new(GraphGenre)
1389
- c1.dataset = c1.dataset.from(:s__a)
1298
+ ds = c1.dataset = c1.dataset.from(:s__a)
1299
+ def ds.columns() [:id] end
1390
1300
  c2.dataset = c2.dataset.from(:s__g)
1391
1301
  c1.many_to_many :a_genres, :class=>c2, :left_primary_key=>:id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:s__ag
1392
1302
  ds = c1.join(:s__t, [:b_id]).eager_graph(:a_genres)
1393
1303
  ds.sql.should == 'SELECT a.id, a_genres.id AS a_genres_id FROM (SELECT * FROM s.a INNER JOIN s.t USING (b_id)) AS a LEFT OUTER JOIN s.ag AS ag ON (ag.album_id = a.id) LEFT OUTER JOIN s.g AS a_genres ON (a_genres.id = ag.genre_id)'
1394
1304
  end
1305
+
1306
+ it "should respect :after_load callbacks on associations when eager graphing" do
1307
+ GraphAlbum.many_to_one :al_band, :class=>GraphBand, :key=>:band_id, :after_load=>proc{|o, a| a.id *=2}
1308
+ GraphAlbum.one_to_many :al_tracks, :class=>GraphTrack, :key=>:album_id, :after_load=>proc{|o, os| os.each{|a| a.id *=2}}
1309
+ GraphAlbum.many_to_many :al_genres, :class=>GraphGenre, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :after_load=>proc{|o, os| os.each{|a| a.id *=2}}
1310
+ ds = GraphAlbum.eager_graph(:al_band, :al_tracks, :al_genres)
1311
+ 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)"
1312
+ 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}
1313
+ a = ds.all.first
1314
+ a.should == GraphAlbum.load(:id => 1, :band_id => 2)
1315
+ a.al_band.should == GraphBand.load(:id=>6, :vocalist_id=>4)
1316
+ a.al_tracks.should == [GraphTrack.load(:id=>10, :album_id=>6)]
1317
+ a.al_genres.should == [GraphGenre.load(:id=>14)]
1318
+ end
1319
+
1320
+ it "should respect limits on associations when eager graphing" do
1321
+ GraphAlbum.many_to_one :al_band, :class=>GraphBand, :key=>:band_id
1322
+ GraphAlbum.one_to_many :al_tracks, :class=>GraphTrack, :key=>:album_id, :limit=>2
1323
+ GraphAlbum.many_to_many :al_genres, :class=>GraphGenre, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :limit=>2
1324
+ ds = GraphAlbum.eager_graph(:al_band, :al_tracks, :al_genres)
1325
+ 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)"
1326
+ 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},
1327
+ {:id=>1, :band_id=>2, :al_band_id=>8, :vocalist_id=>9, :al_tracks_id=>10, :album_id=>11, :al_genres_id=>12},
1328
+ {:id=>1, :band_id=>2, :al_band_id=>13, :vocalist_id=>14, :al_tracks_id=>15, :album_id=>16, :al_genres_id=>17}]
1329
+ a = ds.all.first
1330
+ a.should == GraphAlbum.load(:id => 1, :band_id => 2)
1331
+ a.al_band.should == GraphBand.load(:id=>3, :vocalist_id=>4)
1332
+ a.al_tracks.should == [GraphTrack.load(:id=>5, :album_id=>6), GraphTrack.load(:id=>10, :album_id=>11)]
1333
+ a.al_genres.should == [GraphGenre.load(:id=>7), GraphGenre.load(:id=>12)]
1334
+ end
1335
+
1336
+ it "should eagerly load a many_to_one association with a custom callback" do
1337
+ ds = GraphAlbum.eager_graph(:band => proc {|ds| ds.select(:id).columns(:id)})
1338
+ 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)'
1339
+ ds._fetch = {:id=>1, :band_id=>2, :band_id_0=>2}
1340
+ a = ds.all
1341
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1342
+ a.first.band.should == GraphBand.load(:id => 2)
1343
+ end
1344
+
1345
+ it "should eagerly load a one_to_one association with a custom callback" do
1346
+ GraphAlbum.one_to_one :track, :class=>'GraphTrack', :key=>:album_id
1347
+ ds = GraphAlbum.eager_graph(:track => proc {|ds| ds.select(:album_id).columns(:album_id)})
1348
+ 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)'
1349
+ ds._fetch = {:id=>1, :band_id=>2, :album_id=>1}
1350
+ a = ds.all
1351
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1352
+ a.first.track.should == GraphTrack.load(:album_id=>1)
1353
+ end
1354
+
1355
+ it "should eagerly load a one_to_many association with a custom callback" do
1356
+ ds = GraphAlbum.eager_graph(:tracks => proc {|ds| ds.select(:album_id).columns(:album_id)})
1357
+ ds.sql.should == 'SELECT albums.id, albums.band_id, tracks.album_id FROM albums LEFT OUTER JOIN (SELECT album_id FROM tracks) AS tracks ON (tracks.album_id = albums.id)'
1358
+ ds._fetch = {:id=>1, :band_id=>2, :album_id=>1}
1359
+ a = ds.all
1360
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1361
+ a.first.tracks.should == [GraphTrack.load(:album_id=>1)]
1362
+ end
1363
+
1364
+ it "should eagerly load a many_to_many association with a custom callback" do
1365
+ ds = GraphAlbum.eager_graph(:genres => proc {|ds| ds.select(:id).columns(:id)})
1366
+ 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)'
1367
+ ds._fetch = {:id=>1, :band_id=>2, :genres_id=>4}
1368
+ a = ds.all
1369
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1370
+ a.first.genres.should == [GraphGenre.load(:id => 4)]
1371
+ end
1372
+
1373
+ it "should allow cascading of eager loading with a custom callback with hash value" do
1374
+ ds = GraphTrack.eager_graph(:album=>{proc{|ds| ds.select(:id, :band_id).columns(:id, :band_id)}=>{:band=>:members}})
1375
+ 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 (SELECT id, band_id FROM 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)'
1376
+ ds._fetch = {:id=>3, :album_id=>1, :album_id_0=>1, :band_id=>2, :members_id=>5, :band_id_0=>2, :vocalist_id=>6}
1377
+ a = ds.all
1378
+ a.should == [GraphTrack.load(:id => 3, :album_id => 1)]
1379
+ a = a.first
1380
+ a.album.should == GraphAlbum.load(:id => 1, :band_id => 2)
1381
+ a.album.band.should == GraphBand.load(:id => 2, :vocalist_id=>6)
1382
+ a.album.band.members.should == [GraphBandMember.load(:id => 5)]
1383
+ end
1384
+
1385
+ it "should allow cascading of eager loading with a custom callback with array value" do
1386
+ ds = GraphTrack.eager_graph(:album=>{proc{|ds| ds.select(:id, :band_id).columns(:id, :band_id)}=>[:band, :tracks]})
1387
+ 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, tracks_0.id AS tracks_0_id, tracks_0.album_id AS tracks_0_album_id FROM tracks LEFT OUTER JOIN (SELECT id, band_id FROM albums) AS album ON (album.id = tracks.album_id) LEFT OUTER JOIN bands AS band ON (band.id = album.band_id) LEFT OUTER JOIN tracks AS tracks_0 ON (tracks_0.album_id = album.id)'
1388
+ ds._fetch = {:id=>3, :album_id=>1, :album_id_0=>1, :band_id=>2, :band_id_0=>2, :vocalist_id=>6, :tracks_0_id=>3, :tracks_0_album_id=>1}
1389
+ a = ds.all
1390
+ a.should == [GraphTrack.load(:id => 3, :album_id => 1)]
1391
+ a = a.first
1392
+ a.album.should == GraphAlbum.load(:id => 1, :band_id => 2)
1393
+ a.album.band.should == GraphBand.load(:id => 2, :vocalist_id=>6)
1394
+ a.album.tracks.should == [GraphTrack.load(:id => 3, :album_id => 1)]
1395
+ end
1395
1396
  end