sequel 3.21.0 → 3.41.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 (363) hide show
  1. data/CHANGELOG +1131 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +106 -65
  4. data/Rakefile +59 -27
  5. data/bin/sequel +50 -26
  6. data/doc/active_record.rdoc +68 -52
  7. data/doc/advanced_associations.rdoc +201 -75
  8. data/doc/association_basics.rdoc +327 -79
  9. data/doc/bin_sequel.rdoc +144 -0
  10. data/doc/cheat_sheet.rdoc +21 -21
  11. data/doc/core_extensions.rdoc +374 -0
  12. data/doc/dataset_basics.rdoc +10 -10
  13. data/doc/dataset_filtering.rdoc +54 -42
  14. data/doc/mass_assignment.rdoc +56 -0
  15. data/doc/migration.rdoc +89 -557
  16. data/doc/model_hooks.rdoc +80 -29
  17. data/doc/object_model.rdoc +576 -0
  18. data/doc/opening_databases.rdoc +121 -53
  19. data/doc/prepared_statements.rdoc +41 -15
  20. data/doc/querying.rdoc +137 -56
  21. data/doc/reflection.rdoc +36 -10
  22. data/doc/release_notes/3.22.0.txt +39 -0
  23. data/doc/release_notes/3.23.0.txt +172 -0
  24. data/doc/release_notes/3.24.0.txt +420 -0
  25. data/doc/release_notes/3.25.0.txt +88 -0
  26. data/doc/release_notes/3.26.0.txt +88 -0
  27. data/doc/release_notes/3.27.0.txt +82 -0
  28. data/doc/release_notes/3.28.0.txt +304 -0
  29. data/doc/release_notes/3.29.0.txt +459 -0
  30. data/doc/release_notes/3.30.0.txt +135 -0
  31. data/doc/release_notes/3.31.0.txt +146 -0
  32. data/doc/release_notes/3.32.0.txt +202 -0
  33. data/doc/release_notes/3.33.0.txt +157 -0
  34. data/doc/release_notes/3.34.0.txt +671 -0
  35. data/doc/release_notes/3.35.0.txt +144 -0
  36. data/doc/release_notes/3.36.0.txt +245 -0
  37. data/doc/release_notes/3.37.0.txt +338 -0
  38. data/doc/release_notes/3.38.0.txt +234 -0
  39. data/doc/release_notes/3.39.0.txt +237 -0
  40. data/doc/release_notes/3.40.0.txt +73 -0
  41. data/doc/release_notes/3.41.0.txt +155 -0
  42. data/doc/schema_modification.rdoc +585 -0
  43. data/doc/sharding.rdoc +99 -8
  44. data/doc/sql.rdoc +154 -112
  45. data/doc/testing.rdoc +169 -0
  46. data/doc/thread_safety.rdoc +17 -0
  47. data/doc/transactions.rdoc +137 -0
  48. data/doc/validations.rdoc +1 -1
  49. data/doc/virtual_rows.rdoc +78 -43
  50. data/lib/sequel/adapters/ado/access.rb +334 -0
  51. data/lib/sequel/adapters/ado/mssql.rb +18 -12
  52. data/lib/sequel/adapters/ado.rb +69 -21
  53. data/lib/sequel/adapters/amalgalite.rb +17 -14
  54. data/lib/sequel/adapters/cubrid.rb +143 -0
  55. data/lib/sequel/adapters/db2.rb +178 -85
  56. data/lib/sequel/adapters/dbi.rb +19 -21
  57. data/lib/sequel/adapters/do/mysql.rb +17 -11
  58. data/lib/sequel/adapters/do/postgres.rb +2 -61
  59. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  60. data/lib/sequel/adapters/do.rb +20 -39
  61. data/lib/sequel/adapters/firebird.rb +33 -210
  62. data/lib/sequel/adapters/ibmdb.rb +452 -0
  63. data/lib/sequel/adapters/informix.rb +6 -29
  64. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  65. data/lib/sequel/adapters/jdbc/cubrid.rb +52 -0
  66. data/lib/sequel/adapters/jdbc/db2.rb +78 -0
  67. data/lib/sequel/adapters/jdbc/derby.rb +325 -0
  68. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  69. data/lib/sequel/adapters/jdbc/h2.rb +68 -31
  70. data/lib/sequel/adapters/jdbc/hsqldb.rb +189 -0
  71. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  72. data/lib/sequel/adapters/jdbc/jtds.rb +40 -0
  73. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  74. data/lib/sequel/adapters/jdbc/mysql.rb +16 -28
  75. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  76. data/lib/sequel/adapters/jdbc/postgresql.rb +77 -52
  77. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  78. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  79. data/lib/sequel/adapters/jdbc/sqlserver.rb +72 -0
  80. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  81. data/lib/sequel/adapters/jdbc.rb +308 -114
  82. data/lib/sequel/adapters/mock.rb +372 -0
  83. data/lib/sequel/adapters/mysql.rb +130 -230
  84. data/lib/sequel/adapters/mysql2.rb +64 -63
  85. data/lib/sequel/adapters/odbc/mssql.rb +17 -9
  86. data/lib/sequel/adapters/odbc.rb +29 -10
  87. data/lib/sequel/adapters/openbase.rb +7 -11
  88. data/lib/sequel/adapters/oracle.rb +364 -57
  89. data/lib/sequel/adapters/postgres.rb +379 -145
  90. data/lib/sequel/adapters/shared/access.rb +236 -11
  91. data/lib/sequel/adapters/shared/cubrid.rb +216 -0
  92. data/lib/sequel/adapters/shared/db2.rb +353 -0
  93. data/lib/sequel/adapters/shared/firebird.rb +231 -0
  94. data/lib/sequel/adapters/shared/informix.rb +53 -0
  95. data/lib/sequel/adapters/shared/mssql.rb +412 -116
  96. data/lib/sequel/adapters/shared/mysql.rb +396 -89
  97. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +166 -0
  98. data/lib/sequel/adapters/shared/oracle.rb +262 -45
  99. data/lib/sequel/adapters/shared/postgres.rb +688 -279
  100. data/lib/sequel/adapters/shared/progress.rb +5 -8
  101. data/lib/sequel/adapters/shared/sqlite.rb +317 -96
  102. data/lib/sequel/adapters/sqlite.rb +109 -46
  103. data/lib/sequel/adapters/swift/mysql.rb +10 -12
  104. data/lib/sequel/adapters/swift/postgres.rb +13 -79
  105. data/lib/sequel/adapters/swift/sqlite.rb +9 -6
  106. data/lib/sequel/adapters/swift.rb +31 -27
  107. data/lib/sequel/adapters/tinytds.rb +177 -25
  108. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +77 -0
  109. data/lib/sequel/adapters/utils/pg_types.rb +81 -0
  110. data/lib/sequel/adapters/utils/split_alter_table.rb +36 -0
  111. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  112. data/lib/sequel/ast_transformer.rb +194 -0
  113. data/lib/sequel/connection_pool/sharded_single.rb +23 -13
  114. data/lib/sequel/connection_pool/sharded_threaded.rb +64 -13
  115. data/lib/sequel/connection_pool/single.rb +11 -3
  116. data/lib/sequel/connection_pool/threaded.rb +69 -20
  117. data/lib/sequel/connection_pool.rb +20 -18
  118. data/lib/sequel/core.rb +143 -11
  119. data/lib/sequel/database/connecting.rb +66 -29
  120. data/lib/sequel/database/dataset.rb +4 -4
  121. data/lib/sequel/database/dataset_defaults.rb +63 -2
  122. data/lib/sequel/database/logging.rb +7 -2
  123. data/lib/sequel/database/misc.rb +192 -51
  124. data/lib/sequel/database/query.rb +299 -114
  125. data/lib/sequel/database/schema_generator.rb +103 -36
  126. data/lib/sequel/database/schema_methods.rb +310 -69
  127. data/lib/sequel/dataset/actions.rb +344 -90
  128. data/lib/sequel/dataset/features.rb +101 -8
  129. data/lib/sequel/dataset/graph.rb +42 -15
  130. data/lib/sequel/dataset/misc.rb +52 -25
  131. data/lib/sequel/dataset/mutation.rb +12 -7
  132. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  133. data/lib/sequel/dataset/query.rb +322 -68
  134. data/lib/sequel/dataset/sql.rb +716 -293
  135. data/lib/sequel/dataset.rb +8 -0
  136. data/lib/sequel/exceptions.rb +4 -0
  137. data/lib/sequel/extensions/_pretty_table.rb +83 -0
  138. data/lib/sequel/extensions/arbitrary_servers.rb +109 -0
  139. data/lib/sequel/extensions/blank.rb +4 -0
  140. data/lib/sequel/extensions/columns_introspection.rb +72 -0
  141. data/lib/sequel/extensions/connection_validator.rb +109 -0
  142. data/lib/sequel/extensions/constraint_validations.rb +451 -0
  143. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +30 -46
  144. data/lib/sequel/extensions/eval_inspect.rb +173 -0
  145. data/lib/sequel/extensions/inflector.rb +4 -0
  146. data/lib/sequel/extensions/looser_typecasting.rb +5 -4
  147. data/lib/sequel/extensions/migration.rb +100 -11
  148. data/lib/sequel/extensions/named_timezones.rb +9 -0
  149. data/lib/sequel/extensions/null_dataset.rb +94 -0
  150. data/lib/sequel/extensions/pagination.rb +4 -0
  151. data/lib/sequel/extensions/pg_array.rb +539 -0
  152. data/lib/sequel/extensions/pg_array_ops.rb +263 -0
  153. data/lib/sequel/extensions/pg_auto_parameterize.rb +175 -0
  154. data/lib/sequel/extensions/pg_hstore.rb +334 -0
  155. data/lib/sequel/extensions/pg_hstore_ops.rb +293 -0
  156. data/lib/sequel/extensions/pg_inet.rb +113 -0
  157. data/lib/sequel/extensions/pg_interval.rb +191 -0
  158. data/lib/sequel/extensions/pg_json.rb +212 -0
  159. data/lib/sequel/extensions/pg_range.rb +513 -0
  160. data/lib/sequel/extensions/pg_range_ops.rb +150 -0
  161. data/lib/sequel/extensions/pg_row.rb +576 -0
  162. data/lib/sequel/extensions/pg_row_ops.rb +182 -0
  163. data/lib/sequel/extensions/pg_statement_cache.rb +317 -0
  164. data/lib/sequel/extensions/pretty_table.rb +17 -72
  165. data/lib/sequel/extensions/query.rb +8 -4
  166. data/lib/sequel/extensions/query_literals.rb +79 -0
  167. data/lib/sequel/extensions/schema_caching.rb +76 -0
  168. data/lib/sequel/extensions/schema_dumper.rb +282 -76
  169. data/lib/sequel/extensions/select_remove.rb +39 -0
  170. data/lib/sequel/extensions/server_block.rb +140 -0
  171. data/lib/sequel/extensions/split_array_nil.rb +65 -0
  172. data/lib/sequel/extensions/sql_expr.rb +8 -110
  173. data/lib/sequel/extensions/string_date_time.rb +4 -0
  174. data/lib/sequel/extensions/thread_local_timezones.rb +10 -4
  175. data/lib/sequel/extensions/to_dot.rb +99 -83
  176. data/lib/sequel/model/associations.rb +1263 -409
  177. data/lib/sequel/model/base.rb +624 -171
  178. data/lib/sequel/model/errors.rb +1 -1
  179. data/lib/sequel/model/exceptions.rb +24 -2
  180. data/lib/sequel/model/inflections.rb +1 -1
  181. data/lib/sequel/model.rb +30 -11
  182. data/lib/sequel/no_core_ext.rb +2 -0
  183. data/lib/sequel/plugins/active_model.rb +13 -1
  184. data/lib/sequel/plugins/association_pks.rb +137 -16
  185. data/lib/sequel/plugins/caching.rb +25 -18
  186. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  187. data/lib/sequel/plugins/composition.rb +44 -12
  188. data/lib/sequel/plugins/constraint_validations.rb +198 -0
  189. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  190. data/lib/sequel/plugins/defaults_setter.rb +72 -0
  191. data/lib/sequel/plugins/dirty.rb +214 -0
  192. data/lib/sequel/plugins/eager_each.rb +59 -0
  193. data/lib/sequel/plugins/force_encoding.rb +6 -6
  194. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  195. data/lib/sequel/plugins/identity_map.rb +134 -15
  196. data/lib/sequel/plugins/instance_filters.rb +10 -0
  197. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  198. data/lib/sequel/plugins/json_serializer.rb +77 -6
  199. data/lib/sequel/plugins/list.rb +13 -2
  200. data/lib/sequel/plugins/many_through_many.rb +103 -51
  201. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  202. data/lib/sequel/plugins/nested_attributes.rb +150 -66
  203. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  204. data/lib/sequel/plugins/pg_row.rb +121 -0
  205. data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
  206. data/lib/sequel/plugins/prepared_statements.rb +168 -0
  207. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  208. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  209. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  210. data/lib/sequel/plugins/rcte_tree.rb +31 -17
  211. data/lib/sequel/plugins/schema.rb +8 -3
  212. data/lib/sequel/plugins/serialization.rb +98 -49
  213. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  214. data/lib/sequel/plugins/sharding.rb +21 -54
  215. data/lib/sequel/plugins/single_table_inheritance.rb +57 -12
  216. data/lib/sequel/plugins/static_cache.rb +99 -0
  217. data/lib/sequel/plugins/subclasses.rb +29 -3
  218. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  219. data/lib/sequel/plugins/timestamps.rb +1 -1
  220. data/lib/sequel/plugins/touch.rb +18 -6
  221. data/lib/sequel/plugins/tree.rb +3 -3
  222. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  223. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  224. data/lib/sequel/plugins/validation_class_methods.rb +2 -1
  225. data/lib/sequel/plugins/validation_helpers.rb +57 -4
  226. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  227. data/lib/sequel/sql.rb +721 -141
  228. data/lib/sequel/timezones.rb +67 -40
  229. data/lib/sequel/version.rb +1 -1
  230. data/spec/adapters/db2_spec.rb +146 -0
  231. data/spec/adapters/firebird_spec.rb +53 -39
  232. data/spec/adapters/mssql_spec.rb +261 -66
  233. data/spec/adapters/mysql_spec.rb +597 -352
  234. data/spec/adapters/oracle_spec.rb +92 -117
  235. data/spec/adapters/postgres_spec.rb +2103 -341
  236. data/spec/adapters/spec_helper.rb +16 -6
  237. data/spec/adapters/sqlite_spec.rb +237 -76
  238. data/spec/core/connection_pool_spec.rb +270 -141
  239. data/spec/core/database_spec.rb +980 -524
  240. data/spec/core/dataset_spec.rb +1658 -1243
  241. data/spec/core/expression_filters_spec.rb +741 -334
  242. data/spec/core/mock_adapter_spec.rb +453 -0
  243. data/spec/core/object_graph_spec.rb +88 -129
  244. data/spec/core/schema_generator_spec.rb +4 -4
  245. data/spec/core/schema_spec.rb +556 -53
  246. data/spec/core/spec_helper.rb +7 -48
  247. data/spec/core_extensions_spec.rb +626 -0
  248. data/spec/extensions/active_model_spec.rb +13 -0
  249. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  250. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  251. data/spec/extensions/association_dependencies_spec.rb +15 -25
  252. data/spec/extensions/association_pks_spec.rb +227 -33
  253. data/spec/extensions/association_proxies_spec.rb +4 -4
  254. data/spec/extensions/boolean_readers_spec.rb +25 -25
  255. data/spec/extensions/caching_spec.rb +47 -51
  256. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  257. data/spec/extensions/columns_introspection_spec.rb +91 -0
  258. data/spec/extensions/composition_spec.rb +18 -13
  259. data/spec/extensions/connection_validator_spec.rb +118 -0
  260. data/spec/extensions/constraint_validations_plugin_spec.rb +196 -0
  261. data/spec/extensions/constraint_validations_spec.rb +316 -0
  262. data/spec/extensions/dataset_associations_spec.rb +199 -0
  263. data/spec/extensions/defaults_setter_spec.rb +88 -0
  264. data/spec/extensions/dirty_spec.rb +155 -0
  265. data/spec/extensions/eager_each_spec.rb +34 -0
  266. data/spec/extensions/eval_inspect_spec.rb +67 -0
  267. data/spec/extensions/force_encoding_spec.rb +4 -2
  268. data/spec/extensions/hook_class_methods_spec.rb +97 -128
  269. data/spec/extensions/identity_map_spec.rb +142 -24
  270. data/spec/extensions/inflector_spec.rb +0 -4
  271. data/spec/extensions/instance_filters_spec.rb +11 -21
  272. data/spec/extensions/instance_hooks_spec.rb +72 -0
  273. data/spec/extensions/json_serializer_spec.rb +49 -12
  274. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  275. data/spec/extensions/list_spec.rb +49 -40
  276. data/spec/extensions/looser_typecasting_spec.rb +7 -7
  277. data/spec/extensions/many_through_many_spec.rb +368 -254
  278. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  279. data/spec/extensions/migration_spec.rb +140 -35
  280. data/spec/extensions/named_timezones_spec.rb +29 -11
  281. data/spec/extensions/nested_attributes_spec.rb +268 -89
  282. data/spec/extensions/null_dataset_spec.rb +85 -0
  283. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  284. data/spec/extensions/pg_array_ops_spec.rb +112 -0
  285. data/spec/extensions/pg_array_spec.rb +324 -0
  286. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  287. data/spec/extensions/pg_hstore_ops_spec.rb +140 -0
  288. data/spec/extensions/pg_hstore_spec.rb +200 -0
  289. data/spec/extensions/pg_inet_spec.rb +47 -0
  290. data/spec/extensions/pg_interval_spec.rb +72 -0
  291. data/spec/extensions/pg_json_spec.rb +99 -0
  292. data/spec/extensions/pg_range_ops_spec.rb +56 -0
  293. data/spec/extensions/pg_range_spec.rb +395 -0
  294. data/spec/extensions/pg_row_ops_spec.rb +58 -0
  295. data/spec/extensions/pg_row_plugin_spec.rb +49 -0
  296. data/spec/extensions/pg_row_spec.rb +340 -0
  297. data/spec/extensions/pg_statement_cache_spec.rb +208 -0
  298. data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
  299. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  300. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  301. data/spec/extensions/prepared_statements_spec.rb +87 -0
  302. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  303. data/spec/extensions/pretty_table_spec.rb +7 -6
  304. data/spec/extensions/query_literals_spec.rb +167 -0
  305. data/spec/extensions/query_spec.rb +9 -3
  306. data/spec/extensions/rcte_tree_spec.rb +50 -43
  307. data/spec/extensions/schema_caching_spec.rb +41 -0
  308. data/spec/extensions/schema_dumper_spec.rb +434 -49
  309. data/spec/extensions/schema_spec.rb +30 -49
  310. data/spec/extensions/select_remove_spec.rb +38 -0
  311. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  312. data/spec/extensions/serialization_spec.rb +86 -48
  313. data/spec/extensions/server_block_spec.rb +90 -0
  314. data/spec/extensions/sharding_spec.rb +69 -143
  315. data/spec/extensions/single_table_inheritance_spec.rb +65 -29
  316. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  317. data/spec/extensions/spec_helper.rb +34 -67
  318. data/spec/extensions/split_array_nil_spec.rb +24 -0
  319. data/spec/extensions/sql_expr_spec.rb +29 -60
  320. data/spec/extensions/static_cache_spec.rb +145 -0
  321. data/spec/extensions/subclasses_spec.rb +14 -0
  322. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  323. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  324. data/spec/extensions/timestamps_spec.rb +6 -6
  325. data/spec/extensions/to_dot_spec.rb +8 -11
  326. data/spec/extensions/touch_spec.rb +53 -14
  327. data/spec/extensions/tree_spec.rb +11 -26
  328. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  329. data/spec/extensions/update_primary_key_spec.rb +30 -24
  330. data/spec/extensions/validation_class_methods_spec.rb +77 -68
  331. data/spec/extensions/validation_helpers_spec.rb +48 -37
  332. data/spec/extensions/xml_serializer_spec.rb +29 -4
  333. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  334. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  335. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  336. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  337. data/spec/integration/associations_test.rb +659 -21
  338. data/spec/integration/database_test.rb +25 -5
  339. data/spec/integration/dataset_test.rb +729 -141
  340. data/spec/integration/eager_loader_test.rb +38 -54
  341. data/spec/integration/migrator_test.rb +2 -3
  342. data/spec/integration/model_test.rb +81 -2
  343. data/spec/integration/plugin_test.rb +871 -160
  344. data/spec/integration/prepared_statement_test.rb +272 -128
  345. data/spec/integration/schema_test.rb +377 -57
  346. data/spec/integration/spec_helper.rb +49 -32
  347. data/spec/integration/timezone_test.rb +38 -12
  348. data/spec/integration/transaction_test.rb +183 -8
  349. data/spec/integration/type_test.rb +40 -8
  350. data/spec/model/association_reflection_spec.rb +207 -8
  351. data/spec/model/associations_spec.rb +1206 -693
  352. data/spec/model/base_spec.rb +334 -100
  353. data/spec/model/dataset_methods_spec.rb +45 -27
  354. data/spec/model/eager_loading_spec.rb +828 -692
  355. data/spec/model/hooks_spec.rb +355 -67
  356. data/spec/model/model_spec.rb +251 -179
  357. data/spec/model/plugins_spec.rb +24 -13
  358. data/spec/model/record_spec.rb +506 -232
  359. data/spec/model/spec_helper.rb +16 -77
  360. data/spec/model/validations_spec.rb +38 -2
  361. data/spec/rcov.opts +2 -0
  362. metadata +223 -81
  363. data/spec/core/core_sql_spec.rb +0 -451
@@ -14,7 +14,7 @@ a different block when eager loading via <tt>Dataset#eager</tt>. Association blo
14
14
  useful for things like:
15
15
 
16
16
  Artist.one_to_many :gold_albums, :class=>:Album do |ds|
17
- ds.filter{copies_sold > 500000}
17
+ ds.where{copies_sold > 500000}
18
18
  end
19
19
 
20
20
  There are a whole bunch of options for changing how the association is eagerly
@@ -50,12 +50,12 @@ These can be used like this:
50
50
 
51
51
  # Only returns albums that have sold more than 500,000 copies
52
52
  Artist.one_to_many :gold_albums, :class=>:Album, \
53
- :graph_block=>proc{|j,lj,js| :copies_sold.qualify(j) > 500000}
53
+ :graph_block=>proc{|j,lj,js| Sequel.qualify(j, :copies_sold) > 500000}
54
54
 
55
55
  # Handles the case where the tables are associated by a case insensitive name string
56
56
  Artist.one_to_many :albums, :key=>:artist_name, \
57
57
  :graph_only_conditions=>nil, \
58
- :graph_block=>proc{|j,lj,js| {:lower.sql_function(artist_name.qualify(j))=>:lower.sql_function(name.qualify(lj))}}
58
+ :graph_block=>proc{|j,lj,js| {Sequel.function(:lower, Sequel.qualify(j, :artist_name))=>Sequel.function(:lower, Sequel.qualify(lj, :name))}}
59
59
 
60
60
  # Handles the case where both key columns have the name artist_name, and you want to use
61
61
  # a JOIN USING
@@ -65,6 +65,18 @@ Remember, using +eager_graph+ is generally only necessary when you need to
65
65
  filter/order based on columns in an associated table, it is recommended to
66
66
  use +eager+ for eager loading if possible.
67
67
 
68
+ One advantage of using +eager_graph+ is that you can easily filter/order
69
+ on columns in an associated table on a per-query basis, using regular
70
+ Sequel dataset methods. For example, if you only want to retrieve artists
71
+ who have albums that start with A, and eager load just those albums,
72
+ ordered by the albums name, you can do:
73
+
74
+ albums = Artist.
75
+ eager_graph(:albums).
76
+ where(Sequel.like(:albums__name, 'A%')).
77
+ order(:albums__name).
78
+ all
79
+
68
80
  For lazy loading (e.g. Model[1].association), the <tt>:dataset</tt> option can be used
69
81
  to specify an arbitrary dataset (one that uses different keys, multiple keys,
70
82
  joins to other tables, etc.).
@@ -75,19 +87,15 @@ option. Though it can often be verbose (compared to other things in Sequel),
75
87
  it allows you complete control over how to eagerly load associations for a
76
88
  group of objects.
77
89
 
78
- :eager_loader should be a proc that takes 1 or 3 arguments. If the proc
79
- takes one argument, it will be given a hash with the following keys:
90
+ :eager_loader should be a proc that takes a single hash argument, which will
91
+ have at least the following keys:
80
92
 
81
- :key_hash :: A key_hash, described below
93
+ :id_map :: A mapping of key values to arrays of current model instances,
94
+ usage described below
82
95
  :rows :: An array of model objects
83
96
  :associations :: A hash of dependent associations to eagerly load
84
97
  :self :: The dataset that is doing the eager loading
85
-
86
- If the proc takes three arguments, it gets passed the :key_hash, :rows,
87
- and :associations values. The only way to get the :self value is to
88
- accept one argument. The 3 argument procs are allowed for backwards
89
- compatibility, and it is recommended to use the 1 argument proc format
90
- for new code.
98
+ :eager_block :: A dynamic callback for this eager load.
91
99
 
92
100
  Since you are given all of the records, you can do things like filter on
93
101
  associations that are specified by multiple keys, or do multiple
@@ -96,25 +104,141 @@ necessary for polymorphic associations). Inside the <tt>:eager_loader</tt>
96
104
  proc, you should get the related objects and populate the
97
105
  associations cache for all objects in the array of records. The hash
98
106
  of dependent associations is available for you to cascade the eager
99
- loading down multiple levels, but it is up to you to use it. The
100
- key_hash is a performance enhancement that is used by the default
101
- association loaders and is also available to you. It is a hash with keys being
102
- foreign/primary key symbols in the current table, and the values
103
- being hashes where the key is foreign/primary key values and values
104
- being arrays of current model objects having the foreign/primary key
105
- value associated with the key. This is hard to visualize, so I'll
106
- give an example:
107
-
108
- album1 = Album.load(:id=>1, :artist_id=>2)
109
- album2 = Album.load(:id=>3, :artist_id=>2)
107
+ loading down multiple levels, but it is up to you to use it.
108
+
109
+ The id_map is a performance enhancement that is used by the default
110
+ association loaders and is also available to you. It is a hash with keys
111
+ foreign/primary key values, and values being arrays of current model
112
+ objects having the foreign/primary key value associated with the key.
113
+ This may be hard to visualize, so I'll give an example. Let's say you
114
+ have the following associations
115
+
110
116
  Album.many_to_one :artist
111
117
  Album.one_to_many :tracks
112
- Album.eager(:band, :tracks).all
113
- # The key_hash provided to the :eager_loader proc would be:
114
- {:id=>{1=>[album1], 3=>[album2]}, :artist_id=>{2=>[album1, album2]}}
118
+
119
+ and the following three albums in the database:
120
+
121
+ album1 = Album.create(:artist_id=>3) # id: 1
122
+ album2 = Album.create(:artist_id=>3) # id: 2
123
+ album3 = Album.create(:artist_id=>2) # id: 3
124
+
125
+ If you try to eager load this dataset:
126
+
127
+ Album.eager(:artist, :tracks).all
128
+
129
+ Then the id_map provided to the artist :eager_loader proc would be:
130
+
131
+ {3=>[album1, album2], 2=>[album3]}
132
+
133
+ The artist id_map contains a mapping of artist_id values to arrays of
134
+ album objects. Since both album1 and album2 have the same artist_id,
135
+ the are both in the array related to that key. album3 has a different
136
+ artist_id, so it is in a different array. Eager loading of artists is
137
+ done by looking for any artist having one of the keys in the hash:
138
+
139
+ artists = Artist.where(:id=>id_map.keys).all
140
+
141
+ When the artists are retrieved, you can iterate over them, find entries
142
+ with matching keys, and manually associate them to the albums:
143
+
144
+ artists.each do |artist|
145
+ # Find related albums using the artist_id_map
146
+ if albums = id_map[artist.id]
147
+ # Iterate over the albums
148
+ albums.each do |album|
149
+ # Manually set the artist association for each album
150
+ album.associations[:artist] = artist
151
+ end
152
+ end
153
+ end
154
+
155
+ The id_map provided to the tracks :eager_loader proc would be:
156
+
157
+ {1=>[album1], 2=>[album2], 3=>[album3]}
158
+
159
+ Now the id_map contains a mapping of id values to arrays of album objects (in this
160
+ case each array only has a single object, because id is the primary key). So when
161
+ looking for tracks to eagerly load, you only need to look for ones that have an
162
+ album_id with one of the keys in the hash:
163
+
164
+ tracks = Track.where(:album_id=>id_map.keys).all
165
+
166
+ When the tracks are retrieved, you can iterate over them, find entries with matching
167
+ keys, and manually associate them to the albums:
168
+
169
+ tracks.each do |track|
170
+ if albums = id_map[track.album_id]
171
+ albums.each do |album|
172
+ album.associations[:tracks] << track
173
+ end
174
+ end
175
+ end
176
+
177
+ === Two basic example eager loaders
115
178
 
116
- Using these options, you can build associations that Sequel doesn't natively support,
117
- and still be able to use the same eager loading features that you are used to.
179
+ Putting the code in the above examples together, you almost have enough for a basic
180
+ working eager loader. The main important thing that is missing is you need to set
181
+ initial values for the eagerly loaded associations. For the artist association, you
182
+ need to initial the values to nil:
183
+
184
+ # rows here is the :rows entry in the hash passed to the eager loader
185
+ rows.each{|album| album.associations[:artist] = nil}
186
+
187
+ For the tracks association, you set the initial value to an empty array:
188
+
189
+ rows.each{|album| album.associations[:track] = []}
190
+
191
+ These are done so that if an album currently being loaded doesn't have an associated
192
+ artist or any associated tracks, the lack of them will be cached, so calling the
193
+ artist or tracks method on the album will not do another database lookup.
194
+
195
+ So putting everything together, the artist eager loader looks like:
196
+
197
+ :eager_loader=>(proc do |eo_opts|
198
+ eo_opts[:rows].each{|album| album.associations[:artist] = nil}
199
+ id_map = eo_opts[:id_map]
200
+ Artist.where(:id=>id_map.keys).all do |artist|
201
+ if albums = id_map[artist.id]
202
+ albums.each do |album|
203
+ album.associations[:artist] = artist
204
+ end
205
+ end
206
+ end
207
+ end)
208
+
209
+ and the tracks eager loader looks like:
210
+
211
+ :eager_loader=>(proc do |eo_opts|
212
+ eo_opts[:rows].each{|album| album.associations[:tracks] = []}
213
+ id_map = eo_opts[:id_map]
214
+ Track.where(:id=>id_map.keys).all do |tracks|
215
+ if albums = id_map[track.album_id]
216
+ albums.each do |album|
217
+ album.associations[:tracks] << track
218
+ end
219
+ end
220
+ end
221
+ end)
222
+
223
+ Now, these are both overly simplistic eager loaders that don't respect cascaded
224
+ associations or any of the association options. But hopefully they both
225
+ provide simple examples that you can more easily build and learn from, as
226
+ the custom eager loaders described later in this page are more complex.
227
+
228
+ Basically, the eager loading steps can be broken down into:
229
+
230
+ 1. Set default association values (nil/[]) for each of the current objects
231
+ 2. Return just related associated objects by filtering the associated class
232
+ to include only rows with keys present in the id_map.
233
+ 3. Iterating over the returned associated objects, indexing into the id_map
234
+ using the foreign/primary key value in the associated object to get
235
+ current values associated to that specific object.
236
+ 4. For each of those current values, updating the cached association value to
237
+ include that specific object.
238
+
239
+ Using the :eager_loader proc, you should be able to eagerly load all associations
240
+ that can be eagerly loaded, even if Sequel doesn't natively support such eager
241
+ loading.
118
242
 
119
243
  == ActiveRecord associations
120
244
 
@@ -123,9 +247,9 @@ require different approaches or custom <tt>:eager_loader</tt> options.
123
247
 
124
248
  === Association callbacks
125
249
 
126
- Sequel supports the same callbacks that ActiveRecord does on +one_to_many+ and
250
+ Sequel supports the same callbacks that ActiveRecord does for +one_to_many+ and
127
251
  +many_to_many+ associations: <tt>:before_add</tt>, <tt>:before_remove</tt>, <tt>:after_add</tt>, and
128
- <tt>:after_remove</tt>. One +many_to_one+ associations and +one_to_one+ associations, Sequel
252
+ <tt>:after_remove</tt>. For +many_to_one+ associations and +one_to_one+ associations, Sequel
129
253
  supports the <tt>:before_set</tt> and <tt>:after_set</tt> callbacks. On all associations,
130
254
  Sequel supports <tt>:after_load</tt>, which is called after the association has been
131
255
  loaded.
@@ -148,7 +272,7 @@ otherwise modified:
148
272
  class Author < Sequel::Model
149
273
  one_to_many :authorships
150
274
  end
151
- Author.first.authorships_dataset.filter{|o| o.number < 10}.first
275
+ Author.first.authorships_dataset.where{number < 10}.first
152
276
 
153
277
  You can extend a dataset with a module using the <tt>:extend</tt> association option. You can reference
154
278
  the model object that created the association dataset via the dataset's
@@ -237,6 +361,23 @@ Sequel::Model:
237
361
 
238
362
  class Invoice < Sequel::Model
239
363
  many_to_one :client
364
+
365
+ # has_one :through equivalent 1
366
+ # eager load with :eager=>:firm option on :client association, and eager loading :client
367
+ def firm
368
+ client.firm if client
369
+ end
370
+
371
+ # has_one :through equivalent 2
372
+ # eager load the usual way
373
+ many_to_many :firms, :join_table=>:clients, :left_key=>:id, :left_primary_key=>:client_id, :right_key=>:firm_id
374
+ def firm
375
+ firms.first
376
+ end
377
+
378
+ # has_one :through equivalent 3
379
+ # eager loading requires custom :eager_loader proc
380
+ many_to_one :firm, :dataset=>proc{Firm.join(:clients, :firm_id=>:id, :id=>client_id).select_all(:firms)}
240
381
  end
241
382
 
242
383
  Firm.first.invoices
@@ -252,7 +393,7 @@ Polymorphic associations break referential integrity and are significantly more
252
393
  complex than non-polymorphic associations, so their use is not recommended unless
253
394
  you are stuck with an existing design that uses them.
254
395
 
255
- If you must use them, look for the sequel_polymorphic plugin, as it makes using
396
+ If you must use them, look for the sequel_polymorphic external plugin, as it makes using
256
397
  polymorphic associations in Sequel about as easy as it is in ActiveRecord. However,
257
398
  here's how they can be done using Sequel's custom associations (the sequel_polymorphic
258
399
  plugin is just a generic version of this code):
@@ -280,7 +421,7 @@ Sequel::Model:
280
421
  many_to_one :attachable, :reciprocal=>:assets, \
281
422
  :dataset=>(proc do
282
423
  klass = attachable_type.constantize
283
- klass.filter(klass.primary_key=>attachable_id)
424
+ klass.where(klass.primary_key=>attachable_id)
284
425
  end), \
285
426
  :eager_loader=>(proc do |eo|
286
427
  id_map = {}
@@ -290,7 +431,7 @@ Sequel::Model:
290
431
  end
291
432
  id_map.each do |klass_name, id_map|
292
433
  klass = klass_name.constantize
293
- klass.filter(klass.primary_key=>id_map.keys).all do |attach|
434
+ klass.where(klass.primary_key=>id_map.keys).all do |attach|
294
435
  id_map[attach.pk].each do |asset|
295
436
  asset.associations[:attachable] = attach
296
437
  end
@@ -307,48 +448,34 @@ Sequel::Model:
307
448
  end
308
449
 
309
450
  class Post < Sequel::Model
310
- one_to_many :assets, :key=>:attachable_id do |ds|
311
- ds.filter(:attachable_type=>'Post')
312
- end
451
+ one_to_many :assets, :key=>:attachable_id, :reciprocal=>:attachable, :conditions=>{:attachable_type=>'Post'}
313
452
 
314
453
  private
315
454
 
316
455
  def _add_asset(asset)
317
- asset.attachable_id = pk
318
- asset.attachable_type = 'Post'
319
- asset.save
320
- end
456
+ asset.update(:attachable_id=>pk, :attachable_type=>'Post')
457
+ end
321
458
  def _remove_asset(asset)
322
- asset.attachable_id = nil
323
- asset.attachable_type = nil
324
- asset.save
325
- end
459
+ asset.update(:attachable_id=>nil, :attachable_type=>nil)
460
+ end
326
461
  def _remove_all_assets
327
- Asset.filter(:attachable_id=>pk, :attachable_type=>'Post')\
328
- .update(:attachable_id=>nil, :attachable_type=>nil)
462
+ assets_dataset.update(:attachable_id=>nil, :attachable_type=>nil)
329
463
  end
330
464
  end
331
465
 
332
466
  class Note < Sequel::Model
333
- one_to_many :assets, :key=>:attachable_id do |ds|
334
- ds.filter(:attachable_type=>'Note')
335
- end
467
+ one_to_many :assets, :key=>:attachable_id, :reciprocal=>:attachable, :conditions=>{:attachable_type=>'Note'}
336
468
 
337
469
  private
338
470
 
339
471
  def _add_asset(asset)
340
- asset.attachable_id = pk
341
- asset.attachable_type = 'Note'
342
- asset.save
472
+ asset.update(:attachable_id=>pk, :attachable_type=>'Note')
343
473
  end
344
474
  def _remove_asset(asset)
345
- asset.attachable_id = nil
346
- asset.attachable_type = nil
347
- asset.save
475
+ asset.update(:attachable_id=>nil, :attachable_type=>nil)
348
476
  end
349
477
  def _remove_all_assets
350
- Asset.filter(:attachable_id=>pk, :attachable_type=>'Note')\
351
- .update(:attachable_id=>nil, :attachable_type=>nil)
478
+ assets_dataset.update(:attachable_id=>nil, :attachable_type=>nil)
352
479
  end
353
480
  end
354
481
 
@@ -390,7 +517,7 @@ node.children. You can even eager load the relationship up to a certain depth:
390
517
  # Eager load three generations of generations of children for a given node
391
518
  Node.filter(:id=>1).eager(:children=>{:children=>:children}).all.first
392
519
  # Load parents and grandparents for a group of nodes
393
- Node.filter{|o| o.id < 10}.eager(:parent=>:parent).all
520
+ Node.filter{id < 10}.eager(:parent=>:parent).all
394
521
 
395
522
  What if you want to get all ancestors up to the root node, or all descendents,
396
523
  without knowing the depth of the tree?
@@ -415,7 +542,7 @@ without knowing the depth of the tree?
415
542
  non_root_nodes.each{|n| (id_map[n.parent_id] ||= []) << n}
416
543
  # Doesn't cause an infinte loop, because when only the root node
417
544
  # is left, this is not called.
418
- Node.filter(Node.primary_key=>id_map.keys).eager(:ancestors).all do |node|
545
+ Node.where(Node.primary_key=>id_map.keys).eager(:ancestors).all do |node|
419
546
  # Populate the parent association for each node
420
547
  id_map[node.pk].each{|n| n.associations[:parent] = node}
421
548
  end
@@ -433,7 +560,7 @@ without knowing the depth of the tree?
433
560
  # if no records are returned. Exclude id = parent_id to avoid infinite loop
434
561
  # if the root note is one of the returned records and it has parent_id = id
435
562
  # instead of parent_id = NULL.
436
- Node.filter(:parent_id=>id_map.keys).exclude(:id=>:parent_id).eager(:descendants).all do |node|
563
+ Node.where(:parent_id=>id_map.keys).exclude(:id=>:parent_id).eager(:descendants).all do |node|
437
564
  # Get the parent from the identity map
438
565
  parent = id_map[node.parent_id]
439
566
  # Set the child's parent association to the parent
@@ -456,7 +583,7 @@ this easy:
456
583
 
457
584
  === Joining multiple keys to a single key, through a third table
458
585
 
459
- Let's say you have a database, of songs, lyrics, and artists. Each song
586
+ Let's say you have a database of songs, lyrics, and artists. Each song
460
587
  may or may not have a lyric (most songs are instrumental). The lyric can be
461
588
  associated to an artist in each of four ways: composer, arranger, vocalist,
462
589
  or lyricist. These may all be the same, or they could all be different, and
@@ -469,14 +596,15 @@ name, with no duplicates?
469
596
 
470
597
  class Artist < Sequel::Model
471
598
  one_to_many :songs, :order=>:songs__name, \
472
- :dataset=>proc{Song.select(:songs.*).join(Lyric, :id=>:lyric_id, id=>[:composer_id, :arranger_id, :vocalist_id, :lyricist_id])}, \
599
+ :dataset=>proc{Song.select_all(:songs).join(Lyric, :id=>:lyric_id, id=>[:composer_id, :arranger_id, :vocalist_id, :lyricist_id])}, \
473
600
  :eager_loader=>(proc do |eo|
474
- h = eo[:key_hash][:id]
601
+ h = eo[:id_map]
475
602
  ids = h.keys
476
603
  eo[:rows].each{|r| r.associations[:songs] = []}
477
- Song.select(:songs.*, :lyrics__composer_id, :lyrics__arranger_id, :lyrics__vocalist_id, :lyrics__lyricist_id)\
478
- .join(Lyric, :id=>:lyric_id){{:composer_id=>ids, :arranger_id=>ids, :vocalist_id=>ids, :lyricist_id=>ids}.sql_or}\
479
- .order(:songs__name).all do |song|
604
+ Song.select_all(:songs).
605
+ select_append(:lyrics__composer_id, :lyrics__arranger_id, :lyrics__vocalist_id, :lyrics__lyricist_id).
606
+ join(Lyric, :id=>:lyric_id){Sequel.or(:composer_id=>ids, :arranger_id=>ids, :vocalist_id=>ids, :lyricist_id=>ids)}.
607
+ order(:songs__name).all do |song|
480
608
  [:composer_id, :arranger_id, :vocalist_id, :lyricist_id].each do |x|
481
609
  recs = h[song.values.delete(x)]
482
610
  recs.each{|r| r.associations[:songs] << song} if recs
@@ -499,14 +627,14 @@ associated tickets.
499
627
  class Project < Sequel::Model
500
628
  one_to_many :tickets
501
629
  many_to_one :ticket_hours, :read_only=>true, :key=>:id,
502
- :dataset=>proc{Ticket.filter(:project_id=>id).select{sum(hours).as(hours)}},
630
+ :dataset=>proc{Ticket.where(:project_id=>id).select{sum(hours).as(hours)}},
503
631
  :eager_loader=>(proc do |eo|
504
632
  eo[:rows].each{|p| p.associations[:ticket_hours] = nil}
505
- Ticket.filter(:project_id=>eo[:key_hash][:id].keys).
506
- group(:project_id).
507
- select{[project_id, sum(hours).as(hours)]}.
633
+ Ticket.where(:project_id=>eo[:id_map].keys).
634
+ select_group(:project_id).
635
+ select_append{sum(hours).as(hours)}.
508
636
  all do |t|
509
- p = eo[:key_hash][:id][t.values.delete(:project_id)].first
637
+ p = eo[:id_map][t.values.delete(:project_id)].first
510
638
  p.associations[:ticket_hours] = t
511
639
  end
512
640
  end)
@@ -525,6 +653,4 @@ associated tickets.
525
653
  end
526
654
 
527
655
  Note that it is often better to use a sum cache instead of this approach. You can implement
528
- a sum cache using +after_create+ and +after_delete+ hooks, or using a database trigger
529
- (the preferred method if you only have to support one database and that database supports
530
- triggers).
656
+ a sum cache using +after_create+ and +after_delete+ hooks, or preferrably using a database trigger.