sequel 4.36.0 → 5.61.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 (760) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG +548 -5749
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +265 -159
  5. data/bin/sequel +34 -12
  6. data/doc/advanced_associations.rdoc +228 -187
  7. data/doc/association_basics.rdoc +281 -291
  8. data/doc/bin_sequel.rdoc +5 -3
  9. data/doc/cheat_sheet.rdoc +86 -51
  10. data/doc/code_order.rdoc +25 -19
  11. data/doc/core_extensions.rdoc +104 -63
  12. data/doc/dataset_basics.rdoc +12 -21
  13. data/doc/dataset_filtering.rdoc +99 -86
  14. data/doc/extensions.rdoc +3 -10
  15. data/doc/fork_safety.rdoc +84 -0
  16. data/doc/mass_assignment.rdoc +74 -31
  17. data/doc/migration.rdoc +59 -51
  18. data/doc/model_dataset_method_design.rdoc +129 -0
  19. data/doc/model_hooks.rdoc +15 -25
  20. data/doc/model_plugins.rdoc +12 -12
  21. data/doc/mssql_stored_procedures.rdoc +3 -3
  22. data/doc/object_model.rdoc +58 -68
  23. data/doc/opening_databases.rdoc +85 -95
  24. data/doc/postgresql.rdoc +263 -38
  25. data/doc/prepared_statements.rdoc +29 -24
  26. data/doc/querying.rdoc +189 -167
  27. data/doc/reflection.rdoc +5 -6
  28. data/doc/release_notes/5.0.0.txt +159 -0
  29. data/doc/release_notes/5.1.0.txt +31 -0
  30. data/doc/release_notes/5.10.0.txt +84 -0
  31. data/doc/release_notes/5.11.0.txt +83 -0
  32. data/doc/release_notes/5.12.0.txt +141 -0
  33. data/doc/release_notes/5.13.0.txt +27 -0
  34. data/doc/release_notes/5.14.0.txt +63 -0
  35. data/doc/release_notes/5.15.0.txt +39 -0
  36. data/doc/release_notes/5.16.0.txt +110 -0
  37. data/doc/release_notes/5.17.0.txt +31 -0
  38. data/doc/release_notes/5.18.0.txt +69 -0
  39. data/doc/release_notes/5.19.0.txt +28 -0
  40. data/doc/release_notes/5.2.0.txt +33 -0
  41. data/doc/release_notes/5.20.0.txt +89 -0
  42. data/doc/release_notes/5.21.0.txt +87 -0
  43. data/doc/release_notes/5.22.0.txt +48 -0
  44. data/doc/release_notes/5.23.0.txt +56 -0
  45. data/doc/release_notes/5.24.0.txt +56 -0
  46. data/doc/release_notes/5.25.0.txt +32 -0
  47. data/doc/release_notes/5.26.0.txt +35 -0
  48. data/doc/release_notes/5.27.0.txt +21 -0
  49. data/doc/release_notes/5.28.0.txt +16 -0
  50. data/doc/release_notes/5.29.0.txt +22 -0
  51. data/doc/release_notes/5.3.0.txt +121 -0
  52. data/doc/release_notes/5.30.0.txt +20 -0
  53. data/doc/release_notes/5.31.0.txt +148 -0
  54. data/doc/release_notes/5.32.0.txt +46 -0
  55. data/doc/release_notes/5.33.0.txt +24 -0
  56. data/doc/release_notes/5.34.0.txt +40 -0
  57. data/doc/release_notes/5.35.0.txt +56 -0
  58. data/doc/release_notes/5.36.0.txt +60 -0
  59. data/doc/release_notes/5.37.0.txt +30 -0
  60. data/doc/release_notes/5.38.0.txt +28 -0
  61. data/doc/release_notes/5.39.0.txt +19 -0
  62. data/doc/release_notes/5.4.0.txt +80 -0
  63. data/doc/release_notes/5.40.0.txt +40 -0
  64. data/doc/release_notes/5.41.0.txt +25 -0
  65. data/doc/release_notes/5.42.0.txt +136 -0
  66. data/doc/release_notes/5.43.0.txt +98 -0
  67. data/doc/release_notes/5.44.0.txt +32 -0
  68. data/doc/release_notes/5.45.0.txt +34 -0
  69. data/doc/release_notes/5.46.0.txt +87 -0
  70. data/doc/release_notes/5.47.0.txt +59 -0
  71. data/doc/release_notes/5.48.0.txt +14 -0
  72. data/doc/release_notes/5.49.0.txt +59 -0
  73. data/doc/release_notes/5.5.0.txt +61 -0
  74. data/doc/release_notes/5.50.0.txt +78 -0
  75. data/doc/release_notes/5.51.0.txt +47 -0
  76. data/doc/release_notes/5.52.0.txt +87 -0
  77. data/doc/release_notes/5.53.0.txt +23 -0
  78. data/doc/release_notes/5.54.0.txt +27 -0
  79. data/doc/release_notes/5.55.0.txt +21 -0
  80. data/doc/release_notes/5.56.0.txt +51 -0
  81. data/doc/release_notes/5.57.0.txt +23 -0
  82. data/doc/release_notes/5.58.0.txt +31 -0
  83. data/doc/release_notes/5.59.0.txt +73 -0
  84. data/doc/release_notes/5.6.0.txt +31 -0
  85. data/doc/release_notes/5.60.0.txt +22 -0
  86. data/doc/release_notes/5.61.0.txt +43 -0
  87. data/doc/release_notes/5.7.0.txt +108 -0
  88. data/doc/release_notes/5.8.0.txt +170 -0
  89. data/doc/release_notes/5.9.0.txt +99 -0
  90. data/doc/schema_modification.rdoc +95 -75
  91. data/doc/security.rdoc +109 -80
  92. data/doc/sharding.rdoc +74 -47
  93. data/doc/sql.rdoc +147 -122
  94. data/doc/testing.rdoc +43 -20
  95. data/doc/thread_safety.rdoc +2 -4
  96. data/doc/transactions.rdoc +97 -18
  97. data/doc/validations.rdoc +52 -50
  98. data/doc/virtual_rows.rdoc +90 -109
  99. data/lib/sequel/adapters/ado/access.rb +15 -17
  100. data/lib/sequel/adapters/ado/mssql.rb +6 -15
  101. data/lib/sequel/adapters/ado.rb +150 -20
  102. data/lib/sequel/adapters/amalgalite.rb +11 -23
  103. data/lib/sequel/adapters/ibmdb.rb +47 -55
  104. data/lib/sequel/adapters/jdbc/db2.rb +29 -39
  105. data/lib/sequel/adapters/jdbc/derby.rb +58 -54
  106. data/lib/sequel/adapters/jdbc/h2.rb +93 -35
  107. data/lib/sequel/adapters/jdbc/hsqldb.rb +24 -31
  108. data/lib/sequel/adapters/jdbc/jtds.rb +2 -10
  109. data/lib/sequel/adapters/jdbc/mssql.rb +3 -11
  110. data/lib/sequel/adapters/jdbc/mysql.rb +17 -20
  111. data/lib/sequel/adapters/jdbc/oracle.rb +22 -18
  112. data/lib/sequel/adapters/jdbc/postgresql.rb +69 -71
  113. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +11 -23
  114. data/lib/sequel/adapters/jdbc/sqlite.rb +47 -11
  115. data/lib/sequel/adapters/jdbc/sqlserver.rb +34 -9
  116. data/lib/sequel/adapters/jdbc/transactions.rb +22 -38
  117. data/lib/sequel/adapters/jdbc.rb +145 -130
  118. data/lib/sequel/adapters/mock.rb +100 -111
  119. data/lib/sequel/adapters/mysql.rb +114 -122
  120. data/lib/sequel/adapters/mysql2.rb +147 -63
  121. data/lib/sequel/adapters/odbc/db2.rb +1 -1
  122. data/lib/sequel/adapters/odbc/mssql.rb +8 -14
  123. data/lib/sequel/adapters/odbc/oracle.rb +11 -0
  124. data/lib/sequel/adapters/odbc.rb +20 -25
  125. data/lib/sequel/adapters/oracle.rb +50 -56
  126. data/lib/sequel/adapters/postgres.rb +305 -327
  127. data/lib/sequel/adapters/postgresql.rb +1 -1
  128. data/lib/sequel/adapters/shared/access.rb +74 -78
  129. data/lib/sequel/adapters/shared/db2.rb +118 -71
  130. data/lib/sequel/adapters/shared/mssql.rb +301 -220
  131. data/lib/sequel/adapters/shared/mysql.rb +299 -217
  132. data/lib/sequel/adapters/shared/oracle.rb +226 -65
  133. data/lib/sequel/adapters/shared/postgres.rb +935 -395
  134. data/lib/sequel/adapters/shared/sqlanywhere.rb +105 -126
  135. data/lib/sequel/adapters/shared/sqlite.rb +447 -173
  136. data/lib/sequel/adapters/sqlanywhere.rb +48 -35
  137. data/lib/sequel/adapters/sqlite.rb +156 -111
  138. data/lib/sequel/adapters/tinytds.rb +30 -38
  139. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  140. data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +3 -6
  141. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +2 -2
  142. data/lib/sequel/adapters/utils/mysql_mysql2.rb +87 -0
  143. data/lib/sequel/adapters/utils/mysql_prepared_statements.rb +56 -0
  144. data/lib/sequel/adapters/utils/replace.rb +1 -4
  145. data/lib/sequel/adapters/utils/stored_procedures.rb +7 -22
  146. data/lib/sequel/adapters/utils/unmodified_identifiers.rb +28 -0
  147. data/lib/sequel/ast_transformer.rb +17 -89
  148. data/lib/sequel/connection_pool/sharded_single.rb +18 -15
  149. data/lib/sequel/connection_pool/sharded_threaded.rb +130 -111
  150. data/lib/sequel/connection_pool/single.rb +18 -13
  151. data/lib/sequel/connection_pool/threaded.rb +121 -120
  152. data/lib/sequel/connection_pool.rb +48 -29
  153. data/lib/sequel/core.rb +351 -301
  154. data/lib/sequel/database/connecting.rb +69 -57
  155. data/lib/sequel/database/dataset.rb +13 -5
  156. data/lib/sequel/database/dataset_defaults.rb +18 -102
  157. data/lib/sequel/database/features.rb +18 -4
  158. data/lib/sequel/database/logging.rb +12 -11
  159. data/lib/sequel/database/misc.rb +180 -122
  160. data/lib/sequel/database/query.rb +47 -27
  161. data/lib/sequel/database/schema_generator.rb +178 -84
  162. data/lib/sequel/database/schema_methods.rb +172 -97
  163. data/lib/sequel/database/transactions.rb +205 -44
  164. data/lib/sequel/database.rb +17 -2
  165. data/lib/sequel/dataset/actions.rb +339 -155
  166. data/lib/sequel/dataset/dataset_module.rb +46 -0
  167. data/lib/sequel/dataset/features.rb +90 -35
  168. data/lib/sequel/dataset/graph.rb +80 -58
  169. data/lib/sequel/dataset/misc.rb +137 -47
  170. data/lib/sequel/dataset/placeholder_literalizer.rb +63 -25
  171. data/lib/sequel/dataset/prepared_statements.rb +188 -85
  172. data/lib/sequel/dataset/query.rb +530 -222
  173. data/lib/sequel/dataset/sql.rb +590 -368
  174. data/lib/sequel/dataset.rb +26 -16
  175. data/lib/sequel/deprecated.rb +12 -2
  176. data/lib/sequel/exceptions.rb +46 -16
  177. data/lib/sequel/extensions/_model_constraint_validations.rb +16 -0
  178. data/lib/sequel/extensions/_model_pg_row.rb +43 -0
  179. data/lib/sequel/extensions/_pretty_table.rb +2 -5
  180. data/lib/sequel/extensions/any_not_empty.rb +45 -0
  181. data/lib/sequel/extensions/arbitrary_servers.rb +10 -10
  182. data/lib/sequel/extensions/async_thread_pool.rb +438 -0
  183. data/lib/sequel/extensions/auto_literal_strings.rb +74 -0
  184. data/lib/sequel/extensions/blank.rb +8 -0
  185. data/lib/sequel/extensions/caller_logging.rb +79 -0
  186. data/lib/sequel/extensions/columns_introspection.rb +4 -3
  187. data/lib/sequel/extensions/connection_expiration.rb +20 -10
  188. data/lib/sequel/extensions/connection_validator.rb +11 -10
  189. data/lib/sequel/extensions/constant_sql_override.rb +65 -0
  190. data/lib/sequel/extensions/constraint_validations.rb +62 -39
  191. data/lib/sequel/extensions/core_extensions.rb +42 -48
  192. data/lib/sequel/extensions/core_refinements.rb +80 -59
  193. data/lib/sequel/extensions/current_datetime_timestamp.rb +1 -4
  194. data/lib/sequel/extensions/date_arithmetic.rb +98 -39
  195. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  196. data/lib/sequel/extensions/datetime_parse_to_time.rb +41 -0
  197. data/lib/sequel/extensions/duplicate_columns_handler.rb +21 -14
  198. data/lib/sequel/extensions/empty_array_consider_nulls.rb +2 -2
  199. data/lib/sequel/extensions/escaped_like.rb +100 -0
  200. data/lib/sequel/extensions/eval_inspect.rb +12 -15
  201. data/lib/sequel/extensions/exclude_or_null.rb +68 -0
  202. data/lib/sequel/extensions/fiber_concurrency.rb +24 -0
  203. data/lib/sequel/extensions/freeze_datasets.rb +3 -0
  204. data/lib/sequel/extensions/from_block.rb +1 -34
  205. data/lib/sequel/extensions/graph_each.rb +4 -4
  206. data/lib/sequel/extensions/identifier_mangling.rb +180 -0
  207. data/lib/sequel/extensions/implicit_subquery.rb +48 -0
  208. data/lib/sequel/extensions/index_caching.rb +109 -0
  209. data/lib/sequel/extensions/inflector.rb +13 -5
  210. data/lib/sequel/extensions/integer64.rb +32 -0
  211. data/lib/sequel/extensions/is_distinct_from.rb +141 -0
  212. data/lib/sequel/extensions/looser_typecasting.rb +17 -8
  213. data/lib/sequel/extensions/migration.rb +119 -78
  214. data/lib/sequel/extensions/named_timezones.rb +88 -23
  215. data/lib/sequel/extensions/no_auto_literal_strings.rb +2 -82
  216. data/lib/sequel/extensions/null_dataset.rb +8 -8
  217. data/lib/sequel/extensions/pagination.rb +32 -29
  218. data/lib/sequel/extensions/pg_array.rb +221 -287
  219. data/lib/sequel/extensions/pg_array_ops.rb +17 -9
  220. data/lib/sequel/extensions/pg_enum.rb +63 -23
  221. data/lib/sequel/extensions/pg_extended_date_support.rb +241 -0
  222. data/lib/sequel/extensions/pg_hstore.rb +45 -54
  223. data/lib/sequel/extensions/pg_hstore_ops.rb +58 -6
  224. data/lib/sequel/extensions/pg_inet.rb +31 -12
  225. data/lib/sequel/extensions/pg_inet_ops.rb +2 -2
  226. data/lib/sequel/extensions/pg_interval.rb +56 -29
  227. data/lib/sequel/extensions/pg_json.rb +417 -140
  228. data/lib/sequel/extensions/pg_json_ops.rb +270 -18
  229. data/lib/sequel/extensions/pg_loose_count.rb +4 -2
  230. data/lib/sequel/extensions/pg_multirange.rb +372 -0
  231. data/lib/sequel/extensions/pg_range.rb +131 -191
  232. data/lib/sequel/extensions/pg_range_ops.rb +42 -13
  233. data/lib/sequel/extensions/pg_row.rb +48 -81
  234. data/lib/sequel/extensions/pg_row_ops.rb +33 -14
  235. data/lib/sequel/extensions/pg_static_cache_updater.rb +2 -2
  236. data/lib/sequel/extensions/pg_timestamptz.rb +28 -0
  237. data/lib/sequel/extensions/query.rb +9 -7
  238. data/lib/sequel/extensions/round_timestamps.rb +0 -6
  239. data/lib/sequel/extensions/run_transaction_hooks.rb +72 -0
  240. data/lib/sequel/extensions/s.rb +60 -0
  241. data/lib/sequel/extensions/schema_caching.rb +10 -1
  242. data/lib/sequel/extensions/schema_dumper.rb +71 -48
  243. data/lib/sequel/extensions/select_remove.rb +4 -4
  244. data/lib/sequel/extensions/sequel_4_dataset_methods.rb +85 -0
  245. data/lib/sequel/extensions/server_block.rb +51 -27
  246. data/lib/sequel/extensions/split_array_nil.rb +4 -4
  247. data/lib/sequel/extensions/sql_comments.rb +119 -7
  248. data/lib/sequel/extensions/sql_expr.rb +2 -1
  249. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  250. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  251. data/lib/sequel/extensions/string_agg.rb +11 -8
  252. data/lib/sequel/extensions/string_date_time.rb +19 -23
  253. data/lib/sequel/extensions/symbol_aref.rb +55 -0
  254. data/lib/sequel/extensions/symbol_aref_refinement.rb +43 -0
  255. data/lib/sequel/extensions/symbol_as.rb +23 -0
  256. data/lib/sequel/extensions/symbol_as_refinement.rb +37 -0
  257. data/lib/sequel/extensions/synchronize_sql.rb +45 -0
  258. data/lib/sequel/extensions/to_dot.rb +10 -4
  259. data/lib/sequel/extensions/virtual_row_method_block.rb +44 -0
  260. data/lib/sequel/model/associations.rb +1006 -284
  261. data/lib/sequel/model/base.rb +560 -805
  262. data/lib/sequel/model/dataset_module.rb +11 -10
  263. data/lib/sequel/model/default_inflections.rb +1 -1
  264. data/lib/sequel/model/errors.rb +10 -3
  265. data/lib/sequel/model/exceptions.rb +8 -10
  266. data/lib/sequel/model/inflections.rb +7 -20
  267. data/lib/sequel/model/plugins.rb +114 -0
  268. data/lib/sequel/model.rb +32 -82
  269. data/lib/sequel/plugins/active_model.rb +30 -14
  270. data/lib/sequel/plugins/after_initialize.rb +1 -1
  271. data/lib/sequel/plugins/association_dependencies.rb +25 -18
  272. data/lib/sequel/plugins/association_lazy_eager_option.rb +66 -0
  273. data/lib/sequel/plugins/association_multi_add_remove.rb +85 -0
  274. data/lib/sequel/plugins/association_pks.rb +147 -70
  275. data/lib/sequel/plugins/association_proxies.rb +33 -9
  276. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  277. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  278. data/lib/sequel/plugins/auto_validations.rb +95 -28
  279. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  280. data/lib/sequel/plugins/before_after_save.rb +0 -42
  281. data/lib/sequel/plugins/blacklist_security.rb +21 -12
  282. data/lib/sequel/plugins/boolean_readers.rb +5 -5
  283. data/lib/sequel/plugins/boolean_subsets.rb +13 -8
  284. data/lib/sequel/plugins/caching.rb +25 -16
  285. data/lib/sequel/plugins/class_table_inheritance.rb +179 -100
  286. data/lib/sequel/plugins/column_conflicts.rb +16 -3
  287. data/lib/sequel/plugins/column_encryption.rb +728 -0
  288. data/lib/sequel/plugins/column_select.rb +7 -5
  289. data/lib/sequel/plugins/columns_updated.rb +42 -0
  290. data/lib/sequel/plugins/composition.rb +42 -26
  291. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  292. data/lib/sequel/plugins/constraint_validations.rb +20 -14
  293. data/lib/sequel/plugins/csv_serializer.rb +56 -35
  294. data/lib/sequel/plugins/dataset_associations.rb +40 -17
  295. data/lib/sequel/plugins/def_dataset_method.rb +90 -0
  296. data/lib/sequel/plugins/defaults_setter.rb +65 -10
  297. data/lib/sequel/plugins/delay_add_association.rb +1 -1
  298. data/lib/sequel/plugins/dirty.rb +62 -24
  299. data/lib/sequel/plugins/eager_each.rb +3 -3
  300. data/lib/sequel/plugins/eager_graph_eager.rb +139 -0
  301. data/lib/sequel/plugins/empty_failure_backtraces.rb +38 -0
  302. data/lib/sequel/plugins/enum.rb +124 -0
  303. data/lib/sequel/plugins/error_splitter.rb +17 -12
  304. data/lib/sequel/plugins/finder.rb +246 -0
  305. data/lib/sequel/plugins/forbid_lazy_load.rb +216 -0
  306. data/lib/sequel/plugins/force_encoding.rb +7 -12
  307. data/lib/sequel/plugins/hook_class_methods.rb +37 -54
  308. data/lib/sequel/plugins/input_transformer.rb +18 -10
  309. data/lib/sequel/plugins/insert_conflict.rb +76 -0
  310. data/lib/sequel/plugins/insert_returning_select.rb +2 -2
  311. data/lib/sequel/plugins/instance_filters.rb +10 -8
  312. data/lib/sequel/plugins/instance_hooks.rb +34 -17
  313. data/lib/sequel/plugins/instance_specific_default.rb +113 -0
  314. data/lib/sequel/plugins/inverted_subsets.rb +22 -13
  315. data/lib/sequel/plugins/json_serializer.rb +124 -64
  316. data/lib/sequel/plugins/lazy_attributes.rb +21 -14
  317. data/lib/sequel/plugins/list.rb +35 -21
  318. data/lib/sequel/plugins/many_through_many.rb +134 -21
  319. data/lib/sequel/plugins/modification_detection.rb +15 -5
  320. data/lib/sequel/plugins/mssql_optimistic_locking.rb +6 -5
  321. data/lib/sequel/plugins/nested_attributes.rb +61 -31
  322. data/lib/sequel/plugins/optimistic_locking.rb +3 -3
  323. data/lib/sequel/plugins/pg_array_associations.rb +103 -53
  324. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +350 -0
  325. data/lib/sequel/plugins/pg_row.rb +5 -51
  326. data/lib/sequel/plugins/prepared_statements.rb +60 -72
  327. data/lib/sequel/plugins/prepared_statements_safe.rb +9 -4
  328. data/lib/sequel/plugins/rcte_tree.rb +68 -82
  329. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  330. data/lib/sequel/plugins/serialization.rb +43 -46
  331. data/lib/sequel/plugins/serialization_modification_detection.rb +3 -2
  332. data/lib/sequel/plugins/sharding.rb +15 -10
  333. data/lib/sequel/plugins/single_table_inheritance.rb +67 -28
  334. data/lib/sequel/plugins/skip_create_refresh.rb +3 -3
  335. data/lib/sequel/plugins/skip_saving_columns.rb +108 -0
  336. data/lib/sequel/plugins/split_values.rb +11 -6
  337. data/lib/sequel/plugins/sql_comments.rb +189 -0
  338. data/lib/sequel/plugins/static_cache.rb +77 -53
  339. data/lib/sequel/plugins/static_cache_cache.rb +53 -0
  340. data/lib/sequel/plugins/string_stripper.rb +3 -3
  341. data/lib/sequel/plugins/subclasses.rb +43 -10
  342. data/lib/sequel/plugins/subset_conditions.rb +15 -5
  343. data/lib/sequel/plugins/table_select.rb +2 -2
  344. data/lib/sequel/plugins/tactical_eager_loading.rb +96 -12
  345. data/lib/sequel/plugins/throw_failures.rb +110 -0
  346. data/lib/sequel/plugins/timestamps.rb +20 -8
  347. data/lib/sequel/plugins/touch.rb +19 -8
  348. data/lib/sequel/plugins/tree.rb +62 -32
  349. data/lib/sequel/plugins/typecast_on_load.rb +12 -4
  350. data/lib/sequel/plugins/unlimited_update.rb +1 -7
  351. data/lib/sequel/plugins/unused_associations.rb +521 -0
  352. data/lib/sequel/plugins/update_or_create.rb +4 -4
  353. data/lib/sequel/plugins/update_primary_key.rb +1 -1
  354. data/lib/sequel/plugins/update_refresh.rb +26 -15
  355. data/lib/sequel/plugins/uuid.rb +7 -11
  356. data/lib/sequel/plugins/validate_associated.rb +18 -0
  357. data/lib/sequel/plugins/validation_class_methods.rb +38 -19
  358. data/lib/sequel/plugins/validation_contexts.rb +49 -0
  359. data/lib/sequel/plugins/validation_helpers.rb +57 -41
  360. data/lib/sequel/plugins/whitelist_security.rb +122 -0
  361. data/lib/sequel/plugins/xml_serializer.rb +30 -31
  362. data/lib/sequel/sql.rb +471 -331
  363. data/lib/sequel/timezones.rb +78 -47
  364. data/lib/sequel/version.rb +7 -2
  365. data/lib/sequel.rb +1 -1
  366. metadata +217 -521
  367. data/Rakefile +0 -164
  368. data/doc/active_record.rdoc +0 -928
  369. data/doc/release_notes/1.0.txt +0 -38
  370. data/doc/release_notes/1.1.txt +0 -143
  371. data/doc/release_notes/1.3.txt +0 -101
  372. data/doc/release_notes/1.4.0.txt +0 -53
  373. data/doc/release_notes/1.5.0.txt +0 -155
  374. data/doc/release_notes/2.0.0.txt +0 -298
  375. data/doc/release_notes/2.1.0.txt +0 -271
  376. data/doc/release_notes/2.10.0.txt +0 -328
  377. data/doc/release_notes/2.11.0.txt +0 -215
  378. data/doc/release_notes/2.12.0.txt +0 -534
  379. data/doc/release_notes/2.2.0.txt +0 -253
  380. data/doc/release_notes/2.3.0.txt +0 -88
  381. data/doc/release_notes/2.4.0.txt +0 -106
  382. data/doc/release_notes/2.5.0.txt +0 -137
  383. data/doc/release_notes/2.6.0.txt +0 -157
  384. data/doc/release_notes/2.7.0.txt +0 -166
  385. data/doc/release_notes/2.8.0.txt +0 -171
  386. data/doc/release_notes/2.9.0.txt +0 -97
  387. data/doc/release_notes/3.0.0.txt +0 -221
  388. data/doc/release_notes/3.1.0.txt +0 -406
  389. data/doc/release_notes/3.10.0.txt +0 -286
  390. data/doc/release_notes/3.11.0.txt +0 -254
  391. data/doc/release_notes/3.12.0.txt +0 -304
  392. data/doc/release_notes/3.13.0.txt +0 -210
  393. data/doc/release_notes/3.14.0.txt +0 -118
  394. data/doc/release_notes/3.15.0.txt +0 -78
  395. data/doc/release_notes/3.16.0.txt +0 -45
  396. data/doc/release_notes/3.17.0.txt +0 -58
  397. data/doc/release_notes/3.18.0.txt +0 -120
  398. data/doc/release_notes/3.19.0.txt +0 -67
  399. data/doc/release_notes/3.2.0.txt +0 -268
  400. data/doc/release_notes/3.20.0.txt +0 -41
  401. data/doc/release_notes/3.21.0.txt +0 -87
  402. data/doc/release_notes/3.22.0.txt +0 -39
  403. data/doc/release_notes/3.23.0.txt +0 -172
  404. data/doc/release_notes/3.24.0.txt +0 -420
  405. data/doc/release_notes/3.25.0.txt +0 -88
  406. data/doc/release_notes/3.26.0.txt +0 -88
  407. data/doc/release_notes/3.27.0.txt +0 -82
  408. data/doc/release_notes/3.28.0.txt +0 -304
  409. data/doc/release_notes/3.29.0.txt +0 -459
  410. data/doc/release_notes/3.3.0.txt +0 -192
  411. data/doc/release_notes/3.30.0.txt +0 -135
  412. data/doc/release_notes/3.31.0.txt +0 -146
  413. data/doc/release_notes/3.32.0.txt +0 -202
  414. data/doc/release_notes/3.33.0.txt +0 -157
  415. data/doc/release_notes/3.34.0.txt +0 -671
  416. data/doc/release_notes/3.35.0.txt +0 -144
  417. data/doc/release_notes/3.36.0.txt +0 -245
  418. data/doc/release_notes/3.37.0.txt +0 -338
  419. data/doc/release_notes/3.38.0.txt +0 -234
  420. data/doc/release_notes/3.39.0.txt +0 -237
  421. data/doc/release_notes/3.4.0.txt +0 -325
  422. data/doc/release_notes/3.40.0.txt +0 -73
  423. data/doc/release_notes/3.41.0.txt +0 -155
  424. data/doc/release_notes/3.42.0.txt +0 -74
  425. data/doc/release_notes/3.43.0.txt +0 -105
  426. data/doc/release_notes/3.44.0.txt +0 -152
  427. data/doc/release_notes/3.45.0.txt +0 -179
  428. data/doc/release_notes/3.46.0.txt +0 -122
  429. data/doc/release_notes/3.47.0.txt +0 -270
  430. data/doc/release_notes/3.48.0.txt +0 -477
  431. data/doc/release_notes/3.5.0.txt +0 -510
  432. data/doc/release_notes/3.6.0.txt +0 -366
  433. data/doc/release_notes/3.7.0.txt +0 -179
  434. data/doc/release_notes/3.8.0.txt +0 -151
  435. data/doc/release_notes/3.9.0.txt +0 -233
  436. data/doc/release_notes/4.0.0.txt +0 -262
  437. data/doc/release_notes/4.1.0.txt +0 -85
  438. data/doc/release_notes/4.10.0.txt +0 -226
  439. data/doc/release_notes/4.11.0.txt +0 -147
  440. data/doc/release_notes/4.12.0.txt +0 -105
  441. data/doc/release_notes/4.13.0.txt +0 -169
  442. data/doc/release_notes/4.14.0.txt +0 -68
  443. data/doc/release_notes/4.15.0.txt +0 -56
  444. data/doc/release_notes/4.16.0.txt +0 -36
  445. data/doc/release_notes/4.17.0.txt +0 -38
  446. data/doc/release_notes/4.18.0.txt +0 -36
  447. data/doc/release_notes/4.19.0.txt +0 -45
  448. data/doc/release_notes/4.2.0.txt +0 -129
  449. data/doc/release_notes/4.20.0.txt +0 -79
  450. data/doc/release_notes/4.21.0.txt +0 -94
  451. data/doc/release_notes/4.22.0.txt +0 -72
  452. data/doc/release_notes/4.23.0.txt +0 -65
  453. data/doc/release_notes/4.24.0.txt +0 -99
  454. data/doc/release_notes/4.25.0.txt +0 -181
  455. data/doc/release_notes/4.26.0.txt +0 -44
  456. data/doc/release_notes/4.27.0.txt +0 -78
  457. data/doc/release_notes/4.28.0.txt +0 -57
  458. data/doc/release_notes/4.29.0.txt +0 -41
  459. data/doc/release_notes/4.3.0.txt +0 -40
  460. data/doc/release_notes/4.30.0.txt +0 -37
  461. data/doc/release_notes/4.31.0.txt +0 -57
  462. data/doc/release_notes/4.32.0.txt +0 -132
  463. data/doc/release_notes/4.33.0.txt +0 -88
  464. data/doc/release_notes/4.34.0.txt +0 -86
  465. data/doc/release_notes/4.35.0.txt +0 -130
  466. data/doc/release_notes/4.36.0.txt +0 -116
  467. data/doc/release_notes/4.4.0.txt +0 -92
  468. data/doc/release_notes/4.5.0.txt +0 -34
  469. data/doc/release_notes/4.6.0.txt +0 -30
  470. data/doc/release_notes/4.7.0.txt +0 -103
  471. data/doc/release_notes/4.8.0.txt +0 -175
  472. data/doc/release_notes/4.9.0.txt +0 -190
  473. data/lib/sequel/adapters/cubrid.rb +0 -144
  474. data/lib/sequel/adapters/do/mysql.rb +0 -66
  475. data/lib/sequel/adapters/do/postgres.rb +0 -44
  476. data/lib/sequel/adapters/do/sqlite3.rb +0 -42
  477. data/lib/sequel/adapters/do.rb +0 -158
  478. data/lib/sequel/adapters/jdbc/as400.rb +0 -84
  479. data/lib/sequel/adapters/jdbc/cubrid.rb +0 -64
  480. data/lib/sequel/adapters/jdbc/firebirdsql.rb +0 -36
  481. data/lib/sequel/adapters/jdbc/informix-sqli.rb +0 -33
  482. data/lib/sequel/adapters/jdbc/jdbcprogress.rb +0 -33
  483. data/lib/sequel/adapters/odbc/progress.rb +0 -10
  484. data/lib/sequel/adapters/shared/cubrid.rb +0 -245
  485. data/lib/sequel/adapters/shared/firebird.rb +0 -247
  486. data/lib/sequel/adapters/shared/informix.rb +0 -54
  487. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +0 -152
  488. data/lib/sequel/adapters/shared/progress.rb +0 -40
  489. data/lib/sequel/adapters/swift/mysql.rb +0 -49
  490. data/lib/sequel/adapters/swift/postgres.rb +0 -47
  491. data/lib/sequel/adapters/swift/sqlite.rb +0 -49
  492. data/lib/sequel/adapters/swift.rb +0 -160
  493. data/lib/sequel/adapters/utils/pg_types.rb +0 -70
  494. data/lib/sequel/dataset/mutation.rb +0 -111
  495. data/lib/sequel/extensions/empty_array_ignore_nulls.rb +0 -5
  496. data/lib/sequel/extensions/filter_having.rb +0 -63
  497. data/lib/sequel/extensions/hash_aliases.rb +0 -49
  498. data/lib/sequel/extensions/meta_def.rb +0 -35
  499. data/lib/sequel/extensions/query_literals.rb +0 -84
  500. data/lib/sequel/extensions/ruby18_symbol_extensions.rb +0 -24
  501. data/lib/sequel/extensions/sequel_3_dataset_methods.rb +0 -122
  502. data/lib/sequel/extensions/set_overrides.rb +0 -76
  503. data/lib/sequel/no_core_ext.rb +0 -3
  504. data/lib/sequel/plugins/association_autoreloading.rb +0 -9
  505. data/lib/sequel/plugins/identifier_columns.rb +0 -47
  506. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +0 -9
  507. data/lib/sequel/plugins/pg_typecast_on_load.rb +0 -81
  508. data/lib/sequel/plugins/prepared_statements_associations.rb +0 -119
  509. data/lib/sequel/plugins/prepared_statements_with_pk.rb +0 -61
  510. data/lib/sequel/plugins/schema.rb +0 -82
  511. data/lib/sequel/plugins/scissors.rb +0 -35
  512. data/spec/adapter_spec.rb +0 -4
  513. data/spec/adapters/db2_spec.rb +0 -160
  514. data/spec/adapters/firebird_spec.rb +0 -411
  515. data/spec/adapters/informix_spec.rb +0 -100
  516. data/spec/adapters/mssql_spec.rb +0 -733
  517. data/spec/adapters/mysql_spec.rb +0 -1319
  518. data/spec/adapters/oracle_spec.rb +0 -313
  519. data/spec/adapters/postgres_spec.rb +0 -3790
  520. data/spec/adapters/spec_helper.rb +0 -49
  521. data/spec/adapters/sqlanywhere_spec.rb +0 -170
  522. data/spec/adapters/sqlite_spec.rb +0 -688
  523. data/spec/bin_spec.rb +0 -258
  524. data/spec/core/connection_pool_spec.rb +0 -1045
  525. data/spec/core/database_spec.rb +0 -2636
  526. data/spec/core/dataset_spec.rb +0 -5175
  527. data/spec/core/deprecated_spec.rb +0 -70
  528. data/spec/core/expression_filters_spec.rb +0 -1247
  529. data/spec/core/mock_adapter_spec.rb +0 -464
  530. data/spec/core/object_graph_spec.rb +0 -303
  531. data/spec/core/placeholder_literalizer_spec.rb +0 -163
  532. data/spec/core/schema_generator_spec.rb +0 -203
  533. data/spec/core/schema_spec.rb +0 -1676
  534. data/spec/core/spec_helper.rb +0 -34
  535. data/spec/core/version_spec.rb +0 -7
  536. data/spec/core_extensions_spec.rb +0 -699
  537. data/spec/core_model_spec.rb +0 -2
  538. data/spec/core_spec.rb +0 -1
  539. data/spec/extensions/accessed_columns_spec.rb +0 -51
  540. data/spec/extensions/active_model_spec.rb +0 -85
  541. data/spec/extensions/after_initialize_spec.rb +0 -24
  542. data/spec/extensions/arbitrary_servers_spec.rb +0 -109
  543. data/spec/extensions/association_dependencies_spec.rb +0 -117
  544. data/spec/extensions/association_pks_spec.rb +0 -405
  545. data/spec/extensions/association_proxies_spec.rb +0 -86
  546. data/spec/extensions/auto_validations_spec.rb +0 -192
  547. data/spec/extensions/before_after_save_spec.rb +0 -40
  548. data/spec/extensions/blacklist_security_spec.rb +0 -88
  549. data/spec/extensions/blank_spec.rb +0 -69
  550. data/spec/extensions/boolean_readers_spec.rb +0 -93
  551. data/spec/extensions/boolean_subsets_spec.rb +0 -47
  552. data/spec/extensions/caching_spec.rb +0 -270
  553. data/spec/extensions/class_table_inheritance_spec.rb +0 -444
  554. data/spec/extensions/column_conflicts_spec.rb +0 -60
  555. data/spec/extensions/column_select_spec.rb +0 -108
  556. data/spec/extensions/columns_introspection_spec.rb +0 -91
  557. data/spec/extensions/composition_spec.rb +0 -242
  558. data/spec/extensions/connection_expiration_spec.rb +0 -121
  559. data/spec/extensions/connection_validator_spec.rb +0 -127
  560. data/spec/extensions/constraint_validations_plugin_spec.rb +0 -288
  561. data/spec/extensions/constraint_validations_spec.rb +0 -389
  562. data/spec/extensions/core_refinements_spec.rb +0 -519
  563. data/spec/extensions/csv_serializer_spec.rb +0 -180
  564. data/spec/extensions/current_datetime_timestamp_spec.rb +0 -27
  565. data/spec/extensions/dataset_associations_spec.rb +0 -343
  566. data/spec/extensions/dataset_source_alias_spec.rb +0 -51
  567. data/spec/extensions/date_arithmetic_spec.rb +0 -167
  568. data/spec/extensions/defaults_setter_spec.rb +0 -102
  569. data/spec/extensions/delay_add_association_spec.rb +0 -74
  570. data/spec/extensions/dirty_spec.rb +0 -180
  571. data/spec/extensions/duplicate_columns_handler_spec.rb +0 -110
  572. data/spec/extensions/eager_each_spec.rb +0 -66
  573. data/spec/extensions/empty_array_consider_nulls_spec.rb +0 -24
  574. data/spec/extensions/error_splitter_spec.rb +0 -18
  575. data/spec/extensions/error_sql_spec.rb +0 -20
  576. data/spec/extensions/eval_inspect_spec.rb +0 -73
  577. data/spec/extensions/filter_having_spec.rb +0 -40
  578. data/spec/extensions/force_encoding_spec.rb +0 -114
  579. data/spec/extensions/from_block_spec.rb +0 -21
  580. data/spec/extensions/graph_each_spec.rb +0 -119
  581. data/spec/extensions/hash_aliases_spec.rb +0 -24
  582. data/spec/extensions/hook_class_methods_spec.rb +0 -429
  583. data/spec/extensions/identifier_columns_spec.rb +0 -17
  584. data/spec/extensions/inflector_spec.rb +0 -183
  585. data/spec/extensions/input_transformer_spec.rb +0 -54
  586. data/spec/extensions/insert_returning_select_spec.rb +0 -46
  587. data/spec/extensions/instance_filters_spec.rb +0 -79
  588. data/spec/extensions/instance_hooks_spec.rb +0 -276
  589. data/spec/extensions/inverted_subsets_spec.rb +0 -33
  590. data/spec/extensions/json_serializer_spec.rb +0 -304
  591. data/spec/extensions/lazy_attributes_spec.rb +0 -170
  592. data/spec/extensions/list_spec.rb +0 -278
  593. data/spec/extensions/looser_typecasting_spec.rb +0 -43
  594. data/spec/extensions/many_through_many_spec.rb +0 -2172
  595. data/spec/extensions/meta_def_spec.rb +0 -21
  596. data/spec/extensions/migration_spec.rb +0 -728
  597. data/spec/extensions/modification_detection_spec.rb +0 -80
  598. data/spec/extensions/mssql_optimistic_locking_spec.rb +0 -91
  599. data/spec/extensions/named_timezones_spec.rb +0 -108
  600. data/spec/extensions/nested_attributes_spec.rb +0 -697
  601. data/spec/extensions/no_auto_literal_strings_spec.rb +0 -65
  602. data/spec/extensions/null_dataset_spec.rb +0 -85
  603. data/spec/extensions/optimistic_locking_spec.rb +0 -128
  604. data/spec/extensions/pagination_spec.rb +0 -118
  605. data/spec/extensions/pg_array_associations_spec.rb +0 -736
  606. data/spec/extensions/pg_array_ops_spec.rb +0 -143
  607. data/spec/extensions/pg_array_spec.rb +0 -390
  608. data/spec/extensions/pg_enum_spec.rb +0 -92
  609. data/spec/extensions/pg_hstore_ops_spec.rb +0 -236
  610. data/spec/extensions/pg_hstore_spec.rb +0 -206
  611. data/spec/extensions/pg_inet_ops_spec.rb +0 -101
  612. data/spec/extensions/pg_inet_spec.rb +0 -52
  613. data/spec/extensions/pg_interval_spec.rb +0 -76
  614. data/spec/extensions/pg_json_ops_spec.rb +0 -275
  615. data/spec/extensions/pg_json_spec.rb +0 -218
  616. data/spec/extensions/pg_loose_count_spec.rb +0 -17
  617. data/spec/extensions/pg_range_ops_spec.rb +0 -58
  618. data/spec/extensions/pg_range_spec.rb +0 -473
  619. data/spec/extensions/pg_row_ops_spec.rb +0 -60
  620. data/spec/extensions/pg_row_plugin_spec.rb +0 -62
  621. data/spec/extensions/pg_row_spec.rb +0 -360
  622. data/spec/extensions/pg_static_cache_updater_spec.rb +0 -92
  623. data/spec/extensions/pg_typecast_on_load_spec.rb +0 -63
  624. data/spec/extensions/prepared_statements_associations_spec.rb +0 -159
  625. data/spec/extensions/prepared_statements_safe_spec.rb +0 -61
  626. data/spec/extensions/prepared_statements_spec.rb +0 -103
  627. data/spec/extensions/prepared_statements_with_pk_spec.rb +0 -31
  628. data/spec/extensions/pretty_table_spec.rb +0 -92
  629. data/spec/extensions/query_literals_spec.rb +0 -183
  630. data/spec/extensions/query_spec.rb +0 -102
  631. data/spec/extensions/rcte_tree_spec.rb +0 -392
  632. data/spec/extensions/round_timestamps_spec.rb +0 -43
  633. data/spec/extensions/schema_caching_spec.rb +0 -41
  634. data/spec/extensions/schema_dumper_spec.rb +0 -814
  635. data/spec/extensions/schema_spec.rb +0 -117
  636. data/spec/extensions/scissors_spec.rb +0 -26
  637. data/spec/extensions/select_remove_spec.rb +0 -38
  638. data/spec/extensions/sequel_3_dataset_methods_spec.rb +0 -101
  639. data/spec/extensions/serialization_modification_detection_spec.rb +0 -98
  640. data/spec/extensions/serialization_spec.rb +0 -362
  641. data/spec/extensions/server_block_spec.rb +0 -90
  642. data/spec/extensions/server_logging_spec.rb +0 -45
  643. data/spec/extensions/set_overrides_spec.rb +0 -61
  644. data/spec/extensions/sharding_spec.rb +0 -198
  645. data/spec/extensions/shared_caching_spec.rb +0 -175
  646. data/spec/extensions/single_table_inheritance_spec.rb +0 -297
  647. data/spec/extensions/singular_table_names_spec.rb +0 -22
  648. data/spec/extensions/skip_create_refresh_spec.rb +0 -17
  649. data/spec/extensions/spec_helper.rb +0 -71
  650. data/spec/extensions/split_array_nil_spec.rb +0 -24
  651. data/spec/extensions/split_values_spec.rb +0 -22
  652. data/spec/extensions/sql_comments_spec.rb +0 -27
  653. data/spec/extensions/sql_expr_spec.rb +0 -60
  654. data/spec/extensions/static_cache_spec.rb +0 -361
  655. data/spec/extensions/string_agg_spec.rb +0 -85
  656. data/spec/extensions/string_date_time_spec.rb +0 -95
  657. data/spec/extensions/string_stripper_spec.rb +0 -68
  658. data/spec/extensions/subclasses_spec.rb +0 -66
  659. data/spec/extensions/subset_conditions_spec.rb +0 -38
  660. data/spec/extensions/table_select_spec.rb +0 -71
  661. data/spec/extensions/tactical_eager_loading_spec.rb +0 -136
  662. data/spec/extensions/thread_local_timezones_spec.rb +0 -67
  663. data/spec/extensions/timestamps_spec.rb +0 -175
  664. data/spec/extensions/to_dot_spec.rb +0 -154
  665. data/spec/extensions/touch_spec.rb +0 -203
  666. data/spec/extensions/tree_spec.rb +0 -274
  667. data/spec/extensions/typecast_on_load_spec.rb +0 -80
  668. data/spec/extensions/unlimited_update_spec.rb +0 -20
  669. data/spec/extensions/update_or_create_spec.rb +0 -87
  670. data/spec/extensions/update_primary_key_spec.rb +0 -100
  671. data/spec/extensions/update_refresh_spec.rb +0 -53
  672. data/spec/extensions/uuid_spec.rb +0 -106
  673. data/spec/extensions/validate_associated_spec.rb +0 -52
  674. data/spec/extensions/validation_class_methods_spec.rb +0 -1027
  675. data/spec/extensions/validation_helpers_spec.rb +0 -554
  676. data/spec/extensions/xml_serializer_spec.rb +0 -207
  677. data/spec/files/bad_down_migration/001_create_alt_basic.rb +0 -4
  678. data/spec/files/bad_down_migration/002_create_alt_advanced.rb +0 -4
  679. data/spec/files/bad_timestamped_migrations/1273253849_create_sessions.rb +0 -9
  680. data/spec/files/bad_timestamped_migrations/1273253851_create_nodes.rb +0 -9
  681. data/spec/files/bad_timestamped_migrations/1273253853_3_create_users.rb +0 -3
  682. data/spec/files/bad_up_migration/001_create_alt_basic.rb +0 -4
  683. data/spec/files/bad_up_migration/002_create_alt_advanced.rb +0 -3
  684. data/spec/files/convert_to_timestamp_migrations/001_create_sessions.rb +0 -9
  685. data/spec/files/convert_to_timestamp_migrations/002_create_nodes.rb +0 -9
  686. data/spec/files/convert_to_timestamp_migrations/003_3_create_users.rb +0 -4
  687. data/spec/files/convert_to_timestamp_migrations/1273253850_create_artists.rb +0 -9
  688. data/spec/files/convert_to_timestamp_migrations/1273253852_create_albums.rb +0 -9
  689. data/spec/files/double_migration/001_create_sessions.rb +0 -9
  690. data/spec/files/double_migration/002_create_nodes.rb +0 -19
  691. data/spec/files/double_migration/003_3_create_users.rb +0 -4
  692. data/spec/files/duplicate_integer_migrations/001_create_alt_advanced.rb +0 -4
  693. data/spec/files/duplicate_integer_migrations/001_create_alt_basic.rb +0 -4
  694. data/spec/files/duplicate_timestamped_migrations/1273253849_create_sessions.rb +0 -9
  695. data/spec/files/duplicate_timestamped_migrations/1273253853_create_nodes.rb +0 -9
  696. data/spec/files/duplicate_timestamped_migrations/1273253853_create_users.rb +0 -4
  697. data/spec/files/empty_migration/001_create_sessions.rb +0 -9
  698. data/spec/files/empty_migration/002_create_nodes.rb +0 -0
  699. data/spec/files/empty_migration/003_3_create_users.rb +0 -4
  700. data/spec/files/integer_migrations/001_create_sessions.rb +0 -9
  701. data/spec/files/integer_migrations/002_create_nodes.rb +0 -9
  702. data/spec/files/integer_migrations/003_3_create_users.rb +0 -4
  703. data/spec/files/interleaved_timestamped_migrations/1273253849_create_sessions.rb +0 -9
  704. data/spec/files/interleaved_timestamped_migrations/1273253850_create_artists.rb +0 -9
  705. data/spec/files/interleaved_timestamped_migrations/1273253851_create_nodes.rb +0 -9
  706. data/spec/files/interleaved_timestamped_migrations/1273253852_create_albums.rb +0 -9
  707. data/spec/files/interleaved_timestamped_migrations/1273253853_3_create_users.rb +0 -4
  708. data/spec/files/missing_integer_migrations/001_create_alt_basic.rb +0 -4
  709. data/spec/files/missing_integer_migrations/003_create_alt_advanced.rb +0 -4
  710. data/spec/files/missing_timestamped_migrations/1273253849_create_sessions.rb +0 -9
  711. data/spec/files/missing_timestamped_migrations/1273253853_3_create_users.rb +0 -4
  712. data/spec/files/reversible_migrations/001_reversible.rb +0 -5
  713. data/spec/files/reversible_migrations/002_reversible.rb +0 -5
  714. data/spec/files/reversible_migrations/003_reversible.rb +0 -5
  715. data/spec/files/reversible_migrations/004_reversible.rb +0 -5
  716. data/spec/files/reversible_migrations/005_reversible.rb +0 -10
  717. data/spec/files/reversible_migrations/006_reversible.rb +0 -10
  718. data/spec/files/reversible_migrations/007_reversible.rb +0 -10
  719. data/spec/files/timestamped_migrations/1273253849_create_sessions.rb +0 -9
  720. data/spec/files/timestamped_migrations/1273253851_create_nodes.rb +0 -9
  721. data/spec/files/timestamped_migrations/1273253853_3_create_users.rb +0 -4
  722. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +0 -4
  723. data/spec/files/transaction_specified_migrations/002_create_basic.rb +0 -4
  724. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +0 -3
  725. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +0 -3
  726. data/spec/files/uppercase_timestamped_migrations/1273253849_CREATE_SESSIONS.RB +0 -9
  727. data/spec/files/uppercase_timestamped_migrations/1273253851_CREATE_NODES.RB +0 -9
  728. data/spec/files/uppercase_timestamped_migrations/1273253853_3_CREATE_USERS.RB +0 -4
  729. data/spec/guards_helper.rb +0 -55
  730. data/spec/integration/associations_test.rb +0 -2506
  731. data/spec/integration/database_test.rb +0 -113
  732. data/spec/integration/dataset_test.rb +0 -1858
  733. data/spec/integration/eager_loader_test.rb +0 -687
  734. data/spec/integration/migrator_test.rb +0 -262
  735. data/spec/integration/model_test.rb +0 -230
  736. data/spec/integration/plugin_test.rb +0 -2297
  737. data/spec/integration/prepared_statement_test.rb +0 -467
  738. data/spec/integration/schema_test.rb +0 -815
  739. data/spec/integration/spec_helper.rb +0 -56
  740. data/spec/integration/timezone_test.rb +0 -86
  741. data/spec/integration/transaction_test.rb +0 -406
  742. data/spec/integration/type_test.rb +0 -133
  743. data/spec/model/association_reflection_spec.rb +0 -565
  744. data/spec/model/associations_spec.rb +0 -4589
  745. data/spec/model/base_spec.rb +0 -759
  746. data/spec/model/class_dataset_methods_spec.rb +0 -150
  747. data/spec/model/dataset_methods_spec.rb +0 -149
  748. data/spec/model/eager_loading_spec.rb +0 -2197
  749. data/spec/model/hooks_spec.rb +0 -604
  750. data/spec/model/inflector_spec.rb +0 -26
  751. data/spec/model/model_spec.rb +0 -1097
  752. data/spec/model/plugins_spec.rb +0 -299
  753. data/spec/model/record_spec.rb +0 -2162
  754. data/spec/model/spec_helper.rb +0 -46
  755. data/spec/model/validations_spec.rb +0 -193
  756. data/spec/model_no_assoc_spec.rb +0 -1
  757. data/spec/model_spec.rb +0 -1
  758. data/spec/plugin_spec.rb +0 -1
  759. data/spec/sequel_coverage.rb +0 -15
  760. data/spec/spec_config.rb +0 -10
@@ -7,46 +7,54 @@ module Sequel
7
7
  module Associations
8
8
  # Map of association type symbols to association reflection classes.
9
9
  ASSOCIATION_TYPES = {}
10
-
10
+
11
11
  # Set an empty association reflection hash in the model
12
12
  def self.apply(model)
13
- model.instance_eval do
13
+ model.instance_exec do
14
14
  @association_reflections = {}
15
15
  @autoreloading_associations = {}
16
16
  @cache_associations = true
17
+ @default_eager_limit_strategy = true
17
18
  @default_association_options = {}
19
+ @default_association_type_options = {}
20
+ @dataset_module_class = DatasetModule
18
21
  end
19
22
  end
20
23
 
24
+ # The dataset module to use for classes using the associations plugin.
25
+ class DatasetModule < Model::DatasetModule
26
+ def_dataset_caching_method(self, :eager)
27
+ end
28
+
21
29
  # AssociationReflection is a Hash subclass that keeps information on Sequel::Model associations. It
22
30
  # provides methods to reduce internal code duplication. It should not
23
31
  # be instantiated by the user.
24
32
  class AssociationReflection < Hash
25
33
  include Sequel::Inflections
26
-
34
+
27
35
  # Name symbol for the _add internal association method
28
36
  def _add_method
29
- :"_add_#{singularize(self[:name])}"
37
+ self[:_add_method]
30
38
  end
31
39
 
32
40
  # Name symbol for the _remove_all internal association method
33
41
  def _remove_all_method
34
- :"_remove_all_#{self[:name]}"
42
+ self[:_remove_all_method]
35
43
  end
36
44
 
37
45
  # Name symbol for the _remove internal association method
38
46
  def _remove_method
39
- :"_remove_#{singularize(self[:name])}"
47
+ self[:_remove_method]
40
48
  end
41
49
 
42
50
  # Name symbol for the _setter association method
43
51
  def _setter_method
44
- :"_#{self[:name]}="
52
+ self[:_setter_method]
45
53
  end
46
54
 
47
55
  # Name symbol for the add association method
48
56
  def add_method
49
- :"add_#{singularize(self[:name])}"
57
+ self[:add_method]
50
58
  end
51
59
 
52
60
  # Name symbol for association method, the same as the name of the association.
@@ -56,7 +64,13 @@ module Sequel
56
64
 
57
65
  # The class associated to the current model class via this association
58
66
  def associated_class
59
- cached_fetch(:class){constantize(self[:class_name])}
67
+ cached_fetch(:class) do
68
+ begin
69
+ constantize(self[:class_name])
70
+ rescue NameError => e
71
+ raise NameError, "#{e.message} (this happened when attempting to find the associated class for #{inspect})", e.backtrace
72
+ end
73
+ end
60
74
  end
61
75
 
62
76
  # The dataset associated via this association, with the non-instance specific
@@ -68,16 +82,17 @@ module Sequel
68
82
 
69
83
  # Apply all non-instance specific changes to the given dataset and return it.
70
84
  def apply_dataset_changes(ds)
71
- ds.extend(AssociationDatasetMethods)
72
- ds.association_reflection = self
73
- self[:extend].each{|m| ds.extend(m)}
85
+ ds = ds.with_extend(AssociationDatasetMethods).clone(:association_reflection => self)
86
+ if exts = self[:reverse_extend]
87
+ ds = ds.with_extend(*exts)
88
+ end
74
89
  ds = ds.select(*select) if select
75
90
  if c = self[:conditions]
76
91
  ds = (c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.where(*c) : ds.where(c)
77
92
  end
78
93
  ds = ds.order(*self[:order]) if self[:order]
79
94
  ds = ds.limit(*self[:limit]) if self[:limit]
80
- ds = ds.limit(1) if limit_to_single_row?
95
+ ds = ds.limit(1).skip_limit_check if limit_to_single_row?
81
96
  ds = ds.eager(self[:eager]) if self[:eager]
82
97
  ds = ds.distinct if self[:distinct]
83
98
  ds
@@ -129,7 +144,8 @@ module Sequel
129
144
  def apply_window_function_eager_limit_strategy(ds, limit_and_offset=limit_and_offset())
130
145
  rn = ds.row_number_column
131
146
  limit, offset = limit_and_offset
132
- ds = ds.unordered.select_append{|o| o.row_number{}.over(:partition=>predicate_key, :order=>ds.opts[:order]).as(rn)}.from_self
147
+ ds = ds.unordered.select_append{|o| o.row_number.function.over(:partition=>predicate_key, :order=>ds.opts[:order]).as(rn)}.from_self
148
+ ds = ds.order(rn) if ds.db.database_type == :mysql
133
149
  ds = if !returns_array?
134
150
  ds.where(rn => offset ? offset+1 : 1)
135
151
  elsif offset
@@ -148,11 +164,11 @@ module Sequel
148
164
  # range to return the object(s) at the correct offset/limit.
149
165
  def apply_ruby_eager_limit_strategy(rows, limit_and_offset = limit_and_offset())
150
166
  name = self[:name]
167
+ return unless range = slice_range(limit_and_offset)
151
168
  if returns_array?
152
- range = slice_range(limit_and_offset)
153
169
  rows.each{|o| o.associations[name] = o.associations[name][range] || []}
154
- elsif sr = slice_range(limit_and_offset)
155
- offset = sr.begin
170
+ else
171
+ offset = range.begin
156
172
  rows.each{|o| o.associations[name] = o.associations[name][offset]}
157
173
  end
158
174
  end
@@ -178,7 +194,7 @@ module Sequel
178
194
 
179
195
  # Name symbol for the dataset association method
180
196
  def dataset_method
181
- :"#{self[:name]}_dataset"
197
+ self[:dataset_method]
182
198
  end
183
199
 
184
200
  # Whether the dataset needs a primary key to function, true by default.
@@ -197,7 +213,13 @@ module Sequel
197
213
  # Return an dataset that will load the appropriate associated objects for
198
214
  # the given object using this association.
199
215
  def association_dataset_for(object)
200
- associated_dataset.where(predicate_keys.zip(predicate_key_values(object)))
216
+ condition = if can_have_associated_objects?(object)
217
+ predicate_keys.zip(predicate_key_values(object))
218
+ else
219
+ false
220
+ end
221
+
222
+ associated_dataset.where(condition)
201
223
  end
202
224
 
203
225
  ASSOCIATION_DATASET_PROC = proc{|r| r.association_dataset_for(self)}
@@ -241,7 +263,9 @@ module Sequel
241
263
  # yielding each row to the block.
242
264
  def eager_load_results(eo, &block)
243
265
  rows = eo[:rows]
244
- initialize_association_cache(rows) unless eo[:initialize_rows] == false
266
+ unless eo[:initialize_rows] == false
267
+ Sequel.synchronize_with(eo[:mutex]){initialize_association_cache(rows)}
268
+ end
245
269
  if eo[:id_map]
246
270
  ids = eo[:id_map].keys
247
271
  return ids if ids.empty?
@@ -250,13 +274,16 @@ module Sequel
250
274
  cascade = eo[:associations]
251
275
  eager_limit = nil
252
276
 
253
- if eo[:eager_block] || eo[:loader] == false
277
+ if eo[:no_results]
278
+ no_results = true
279
+ elsif eo[:eager_block] || eo[:loader] == false
254
280
  ds = eager_loading_dataset(eo)
255
281
 
256
282
  strategy = ds.opts[:eager_limit_strategy] || strategy
257
283
 
258
284
  eager_limit =
259
285
  if el = ds.opts[:eager_limit]
286
+ raise Error, "The :eager_limit dataset option is not supported for associations returning a single record" unless returns_array?
260
287
  strategy ||= true_eager_graph_limit_strategy
261
288
  if el.is_a?(Array)
262
289
  el
@@ -270,6 +297,7 @@ module Sequel
270
297
  strategy = true_eager_graph_limit_strategy if strategy == :union
271
298
  # Correlated subqueries are not supported for regular eager loading
272
299
  strategy = :ruby if strategy == :correlated_subquery
300
+ strategy = nil if strategy == :ruby && assign_singular?
273
301
  objects = apply_eager_limit_strategy(ds, strategy, eager_limit).all
274
302
  elsif strategy == :union
275
303
  objects = []
@@ -287,7 +315,8 @@ module Sequel
287
315
  objects = loader.all(ids)
288
316
  end
289
317
 
290
- objects.each(&block)
318
+ Sequel.synchronize_with(eo[:mutex]){objects.each(&block)} unless no_results
319
+
291
320
  if strategy == :ruby
292
321
  apply_ruby_eager_limit_strategy(rows, eager_limit || limit_and_offset)
293
322
  end
@@ -304,11 +333,6 @@ module Sequel
304
333
  false
305
334
  end
306
335
 
307
- # Alias of predicate_key, only for backwards compatibility.
308
- def eager_loading_predicate_key
309
- predicate_key
310
- end
311
-
312
336
  # Whether to eagerly graph a lazy dataset, true by default. If this
313
337
  # is false, the association won't respect the :eager_graph option
314
338
  # when loading the association for a single record.
@@ -331,6 +355,44 @@ module Sequel
331
355
  {filter_by_associations_conditions_key=>ds}
332
356
  end
333
357
 
358
+ # Finalize the association by first attempting to populate the thread-safe cache,
359
+ # and then transfering the thread-safe cache value to the association itself,
360
+ # so that a mutex is not needed to get the value.
361
+ def finalize
362
+ return unless cache = self[:cache]
363
+
364
+ finalizer = proc do |meth, key|
365
+ next if has_key?(key)
366
+
367
+ # Allow calling private methods to make sure caching is done appropriately
368
+ send(meth)
369
+ self[key] = cache.delete(key) if cache.has_key?(key)
370
+ end
371
+
372
+ finalize_settings.each(&finalizer)
373
+
374
+ unless self[:instance_specific]
375
+ finalizer.call(:associated_eager_dataset, :associated_eager_dataset)
376
+ finalizer.call(:filter_by_associations_conditions_dataset, :filter_by_associations_conditions_dataset)
377
+ end
378
+
379
+ nil
380
+ end
381
+
382
+ # Map of methods to cache keys used for finalizing associations.
383
+ FINALIZE_SETTINGS = {
384
+ :associated_class=>:class,
385
+ :associated_dataset=>:_dataset,
386
+ :eager_limit_strategy=>:_eager_limit_strategy,
387
+ :placeholder_loader=>:placeholder_loader,
388
+ :predicate_key=>:predicate_key,
389
+ :predicate_keys=>:predicate_keys,
390
+ :reciprocal=>:reciprocal,
391
+ }.freeze
392
+ def finalize_settings
393
+ FINALIZE_SETTINGS
394
+ end
395
+
334
396
  # Whether to handle silent modification failure when adding/removing
335
397
  # associated records, false by default.
336
398
  def handle_silent_modification_failure?
@@ -347,6 +409,18 @@ module Sequel
347
409
  end
348
410
  end
349
411
 
412
+ # Show which type of reflection this is, and a guess at what code was used to create the
413
+ # association.
414
+ def inspect
415
+ o = self[:orig_opts].dup
416
+ o.delete(:class)
417
+ o.delete(:class_name)
418
+ o.delete(:block) unless o[:block]
419
+ o[:class] = self[:orig_class] if self[:orig_class]
420
+
421
+ "#<#{self.class} #{self[:model]}.#{self[:type]} #{self[:name].inspect}#{", #{o.inspect[1...-1]}" unless o.empty?}>"
422
+ end
423
+
350
424
  # The limit and offset for this association (returned as a two element array).
351
425
  def limit_and_offset
352
426
  if (v = self[:limit]).is_a?(Array)
@@ -368,7 +442,11 @@ module Sequel
368
442
  if use_placeholder_loader?
369
443
  cached_fetch(:placeholder_loader) do
370
444
  Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds|
371
- ds.where(*predicate_keys.map{|k| SQL::BooleanExpression.new(:'=', k, pl.arg)})
445
+ ds = ds.where(Sequel.&(*predicate_keys.map{|k| SQL::BooleanExpression.new(:'=', k, pl.arg)}))
446
+ if self[:block]
447
+ ds = self[:block].call(ds)
448
+ end
449
+ ds
372
450
  end
373
451
  end
374
452
  end
@@ -384,16 +462,14 @@ module Sequel
384
462
  predicate_key_methods.map{|k| object.get_column_value(k)}
385
463
  end
386
464
 
387
- # Qualify +col+ with the given table name. If +col+ is an array of columns,
388
- # return an array of qualified columns. Only qualifies Symbols and SQL::Identifier
389
- # values, other values are not modified.
465
+ # Qualify +col+ with the given table name.
390
466
  def qualify(table, col)
391
467
  transform(col) do |k|
392
468
  case k
393
469
  when Symbol, SQL::Identifier
394
470
  SQL::QualifiedIdentifier.new(table, k)
395
471
  else
396
- Sequel::Qualifier.new(self[:model].dataset, table).transform(k)
472
+ Sequel::Qualifier.new(table).transform(k)
397
473
  end
398
474
  end
399
475
  end
@@ -439,7 +515,7 @@ module Sequel
439
515
 
440
516
  # Name symbol for the remove_all_ association method
441
517
  def remove_all_method
442
- :"remove_all_#{self[:name]}"
518
+ self[:remove_all_method]
443
519
  end
444
520
 
445
521
  # Whether associated objects need to be removed from the association before
@@ -450,7 +526,7 @@ module Sequel
450
526
 
451
527
  # Name symbol for the remove_ association method
452
528
  def remove_method
453
- :"remove_#{singularize(self[:name])}"
529
+ self[:remove_method]
454
530
  end
455
531
 
456
532
  # Whether to check that an object to be disassociated is already associated to this object, false by default.
@@ -477,7 +553,7 @@ module Sequel
477
553
 
478
554
  # Name symbol for the setter association method
479
555
  def setter_method
480
- :"#{self[:name]}="
556
+ self[:setter_method]
481
557
  end
482
558
 
483
559
  # The range used for slicing when using the :ruby eager limit strategy.
@@ -490,8 +566,10 @@ module Sequel
490
566
 
491
567
  private
492
568
 
493
- # On non-GVL rubies, assume the need to synchronize access. Store the key
494
- # in a special sub-hash that always uses this method to synchronize access.
569
+ # If the key exists in the reflection hash, return it.
570
+ # If the key doesn't exist and association reflections are uncached, then yield to get the value.
571
+ # If the key doesn't exist and association reflection are cached, check the cache and return
572
+ # the value if present, or yield to get the value, cache the value, and return it.
495
573
  def cached_fetch(key)
496
574
  fetch(key) do
497
575
  return yield unless h = self[:cache]
@@ -501,7 +579,7 @@ module Sequel
501
579
  end
502
580
  end
503
581
 
504
- # Cache the value at the given key, synchronizing access.
582
+ # Cache the value at the given key if caching.
505
583
  def cached_set(key, value)
506
584
  return unless h = self[:cache]
507
585
  Sequel.synchronize{h[key] = value}
@@ -510,10 +588,10 @@ module Sequel
510
588
  # The base dataset used for the association, before any order/conditions
511
589
  # options have been applied.
512
590
  def _associated_dataset
513
- associated_class.dataset.clone
591
+ associated_class.dataset
514
592
  end
515
593
 
516
- # Whether for the reciprocal type for the given association can not be
594
+ # Whether for the reciprocal type for the given association cannot be
517
595
  # known in advantage, false by default.
518
596
  def ambiguous_reciprocal_type?
519
597
  false
@@ -562,17 +640,20 @@ module Sequel
562
640
  # given the hash passed to the eager loader.
563
641
  def eager_loading_dataset(eo=OPTS)
564
642
  ds = eo[:dataset] || associated_eager_dataset
565
- if id_map = eo[:id_map]
566
- ds = ds.where(eager_loading_predicate_condition(id_map.keys))
567
- end
643
+ ds = eager_loading_set_predicate_condition(ds, eo)
568
644
  if associations = eo[:associations]
569
645
  ds = ds.eager(associations)
570
646
  end
571
647
  if block = eo[:eager_block]
648
+ orig_ds = ds
572
649
  ds = block.call(ds)
573
650
  end
574
651
  if eager_loading_use_associated_key?
575
- ds = ds.select_append(*associated_key_array)
652
+ ds = if ds.opts[:eager_graph] && !orig_ds.opts[:eager_graph]
653
+ block.call(orig_ds.select_append(*associated_key_array))
654
+ else
655
+ ds.select_append(*associated_key_array)
656
+ end
576
657
  end
577
658
  if self[:eager_graph]
578
659
  raise(Error, "cannot eagerly load a #{self[:type]} association that uses :eager_graph") if eager_loading_use_associated_key?
@@ -586,6 +667,15 @@ module Sequel
586
667
  self[:model].default_eager_limit_strategy || :ruby
587
668
  end
588
669
 
670
+ # Set the predicate condition for the eager loading dataset based on the id map
671
+ # in the eager loading options.
672
+ def eager_loading_set_predicate_condition(ds, eo)
673
+ if id_map = eo[:id_map]
674
+ ds = ds.where(eager_loading_predicate_condition(id_map.keys))
675
+ end
676
+ ds
677
+ end
678
+
589
679
  # The predicate condition to use for the eager_loader.
590
680
  def eager_loading_predicate_condition(keys)
591
681
  {predicate_key=>keys}
@@ -630,14 +720,12 @@ module Sequel
630
720
  v = fetch(:filter_limit_strategy, self[:eager_limit_strategy])
631
721
  if v || self[:limit] || !returns_array?
632
722
  case v ||= self[:model].default_eager_limit_strategy
633
- when :union, :ruby
723
+ when true, :union, :ruby
634
724
  # Can't use a union or ruby-based strategy for filtering by associations, switch to default eager graph limit
635
725
  # strategy.
636
726
  true_eager_graph_limit_strategy
637
727
  when Symbol
638
728
  v
639
- when true
640
- true_eager_graph_limit_strategy
641
729
  end
642
730
  end
643
731
  end
@@ -685,8 +773,8 @@ module Sequel
685
773
 
686
774
  # If +s+ is an array, map +s+ over the block. Otherwise, just call the
687
775
  # block with +s+.
688
- def transform(s)
689
- s.is_a?(Array) ? s.map(&Proc.new) : (yield s)
776
+ def transform(s, &block)
777
+ s.is_a?(Array) ? s.map(&block) : (yield s)
690
778
  end
691
779
 
692
780
  # What eager limit strategy should be used when true is given as the value,
@@ -729,7 +817,7 @@ module Sequel
729
817
 
730
818
  # Whether the placeholder loader can be used to load the association.
731
819
  def use_placeholder_loader?
732
- !self[:instance_specific] && !self[:eager_graph]
820
+ self[:use_placeholder_loader]
733
821
  end
734
822
  end
735
823
 
@@ -774,6 +862,18 @@ module Sequel
774
862
  nil
775
863
  end
776
864
 
865
+ FINALIZE_SETTINGS = superclass::FINALIZE_SETTINGS.merge(
866
+ :primary_key=>:primary_key,
867
+ :primary_keys=>:primary_keys,
868
+ :primary_key_method=>:primary_key_method,
869
+ :primary_key_methods=>:primary_key_methods,
870
+ :qualified_primary_key=>:qualified_primary_key,
871
+ :reciprocal_type=>:reciprocal_type
872
+ ).freeze
873
+ def finalize_settings
874
+ FINALIZE_SETTINGS
875
+ end
876
+
777
877
  # The expression to use on the left hand side of the IN lookup when eager loading
778
878
  def predicate_key
779
879
  cached_fetch(:predicate_key){qualified_primary_key}
@@ -918,6 +1018,13 @@ module Sequel
918
1018
  :"#{underscore(demodulize(self[:model].name))}_id"
919
1019
  end
920
1020
 
1021
+ FINALIZE_SETTINGS = superclass::FINALIZE_SETTINGS.merge(
1022
+ :qualified_primary_key=>:qualified_primary_key
1023
+ ).freeze
1024
+ def finalize_settings
1025
+ FINALIZE_SETTINGS
1026
+ end
1027
+
921
1028
  # Handle silent failure of add/remove methods if raise_on_save_failure is false.
922
1029
  def handle_silent_modification_failure?
923
1030
  self[:raise_on_save_failure] == false
@@ -1023,7 +1130,7 @@ module Sequel
1023
1130
  end
1024
1131
 
1025
1132
  # Support automatic use of correlated subqueries if :ruby option is best available option,
1026
- # MySQL is not being used, and either the associated class has a non-composite primary key
1133
+ # the database supports them, and either the associated class has a non-composite primary key
1027
1134
  # or the database supports multiple columns in IN.
1028
1135
  def true_eager_graph_limit_strategy
1029
1136
  r = super
@@ -1158,7 +1265,9 @@ module Sequel
1158
1265
  else
1159
1266
  assoc_record.values.delete(left_key_alias)
1160
1267
  end
1161
- next unless objects = h[hash_key]
1268
+
1269
+ objects = h[hash_key]
1270
+
1162
1271
  if assign_singular
1163
1272
  objects.each do |object|
1164
1273
  object.associations[name] ||= assoc_record
@@ -1188,6 +1297,22 @@ module Sequel
1188
1297
  :"#{singularize(self[:name])}_id"
1189
1298
  end
1190
1299
 
1300
+ FINALIZE_SETTINGS = superclass::FINALIZE_SETTINGS.merge(
1301
+ :associated_key_array=>:associated_key_array,
1302
+ :qualified_right_key=>:qualified_right_key,
1303
+ :join_table_source=>:join_table_source,
1304
+ :join_table_alias=>:join_table_alias,
1305
+ :qualified_right_primary_key=>:qualified_right_primary_key,
1306
+ :right_primary_key=>:right_primary_key,
1307
+ :right_primary_keys=>:right_primary_keys,
1308
+ :right_primary_key_method=>:right_primary_key_method,
1309
+ :right_primary_key_methods=>:right_primary_key_methods,
1310
+ :select=>:select
1311
+ ).freeze
1312
+ def finalize_settings
1313
+ FINALIZE_SETTINGS
1314
+ end
1315
+
1191
1316
  # The hash key to use for the eager loading predicate (left side of IN (1, 2, 3)).
1192
1317
  # The left key qualified by the join table.
1193
1318
  def predicate_key
@@ -1202,7 +1327,7 @@ module Sequel
1202
1327
 
1203
1328
  # many_to_many associations need to select a key in an associated table to eagerly load
1204
1329
  def eager_loading_use_associated_key?
1205
- true
1330
+ !separate_query_per_table?
1206
1331
  end
1207
1332
 
1208
1333
  # The source of the join table. This is the join table itself, unless it
@@ -1259,10 +1384,30 @@ module Sequel
1259
1384
  cached_fetch(:select){default_select}
1260
1385
  end
1261
1386
 
1387
+ # Whether a separate query should be used for the join table.
1388
+ def separate_query_per_table?
1389
+ self[:join_table_db]
1390
+ end
1391
+
1262
1392
  private
1263
1393
 
1394
+ # Join to the the join table, unless using a separate query per table.
1264
1395
  def _associated_dataset
1265
- super.inner_join(self[:join_table], self[:right_keys].zip(right_primary_keys), :qualify=>:deep)
1396
+ if separate_query_per_table?
1397
+ super
1398
+ else
1399
+ super.inner_join(self[:join_table], self[:right_keys].zip(right_primary_keys), :qualify=>:deep)
1400
+ end
1401
+ end
1402
+
1403
+ # Use the right_keys from the eager loading options if
1404
+ # using a separate query per table.
1405
+ def eager_loading_set_predicate_condition(ds, eo)
1406
+ if separate_query_per_table?
1407
+ ds.where(right_primary_key=>eo[:right_keys])
1408
+ else
1409
+ super
1410
+ end
1266
1411
  end
1267
1412
 
1268
1413
  # The default selection for associations that require joins. These do not use the default
@@ -1353,10 +1498,20 @@ module Sequel
1353
1498
  # This module contains methods added to all association datasets
1354
1499
  module AssociationDatasetMethods
1355
1500
  # The model object that created the association dataset
1356
- attr_accessor :model_object
1501
+ def model_object
1502
+ @opts[:model_object]
1503
+ end
1357
1504
 
1358
1505
  # The association reflection related to the association dataset
1359
- attr_accessor :association_reflection
1506
+ def association_reflection
1507
+ @opts[:association_reflection]
1508
+ end
1509
+
1510
+ private
1511
+
1512
+ def non_sql_option?(key)
1513
+ super || key == :model_object || key == :association_reflection
1514
+ end
1360
1515
  end
1361
1516
 
1362
1517
  # Each kind of association adds a number of instance methods to the model class which
@@ -1397,7 +1552,7 @@ module Sequel
1397
1552
  # Project.associations
1398
1553
  # => [:portfolio, :milestones]
1399
1554
  # Project.association_reflection(:portfolio)
1400
- # => {:type => :many_to_one, :name => :portfolio, ...}
1555
+ # => #<Sequel::Model::Associations::ManyToOneAssociationReflection Project.many_to_one :portfolio>
1401
1556
  #
1402
1557
  # Associations should not have the same names as any of the columns in the
1403
1558
  # model's current table they reference. If you are dealing with an existing schema that
@@ -1419,13 +1574,19 @@ module Sequel
1419
1574
 
1420
1575
  # Whether association metadata should be cached in the association reflection. If not cached, it will be computed
1421
1576
  # on demand. In general you only want to set this to false when using code reloading. When using code reloading,
1422
- # setting this will make sure that if an associated class is removed or modified, this class will not hang on to
1577
+ # setting this will make sure that if an associated class is removed or modified, this class will not have a reference to
1423
1578
  # the previous class.
1424
1579
  attr_accessor :cache_associations
1425
1580
 
1426
- # The default options to use for all associations.
1581
+ # The default options to use for all associations. This hash is merged into the association reflection hash for
1582
+ # all association reflections.
1427
1583
  attr_accessor :default_association_options
1428
1584
 
1585
+ # The default options to use for all associations of a given type. This is a hash keyed by association type
1586
+ # symbol. If there is a value for the association type symbol key, the resulting hash will be merged into the
1587
+ # association reflection hash for all association reflections of that type.
1588
+ attr_accessor :default_association_type_options
1589
+
1429
1590
  # The default :eager_limit_strategy option to use for limited or offset associations (default: true, causing Sequel
1430
1591
  # to use what it considers the most appropriate strategy).
1431
1592
  attr_accessor :default_eager_limit_strategy
@@ -1463,6 +1624,7 @@ module Sequel
1463
1624
  # === Multiple Types
1464
1625
  # :adder :: Proc used to define the private _add_* method for doing the database work
1465
1626
  # to associate the given object to the current object (*_to_many assocations).
1627
+ # Set to nil to not define a add_* method for the association.
1466
1628
  # :after_add :: Symbol, Proc, or array of both/either specifying a callback to call
1467
1629
  # after a new item is added to the association.
1468
1630
  # :after_load :: Symbol, Proc, or array of both/either specifying a callback to call
@@ -1473,6 +1635,8 @@ module Sequel
1473
1635
  # after an item is set using the association setter method.
1474
1636
  # :allow_eager :: If set to false, you cannot load the association eagerly
1475
1637
  # via eager or eager_graph
1638
+ # :allow_eager_graph :: If set to false, you cannot load the association eagerly via eager_graph.
1639
+ # :allow_filtering_by :: If set to false, you cannot use the association when filtering
1476
1640
  # :before_add :: Symbol, Proc, or array of both/either specifying a callback to call
1477
1641
  # before a new item is added to the association.
1478
1642
  # :before_remove :: Symbol, Proc, or array of both/either specifying a callback to call
@@ -1488,16 +1652,22 @@ module Sequel
1488
1652
  # singularized unless the type is :many_to_one, :one_to_one, or one_through_one). If this is specified
1489
1653
  # as a string or symbol, you must specify the full class name (e.g. "::SomeModule::MyModel").
1490
1654
  # :class_namespace :: If :class is given as a string or symbol, sets the default namespace in which to look for
1491
- # the class. <tt>:class=>'Foo', :class_namespace=>'Bar'</tt> looks for <tt>::Bar::Foo</tt>.)
1655
+ # the class. <tt>class: 'Foo', class_namespace: 'Bar'</tt> looks for <tt>::Bar::Foo</tt>.)
1492
1656
  # :clearer :: Proc used to define the private _remove_all_* method for doing the database work
1493
1657
  # to remove all objects associated to the current object (*_to_many assocations).
1658
+ # Set to nil to not define a remove_all_* method for the association.
1494
1659
  # :clone :: Merge the current options and block into the options and block used in defining
1495
1660
  # the given association. Can be used to DRY up a bunch of similar associations that
1496
1661
  # all share the same options such as :class and :key, while changing the order and block used.
1497
1662
  # :conditions :: The conditions to use to filter the association, can be any argument passed to where.
1498
- # :dataset :: A proc that is instance_execed to get the base dataset to use (before the other
1663
+ # This option is not respected when using eager_graph or association_join, unless it
1664
+ # is hash or array of two element arrays. Consider also specifying the :graph_block
1665
+ # option if the value for this option is not a hash or array of two element arrays
1666
+ # and you plan to use this association in eager_graph or association_join.
1667
+ # :dataset :: A proc that is used to define the method to get the base dataset to use (before the other
1499
1668
  # options are applied). If the proc accepts an argument, it is passed the related
1500
- # association reflection.
1669
+ # association reflection. It is a best practice to always have the dataset accept an argument
1670
+ # and use the argument to return the appropriate dataset.
1501
1671
  # :distinct :: Use the DISTINCT clause when selecting associating object, both when
1502
1672
  # lazy loading and eager loading via .eager (but not when using .eager_graph).
1503
1673
  # :eager :: The associations to eagerly load via +eager+ when loading the associated object(s).
@@ -1540,31 +1710,37 @@ module Sequel
1540
1710
  # :graph_only_conditions :: The conditions to use on the SQL join when eagerly loading
1541
1711
  # the association via +eager_graph+, instead of the default conditions specified by the
1542
1712
  # foreign/primary keys. This option causes the :graph_conditions option to be ignored.
1543
- # :graph_order :: Over the order to use when using eager_graph, instead of the default order. This should be used
1713
+ # :graph_order :: the order to use when using eager_graph, instead of the default order. This should be used
1544
1714
  # in the case where :order contains an identifier qualified by the table's name, which may not match
1545
1715
  # the alias used when eager graphing. By setting this to the unqualified identifier, it will be
1546
1716
  # automatically qualified when using eager_graph.
1547
1717
  # :graph_select :: A column or array of columns to select from the associated table
1548
1718
  # when eagerly loading the association via +eager_graph+. Defaults to all
1549
1719
  # columns in the associated table.
1720
+ # :instance_specific :: Marks the association as instance specific. Should be used if the association block
1721
+ # uses instance specific state, or transient state (accessing current date/time, etc.).
1550
1722
  # :limit :: Limit the number of records to the provided value. Use
1551
1723
  # an array with two elements for the value to specify a
1552
1724
  # limit (first element) and an offset (second element).
1553
1725
  # :methods_module :: The module that methods the association creates will be placed into. Defaults
1554
1726
  # to the module containing the model's columns.
1727
+ # :no_association_method :: Do not add a method for the association. This can save memory if the association
1728
+ # method is never used.
1729
+ # :no_dataset_method :: Do not add a method for the association dataset. This can save memory if the dataset
1730
+ # method is never used.
1555
1731
  # :order :: the column(s) by which to order the association dataset. Can be a
1556
1732
  # singular column symbol or an array of column symbols.
1557
1733
  # :order_eager_graph :: Whether to add the association's order to the graphed dataset's order when graphing
1558
1734
  # via +eager_graph+. Defaults to true, so set to false to disable.
1559
1735
  # :read_only :: Do not add a setter method (for many_to_one or one_to_one associations),
1560
- # or add_/remove_/remove_all_ methods (for one_to_many and many_to_many associations). Always
1561
- # true for one_through_one associations.
1736
+ # or add_/remove_/remove_all_ methods (for one_to_many and many_to_many associations).
1562
1737
  # :reciprocal :: the symbol name of the reciprocal association,
1563
1738
  # if it exists. By default, Sequel will try to determine it by looking at the
1564
1739
  # associated model's assocations for a association that matches
1565
1740
  # the current association's key(s). Set to nil to not use a reciprocal.
1566
1741
  # :remover :: Proc used to define the private _remove_* method for doing the database work
1567
1742
  # to remove the association between the given object and the current object (*_to_many assocations).
1743
+ # Set to nil to not define a remove_* method for the association.
1568
1744
  # :select :: the columns to select. Defaults to the associated class's table_name.* in an association
1569
1745
  # that uses joins, which means it doesn't include the attributes from the
1570
1746
  # join table. If you want to include the join table attributes, you can
@@ -1573,6 +1749,7 @@ module Sequel
1573
1749
  # the same name in both the join table and the associated table.
1574
1750
  # :setter :: Proc used to define the private _*= method for doing the work to setup the assocation
1575
1751
  # between the given object and the current object (*_to_one associations).
1752
+ # Set to nil to not define a setter method for the association.
1576
1753
  # :subqueries_per_union :: The number of subqueries to use in each UNION query, for eager
1577
1754
  # loading limited associations using the default :union strategy.
1578
1755
  # :validate :: Set to false to not validate when implicitly saving any associated object.
@@ -1589,7 +1766,7 @@ module Sequel
1589
1766
  # array of symbols for a composite key association.
1590
1767
  # :primary_key_method :: the method symbol or array of method symbols to call on the associated
1591
1768
  # object to get the foreign key values. Defaults to :primary_key option.
1592
- # :qualify :: Whether to use qualifier primary keys when loading the association. The default
1769
+ # :qualify :: Whether to use qualified primary keys when loading the association. The default
1593
1770
  # is true, so you must set to false to not qualify. Qualification rarely causes
1594
1771
  # problems, but it's necessary to disable in some cases, such as when you are doing
1595
1772
  # a JOIN USING operation on the column on Oracle.
@@ -1629,6 +1806,9 @@ module Sequel
1629
1806
  # underscored, sorted, and joined with '_'.
1630
1807
  # :join_table_block :: proc that can be used to modify the dataset used in the add/remove/remove_all
1631
1808
  # methods. Should accept a dataset argument and return a modified dataset if present.
1809
+ # :join_table_db :: When retrieving records when using lazy loading or eager loading via +eager+, instead of
1810
+ # a join between to the join table and the associated table, use a separate query for the
1811
+ # join table using the given Database object.
1632
1812
  # :left_key :: foreign key in join table that points to current model's
1633
1813
  # primary key, as a symbol. Defaults to :"#{self.name.underscore}_id".
1634
1814
  # Can use an array of symbols for a composite key association.
@@ -1650,7 +1830,7 @@ module Sequel
1650
1830
  # Defaults to :right_primary_key option.
1651
1831
  # :uniq :: Adds a after_load callback that makes the array of objects unique.
1652
1832
  def associate(type, name, opts = OPTS, &block)
1653
- raise(Error, 'invalid association type') unless assoc_class = ASSOCIATION_TYPES[type]
1833
+ raise(Error, 'invalid association type') unless assoc_class = Sequel.synchronize{ASSOCIATION_TYPES[type]}
1654
1834
  raise(Error, 'Model.associate name argument must be a symbol') unless name.is_a?(Symbol)
1655
1835
 
1656
1836
  # dup early so we don't modify opts
@@ -1658,16 +1838,35 @@ module Sequel
1658
1838
 
1659
1839
  if opts[:clone]
1660
1840
  cloned_assoc = association_reflection(opts[:clone])
1841
+ remove_class_name = orig_opts[:class] && !orig_opts[:class_name]
1661
1842
  orig_opts = cloned_assoc[:orig_opts].merge(orig_opts)
1843
+ orig_opts.delete(:class_name) if remove_class_name
1662
1844
  end
1663
1845
 
1664
- opts = default_association_options.merge(orig_opts).merge(:type => type, :name => name, :cache=>({} if cache_associations), :model => self)
1846
+ opts = Hash[default_association_options]
1847
+ if type_options = default_association_type_options[type]
1848
+ opts.merge!(type_options)
1849
+ end
1850
+ opts.merge!(orig_opts)
1851
+ opts.merge!(:type => type, :name => name, :cache=>({} if cache_associations), :model => self)
1852
+
1665
1853
  opts[:block] = block if block
1666
- if !opts.has_key?(:instance_specific) && (block || orig_opts[:block] || orig_opts[:dataset])
1854
+ opts[:instance_specific] = true if orig_opts[:dataset]
1855
+ if !opts.has_key?(:instance_specific) && (block || orig_opts[:block])
1667
1856
  # It's possible the association is instance specific, in that it depends on
1668
1857
  # values other than the foreign key value. This needs to be checked for
1669
1858
  # in certain places to disable optimizations.
1670
- opts[:instance_specific] = true
1859
+ opts[:instance_specific] = _association_instance_specific_default(name)
1860
+ end
1861
+ if (orig_opts[:instance_specific] || orig_opts[:dataset]) && !opts.has_key?(:allow_eager) && !opts[:eager_loader]
1862
+ # For associations explicitly marked as instance specific, or that use the
1863
+ # :dataset option, where :allow_eager is not set, and no :eager_loader is
1864
+ # provided, disallow eager loading. In these cases, eager loading is
1865
+ # unlikely to work. This is not done for implicit setting of :instance_specific,
1866
+ # because implicit use is done by default for all associations with blocks,
1867
+ # and the vast majority of associations with blocks use the block for filtering
1868
+ # in a manner compatible with eager loading.
1869
+ opts[:allow_eager] = false
1671
1870
  end
1672
1871
  opts = assoc_class.new.merge!(opts)
1673
1872
 
@@ -1675,10 +1874,8 @@ module Sequel
1675
1874
  raise(Error, "cannot clone an association to an association of different type (association #{name} with type #{type} cloning #{opts[:clone]} with type #{cloned_assoc[:type]})")
1676
1875
  end
1677
1876
 
1877
+ opts[:use_placeholder_loader] = !opts[:instance_specific] && !opts[:eager_graph]
1678
1878
  opts[:eager_block] = opts[:block] unless opts.include?(:eager_block)
1679
- if !opts.has_key?(:predicate_key) && opts.has_key?(:eager_loading_predicate_key)
1680
- opts[:predicate_key] = opts[:eager_loading_predicate_key]
1681
- end
1682
1879
  opts[:graph_join_type] ||= :left_outer
1683
1880
  opts[:order_eager_graph] = true unless opts.include?(:order_eager_graph)
1684
1881
  conds = opts[:conditions]
@@ -1686,18 +1883,24 @@ module Sequel
1686
1883
  opts[:graph_conditions] = conds if !opts.include?(:graph_conditions) and Sequel.condition_specifier?(conds)
1687
1884
  opts[:graph_conditions] = opts.fetch(:graph_conditions, []).to_a
1688
1885
  opts[:graph_select] = Array(opts[:graph_select]) if opts[:graph_select]
1689
- [:before_add, :before_remove, :after_add, :after_remove, :after_load, :before_set, :after_set, :extend].each do |cb_type|
1690
- opts[cb_type] = Array(opts[cb_type])
1886
+ [:before_add, :before_remove, :after_add, :after_remove, :after_load, :before_set, :after_set].each do |cb_type|
1887
+ opts[cb_type] = Array(opts[cb_type]) if opts[cb_type]
1888
+ end
1889
+
1890
+ if opts[:extend]
1891
+ opts[:extend] = Array(opts[:extend])
1892
+ opts[:reverse_extend] = opts[:extend].reverse
1691
1893
  end
1894
+
1692
1895
  late_binding_class_option(opts, opts.returns_array? ? singularize(name) : name)
1693
1896
 
1694
1897
  # Remove :class entry if it exists and is nil, to work with cached_fetch
1695
1898
  opts.delete(:class) unless opts[:class]
1696
1899
 
1697
- send(:"def_#{type}", opts)
1698
- def_association_instance_methods(opts)
1900
+ def_association(opts)
1699
1901
 
1700
1902
  orig_opts.delete(:clone)
1903
+ opts[:orig_class] = orig_opts[:class] || orig_opts[:class_name]
1701
1904
  orig_opts.merge!(:class_name=>opts[:class_name], :class=>opts[:class], :block=>opts[:block])
1702
1905
  opts[:orig_opts] = orig_opts
1703
1906
  # don't add to association_reflections until we are sure there are no errors
@@ -1719,6 +1922,25 @@ module Sequel
1719
1922
  opts.eager_load_results(eo, &block)
1720
1923
  end
1721
1924
 
1925
+ # Freeze association related metadata when freezing model class.
1926
+ def freeze
1927
+ @association_reflections.freeze.each_value(&:freeze)
1928
+ @autoreloading_associations.freeze.each_value(&:freeze)
1929
+ @default_association_options.freeze
1930
+ @default_association_type_options.freeze
1931
+ @default_association_type_options.each_value(&:freeze)
1932
+
1933
+ super
1934
+ end
1935
+
1936
+ # Finalize all associations such that values that are looked up
1937
+ # dynamically in associated classes are set statically.
1938
+ # As this modifies the associations, it must be done before
1939
+ # calling freeze.
1940
+ def finalize_associations
1941
+ @association_reflections.each_value(&:finalize)
1942
+ end
1943
+
1722
1944
  # Shortcut for adding a many_to_many association, see #associate
1723
1945
  def many_to_many(name, opts=OPTS, &block)
1724
1946
  associate(:many_to_many, name, opts, &block)
@@ -1729,7 +1951,7 @@ module Sequel
1729
1951
  associate(:many_to_one, name, opts, &block)
1730
1952
  end
1731
1953
 
1732
- # Shortcut for adding a one_through_one association, see #associate.
1954
+ # Shortcut for adding a one_through_one association, see #associate
1733
1955
  def one_through_one(name, opts=OPTS, &block)
1734
1956
  associate(:one_through_one, name, opts, &block)
1735
1957
  end
@@ -1739,15 +1961,21 @@ module Sequel
1739
1961
  associate(:one_to_many, name, opts, &block)
1740
1962
  end
1741
1963
 
1742
- # Shortcut for adding a one_to_one association, see #associate.
1964
+ # Shortcut for adding a one_to_one association, see #associate
1743
1965
  def one_to_one(name, opts=OPTS, &block)
1744
1966
  associate(:one_to_one, name, opts, &block)
1745
1967
  end
1746
1968
 
1747
- Plugins.inherited_instance_variables(self, :@association_reflections=>:dup, :@autoreloading_associations=>:hash_dup, :@default_association_options=>:dup, :@cache_associations=>nil, :@default_eager_limit_strategy=>nil)
1969
+ Plugins.inherited_instance_variables(self, :@association_reflections=>:dup, :@autoreloading_associations=>:hash_dup, :@default_association_options=>:dup, :@default_association_type_options=>:hash_dup, :@cache_associations=>nil, :@default_eager_limit_strategy=>nil)
1748
1970
  Plugins.def_dataset_methods(self, [:eager, :eager_graph, :eager_graph_with_options, :association_join, :association_full_join, :association_inner_join, :association_left_join, :association_right_join])
1749
1971
 
1750
1972
  private
1973
+
1974
+ # The default value for the instance_specific option, if the association
1975
+ # could be instance specific and the :instance_specific option is not specified.
1976
+ def _association_instance_specific_default(_)
1977
+ true
1978
+ end
1751
1979
 
1752
1980
  # The module to use for the association's methods. Defaults to
1753
1981
  # the overridable_methods_module.
@@ -1759,7 +1987,22 @@ module Sequel
1759
1987
  # can be easily overridden in the class itself while allowing for
1760
1988
  # super to be called.
1761
1989
  def association_module_def(name, opts=OPTS, &block)
1762
- association_module(opts).module_eval{define_method(name, &block)}
1990
+ mod = association_module(opts)
1991
+ mod.send(:define_method, name, &block)
1992
+ mod.send(:alias_method, name, name)
1993
+ end
1994
+
1995
+ # Add a method to the module included in the class, so the method
1996
+ # can be easily overridden in the class itself while allowing for
1997
+ # super to be called. This method allows passing keywords through
1998
+ # the defined methods.
1999
+ def association_module_delegate_def(name, opts, &block)
2000
+ mod = association_module(opts)
2001
+ mod.send(:define_method, name, &block)
2002
+ # :nocov:
2003
+ mod.send(:ruby2_keywords, name) if mod.respond_to?(:ruby2_keywords, true)
2004
+ # :nocov:
2005
+ mod.send(:alias_method, name, name)
1763
2006
  end
1764
2007
 
1765
2008
  # Add a private method to the module included in the class.
@@ -1768,37 +2011,67 @@ module Sequel
1768
2011
  association_module(opts).send(:private, name)
1769
2012
  end
1770
2013
 
2014
+ # Delegate to the type-specific association method to setup the
2015
+ # association, and define the association instance methods.
2016
+ def def_association(opts)
2017
+ send(:"def_#{opts[:type]}", opts)
2018
+ def_association_instance_methods(opts)
2019
+ end
2020
+
1771
2021
  # Adds the association method to the association methods module.
1772
2022
  def def_association_method(opts)
1773
- association_module_def(opts.association_method, opts){|*dynamic_opts, &block| load_associated_objects(opts, dynamic_opts[0], &block)}
2023
+ association_module_def(opts.association_method, opts) do |dynamic_opts=OPTS, &block|
2024
+ load_associated_objects(opts, dynamic_opts, &block)
2025
+ end
1774
2026
  end
1775
2027
 
1776
2028
  # Define all of the association instance methods for this association.
1777
2029
  def def_association_instance_methods(opts)
1778
- association_module_def(opts.dataset_method, opts){_dataset(opts)}
1779
- def_association_method(opts)
2030
+ # Always set the method names in the association reflection, even if they
2031
+ # are not used, for backwards compatibility.
2032
+ opts[:dataset_method] = :"#{opts[:name]}_dataset"
2033
+ if opts.returns_array?
2034
+ sname = singularize(opts[:name])
2035
+ opts[:_add_method] = :"_add_#{sname}"
2036
+ opts[:add_method] = :"add_#{sname}"
2037
+ opts[:_remove_method] = :"_remove_#{sname}"
2038
+ opts[:remove_method] = :"remove_#{sname}"
2039
+ opts[:_remove_all_method] = :"_remove_all_#{opts[:name]}"
2040
+ opts[:remove_all_method] = :"remove_all_#{opts[:name]}"
2041
+ else
2042
+ opts[:_setter_method] = :"_#{opts[:name]}="
2043
+ opts[:setter_method] = :"#{opts[:name]}="
2044
+ end
2045
+
2046
+ association_module_def(opts.dataset_method, opts){_dataset(opts)} unless opts[:no_dataset_method]
2047
+ if opts[:block]
2048
+ opts[:block_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_block", 1, &opts[:block])
2049
+ end
2050
+ opts[:dataset_opt_arity] = opts[:dataset].arity == 0 ? 0 : 1
2051
+ opts[:dataset_opt_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_dataset_opt", opts[:dataset_opt_arity], &opts[:dataset])
2052
+ def_association_method(opts) unless opts[:no_association_method]
1780
2053
 
1781
2054
  return if opts[:read_only]
1782
2055
 
1783
2056
  if opts[:setter] && opts[:_setter]
1784
2057
  # This is backwards due to backwards compatibility
1785
- association_module_private_def(opts._setter_method, opts, &opts[:setter])
1786
- association_module_def(opts.setter_method, opts, &opts[:_setter])
2058
+ association_module_private_def(opts[:_setter_method], opts, &opts[:setter])
2059
+ association_module_def(opts[:setter_method], opts, &opts[:_setter])
1787
2060
  end
1788
2061
 
1789
2062
  if adder = opts[:adder]
1790
- association_module_private_def(opts._add_method, opts, &adder)
1791
- association_module_def(opts.add_method, opts){|o,*args| add_associated_object(opts, o, *args)}
2063
+ association_module_private_def(opts[:_add_method], opts, &adder)
2064
+ association_module_delegate_def(opts[:add_method], opts){|o,*args| add_associated_object(opts, o, *args)}
1792
2065
  end
1793
2066
 
1794
2067
  if remover = opts[:remover]
1795
- association_module_private_def(opts._remove_method, opts, &remover)
1796
- association_module_def(opts.remove_method, opts){|o,*args| remove_associated_object(opts, o, *args)}
2068
+ association_module_private_def(opts[:_remove_method], opts, &remover)
2069
+ association_module_delegate_def(opts[:remove_method], opts){|o,*args| remove_associated_object(opts, o, *args)}
1797
2070
  end
1798
2071
 
1799
2072
  if clearer = opts[:clearer]
1800
- association_module_private_def(opts._remove_all_method, opts, &clearer)
1801
- association_module_def(opts.remove_all_method, opts){|*args| remove_all_associated_objects(opts, *args)}
2073
+ association_module_private_def(opts[:_remove_all_method], opts, &clearer)
2074
+ association_module_delegate_def(opts[:remove_all_method], opts){|*args| remove_all_associated_objects(opts, *args)}
1802
2075
  end
1803
2076
  end
1804
2077
 
@@ -1820,14 +2093,84 @@ module Sequel
1820
2093
  raise(Error, "mismatched number of right keys: #{rcks.inspect} vs #{rcpks.inspect}") unless rcks.length == rcpks.length
1821
2094
  end
1822
2095
  opts[:uses_left_composite_keys] = lcks.length > 1
1823
- opts[:uses_right_composite_keys] = rcks.length > 1
2096
+ uses_rcks = opts[:uses_right_composite_keys] = rcks.length > 1
1824
2097
  opts[:cartesian_product_number] ||= one_through_one ? 0 : 1
1825
2098
  join_table = (opts[:join_table] ||= opts.default_join_table)
1826
2099
  opts[:left_key_alias] ||= opts.default_associated_key_alias
1827
2100
  opts[:graph_join_table_join_type] ||= opts[:graph_join_type]
1828
- opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
1829
- opts[:dataset] ||= opts.association_dataset_proc
1830
- opts[:eager_loader] ||= opts.method(:default_eager_loader)
2101
+ if opts[:uniq]
2102
+ opts[:after_load] ||= []
2103
+ opts[:after_load].unshift(:array_uniq!)
2104
+ end
2105
+ if join_table_db = opts[:join_table_db]
2106
+ opts[:use_placeholder_loader] = false
2107
+ opts[:allow_eager_graph] = false
2108
+ opts[:allow_filtering_by] = false
2109
+ opts[:eager_limit_strategy] = nil
2110
+ join_table_ds = join_table_db.from(join_table)
2111
+ opts[:dataset] ||= proc do |r|
2112
+ vals = join_table_ds.where(lcks.zip(lcpks.map{|k| get_column_value(k)})).select_map(right)
2113
+ ds = r.associated_dataset.where(opts.right_primary_key => vals)
2114
+ if uses_rcks
2115
+ vals.delete_if{|v| v.any?(&:nil?)}
2116
+ else
2117
+ vals.delete(nil)
2118
+ end
2119
+ ds = ds.clone(:no_results=>true) if vals.empty?
2120
+ ds
2121
+ end
2122
+ opts[:eager_loader] ||= proc do |eo|
2123
+ h = eo[:id_map]
2124
+ assign_singular = opts.assign_singular?
2125
+ rpk = opts.right_primary_key
2126
+ name = opts[:name]
2127
+
2128
+ join_map = join_table_ds.where(left=>h.keys).select_hash_groups(right, left)
2129
+
2130
+ if uses_rcks
2131
+ join_map.delete_if{|v,| v.any?(&:nil?)}
2132
+ else
2133
+ join_map.delete(nil)
2134
+ end
2135
+
2136
+ eo = Hash[eo]
2137
+
2138
+ if join_map.empty?
2139
+ eo[:no_results] = true
2140
+ else
2141
+ join_map.each_value do |vs|
2142
+ vs.replace(vs.flat_map{|v| h[v]})
2143
+ vs.uniq!
2144
+ end
2145
+
2146
+ eo[:loader] = false
2147
+ eo[:right_keys] = join_map.keys
2148
+ end
2149
+
2150
+ opts[:model].eager_load_results(opts, eo) do |assoc_record|
2151
+ rpkv = if uses_rcks
2152
+ assoc_record.values.values_at(*rpk)
2153
+ else
2154
+ assoc_record.values[rpk]
2155
+ end
2156
+
2157
+ objects = join_map[rpkv]
2158
+
2159
+ if assign_singular
2160
+ objects.each do |object|
2161
+ object.associations[name] ||= assoc_record
2162
+ end
2163
+ else
2164
+ objects.each do |object|
2165
+ object.associations[name].push(assoc_record)
2166
+ end
2167
+ end
2168
+ end
2169
+ end
2170
+ else
2171
+ opts[:dataset] ||= opts.association_dataset_proc
2172
+ opts[:eager_loader] ||= opts.method(:default_eager_loader)
2173
+ end
1831
2174
 
1832
2175
  join_type = opts[:graph_join_type]
1833
2176
  select = opts[:graph_select]
@@ -1861,50 +2204,60 @@ module Sequel
1861
2204
  return if opts[:read_only]
1862
2205
 
1863
2206
  if one_through_one
1864
- opts[:setter] ||= proc do |o|
1865
- h = {}
1866
- lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
1867
- jtds = _join_table_dataset(opts).where(lh)
1868
-
1869
- checked_transaction do
1870
- current = jtds.first
2207
+ unless opts.has_key?(:setter)
2208
+ opts[:setter] = proc do |o|
2209
+ h = {}
2210
+ lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
2211
+ jtds = _join_table_dataset(opts).where(lh)
1871
2212
 
1872
- if o
1873
- new_values = []
1874
- rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
1875
- end
2213
+ checked_transaction do
2214
+ current = jtds.first
1876
2215
 
1877
- if current
1878
- current_values = rcks.map{|k| current[k]}
1879
- jtds = jtds.where(rcks.zip(current_values))
1880
2216
  if o
1881
- if current_values != new_values
1882
- jtds.update(h)
2217
+ new_values = []
2218
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
2219
+ end
2220
+
2221
+ if current
2222
+ current_values = rcks.map{|k| current[k]}
2223
+ jtds = jtds.where(rcks.zip(current_values))
2224
+ if o
2225
+ if current_values != new_values
2226
+ jtds.update(h)
2227
+ end
2228
+ else
2229
+ jtds.delete
1883
2230
  end
1884
- else
1885
- jtds.delete
2231
+ elsif o
2232
+ lh.each{|k,v| h[k] = v}
2233
+ jtds.insert(h)
1886
2234
  end
1887
- elsif o
1888
- lh.each{|k,v| h[k] = v}
1889
- jtds.insert(h)
1890
2235
  end
1891
2236
  end
1892
2237
  end
1893
- opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
2238
+ if opts.fetch(:setter, true)
2239
+ opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
2240
+ end
1894
2241
  else
1895
- opts[:adder] ||= proc do |o|
1896
- h = {}
1897
- lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
1898
- rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
1899
- _join_table_dataset(opts).insert(h)
2242
+ unless opts.has_key?(:adder)
2243
+ opts[:adder] = proc do |o|
2244
+ h = {}
2245
+ lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
2246
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
2247
+ _join_table_dataset(opts).insert(h)
2248
+ end
1900
2249
  end
1901
2250
 
1902
- opts[:remover] ||= proc do |o|
1903
- _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)}) + rcks.zip(opts.right_primary_key_methods.map{|k| o.get_column_value(k)})).delete
2251
+ unless opts.has_key?(:remover)
2252
+ opts[:remover] = proc do |o|
2253
+ _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)}) + rcks.zip(opts.right_primary_key_methods.map{|k| o.get_column_value(k)})).delete
2254
+ end
1904
2255
  end
1905
2256
 
1906
- opts[:clearer] ||= proc do
1907
- _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
2257
+ unless opts.has_key?(:clearer)
2258
+ opts[:clearer] = proc do
2259
+ _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
2260
+ end
1908
2261
  end
1909
2262
  end
1910
2263
  end
@@ -1943,9 +2296,7 @@ module Sequel
1943
2296
 
1944
2297
  eager_load_results(opts, eo) do |assoc_record|
1945
2298
  hash_key = uses_cks ? pk_meths.map{|k| assoc_record.get_column_value(k)} : assoc_record.get_column_value(opts.primary_key_method)
1946
- if objects = h[hash_key]
1947
- objects.each{|object| object.associations[name] = assoc_record}
1948
- end
2299
+ h[hash_key].each{|object| object.associations[name] = assoc_record}
1949
2300
  end
1950
2301
  end
1951
2302
 
@@ -1958,13 +2309,17 @@ module Sequel
1958
2309
  graph_cks = opts[:graph_keys]
1959
2310
  opts[:eager_grapher] ||= proc do |eo|
1960
2311
  ds = eo[:self]
1961
- ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.primary_keys.zip(graph_cks) + conditions, Hash[eo].merge!(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep, :from_self_alias=>eo[:from_self_alias]), &graph_block)
2312
+ ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.primary_keys.zip(graph_cks) + conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep), &graph_block)
1962
2313
  end
1963
2314
 
1964
2315
  return if opts[:read_only]
1965
2316
 
1966
- opts[:setter] ||= proc{|o| cks.zip(opts.primary_key_methods).each{|k, pk| set_column_value(:"#{k}=", (o.get_column_value(pk) if o))}}
1967
- opts[:_setter] = proc{|o| set_associated_object(opts, o)}
2317
+ unless opts.has_key?(:setter)
2318
+ opts[:setter] = proc{|o| cks.zip(opts.primary_key_methods).each{|k, pk| set_column_value(:"#{k}=", (o.get_column_value(pk) if o))}}
2319
+ end
2320
+ if opts.fetch(:setter, true)
2321
+ opts[:_setter] = proc{|o| set_associated_object(opts, o)}
2322
+ end
1968
2323
  end
1969
2324
 
1970
2325
  # Configures one_to_many and one_to_one association reflections and adds the related association methods
@@ -1992,7 +2347,7 @@ module Sequel
1992
2347
  eager_load_results(opts, eo) do |assoc_record|
1993
2348
  assoc_record.values.delete(delete_rn) if delete_rn
1994
2349
  hash_key = uses_cks ? km.map{|k| assoc_record.get_column_value(k)} : assoc_record.get_column_value(km)
1995
- next unless objects = h[hash_key]
2350
+ objects = h[hash_key]
1996
2351
  if assign_singular
1997
2352
  objects.each do |object|
1998
2353
  unless object.associations[name]
@@ -2018,7 +2373,7 @@ module Sequel
2018
2373
  graph_block = opts[:graph_block]
2019
2374
  opts[:eager_grapher] ||= proc do |eo|
2020
2375
  ds = eo[:self]
2021
- ds = ds.graph(opts.apply_eager_graph_limit_strategy(eo[:limit_strategy], eager_graph_dataset(opts, eo)), use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions, Hash[eo].merge!(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep, :from_self_alias=>eo[:from_self_alias]), &graph_block)
2376
+ ds = ds.graph(opts.apply_eager_graph_limit_strategy(eo[:limit_strategy], eager_graph_dataset(opts, eo)), use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep), &graph_block)
2022
2377
  # We only load reciprocals for one_to_many associations, as other reciprocals don't make sense
2023
2378
  ds.opts[:eager_graph][:reciprocals][eo[:table_alias]] = opts.reciprocal
2024
2379
  ds
@@ -2031,33 +2386,59 @@ module Sequel
2031
2386
  cks.each{|k| ck_nil_hash[k] = nil}
2032
2387
 
2033
2388
  if one_to_one
2034
- opts[:setter] ||= proc do |o|
2035
- up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
2036
- if o
2037
- up_ds = up_ds.exclude(o.pk_hash) unless o.new?
2038
- cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2039
- end
2040
- checked_transaction do
2041
- up_ds.update(ck_nil_hash)
2042
- o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2389
+ unless opts.has_key?(:setter)
2390
+ opts[:setter] = proc do |o|
2391
+ up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
2392
+
2393
+ if (froms = up_ds.opts[:from]) && (from = froms[0]) && (from.is_a?(Sequel::Dataset) || (from.is_a?(Sequel::SQL::AliasedExpression) && from.expression.is_a?(Sequel::Dataset)))
2394
+ if old = up_ds.first
2395
+ cks.each{|k| old.set_column_value(:"#{k}=", nil)}
2396
+ end
2397
+ save_old = true
2398
+ end
2399
+
2400
+ if o
2401
+ if !o.new? && !save_old
2402
+ up_ds = up_ds.exclude(o.pk_hash)
2403
+ end
2404
+ cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2405
+ end
2406
+
2407
+ checked_transaction do
2408
+ if save_old
2409
+ old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
2410
+ else
2411
+ up_ds.skip_limit_check.update(ck_nil_hash)
2412
+ end
2413
+
2414
+ o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2415
+ end
2043
2416
  end
2044
2417
  end
2045
- opts[:_setter] = proc{|o| set_one_to_one_associated_object(opts, o)}
2418
+ if opts.fetch(:setter, true)
2419
+ opts[:_setter] = proc{|o| set_one_to_one_associated_object(opts, o)}
2420
+ end
2046
2421
  else
2047
2422
  save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
2048
2423
 
2049
- opts[:adder] ||= proc do |o|
2050
- cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2051
- o.save(save_opts)
2424
+ unless opts.has_key?(:adder)
2425
+ opts[:adder] = proc do |o|
2426
+ cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2427
+ o.save(save_opts)
2428
+ end
2052
2429
  end
2053
2430
 
2054
- opts[:remover] ||= proc do |o|
2055
- cks.each{|k| o.set_column_value(:"#{k}=", nil)}
2056
- o.save(save_opts)
2431
+ unless opts.has_key?(:remover)
2432
+ opts[:remover] = proc do |o|
2433
+ cks.each{|k| o.set_column_value(:"#{k}=", nil)}
2434
+ o.save(save_opts)
2435
+ end
2057
2436
  end
2058
2437
 
2059
- opts[:clearer] ||= proc do
2060
- _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)}))).update(ck_nil_hash)
2438
+ unless opts.has_key?(:clearer)
2439
+ opts[:clearer] = proc do
2440
+ _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)}))).update(ck_nil_hash)
2441
+ end
2061
2442
  end
2062
2443
  end
2063
2444
  end
@@ -2101,8 +2482,10 @@ module Sequel
2101
2482
  # retrieving associations after freezing will still work in most cases,
2102
2483
  # but the associations will not be cached in the association cache.
2103
2484
  def freeze
2104
- associations.freeze
2485
+ associations
2105
2486
  super
2487
+ associations.freeze
2488
+ self
2106
2489
  end
2107
2490
 
2108
2491
  private
@@ -2112,15 +2495,16 @@ module Sequel
2112
2495
  unless ds.kind_of?(AssociationDatasetMethods)
2113
2496
  ds = opts.apply_dataset_changes(ds)
2114
2497
  end
2115
- ds.model_object = self
2498
+ ds = ds.clone(:model_object => self)
2116
2499
  ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph] && opts.eager_graph_lazy_dataset?
2117
- ds = instance_exec(ds, &opts[:block]) if opts[:block]
2500
+ # block method is private
2501
+ ds = send(opts[:block_method], ds) if opts[:block_method]
2118
2502
  ds
2119
2503
  end
2120
2504
 
2121
2505
  # Return a dataset for the association after applying any dynamic callback.
2122
2506
  def _associated_dataset(opts, dynamic_opts)
2123
- ds = send(opts.dataset_method)
2507
+ ds = public_send(opts.dataset_method)
2124
2508
  if callback = dynamic_opts[:callback]
2125
2509
  ds = callback.call(ds)
2126
2510
  end
@@ -2137,17 +2521,18 @@ module Sequel
2137
2521
  # Return an association dataset for the given association reflection
2138
2522
  def _dataset(opts)
2139
2523
  raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
2140
- ds = if opts[:dataset].arity == 1
2141
- instance_exec(opts, &opts[:dataset])
2524
+ ds = if opts[:dataset_opt_arity] == 1
2525
+ # dataset_opt_method is private
2526
+ send(opts[:dataset_opt_method], opts)
2142
2527
  else
2143
- instance_exec(&opts[:dataset])
2528
+ send(opts[:dataset_opt_method])
2144
2529
  end
2145
2530
  _apply_association_options(opts, ds)
2146
2531
  end
2147
2532
 
2148
2533
  # Dataset for the join table of the given many to many association reflection
2149
2534
  def _join_table_dataset(opts)
2150
- ds = model.db.from(opts.join_table_source)
2535
+ ds = (opts[:join_table_db] || model.db).from(opts.join_table_source)
2151
2536
  opts[:join_table_block] ? opts[:join_table_block].call(ds) : ds
2152
2537
  end
2153
2538
 
@@ -2168,7 +2553,12 @@ module Sequel
2168
2553
  if loader = _associated_object_loader(opts, dynamic_opts)
2169
2554
  loader.all(*opts.predicate_key_values(self))
2170
2555
  else
2171
- _associated_dataset(opts, dynamic_opts).all
2556
+ ds = _associated_dataset(opts, dynamic_opts)
2557
+ if ds.opts[:no_results]
2558
+ []
2559
+ else
2560
+ ds.all
2561
+ end
2172
2562
  end
2173
2563
  end
2174
2564
 
@@ -2200,7 +2590,8 @@ module Sequel
2200
2590
  raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
2201
2591
  ensure_associated_primary_key(opts, o, *args)
2202
2592
  return if run_association_callbacks(opts, :before_add, o) == false
2203
- return if !send(opts._add_method, o, *args) && opts.handle_silent_modification_failure?
2593
+ # Allow calling private _add method
2594
+ return if !send(opts[:_add_method], o, *args) && opts.handle_silent_modification_failure?
2204
2595
  if array = associations[opts[:name]] and !array.include?(o)
2205
2596
  array.push(o)
2206
2597
  end
@@ -2208,6 +2599,9 @@ module Sequel
2208
2599
  run_association_callbacks(opts, :after_add, o)
2209
2600
  o
2210
2601
  end
2602
+ # :nocov:
2603
+ ruby2_keywords(:add_associated_object) if respond_to?(:ruby2_keywords, true)
2604
+ # :nocov:
2211
2605
 
2212
2606
  # Add/Set the current object to/as the given object's reciprocal association.
2213
2607
  def add_reciprocal_object(opts, o)
@@ -2232,7 +2626,32 @@ module Sequel
2232
2626
  # cached associations.
2233
2627
  def change_column_value(column, value)
2234
2628
  if assocs = model.autoreloading_associations[column]
2235
- assocs.each{|a| associations.delete(a)}
2629
+ vals = @values
2630
+ if new?
2631
+ # Do deeper checking for new objects, so that associations are
2632
+ # not deleted when values do not change. This code is run at
2633
+ # a higher level for existing objects.
2634
+ if value == (c = vals[column]) && value.class == c.class
2635
+ # If the value is the same, there is no reason to delete
2636
+ # the related associations, so exit early in that case.
2637
+ return super
2638
+ end
2639
+
2640
+ only_delete_nil = c.nil?
2641
+ elsif vals[column].nil?
2642
+ only_delete_nil = true
2643
+ end
2644
+
2645
+ if only_delete_nil
2646
+ # If the current foreign key value is nil, but the association
2647
+ # is already present in the cache, it was probably added to the
2648
+ # cache for a reason, and we do not want to delete it in that case.
2649
+ # However, we still want to delete associations with nil values
2650
+ # to remove the cached false negative.
2651
+ assocs.each{|a| associations.delete(a) if associations[a].nil?}
2652
+ else
2653
+ assocs.each{|a| associations.delete(a)}
2654
+ end
2236
2655
  end
2237
2656
  super
2238
2657
  end
@@ -2254,25 +2673,10 @@ module Sequel
2254
2673
  self
2255
2674
  end
2256
2675
 
2257
- # Handle parsing of options when loading associations. For historical
2258
- # reasons, you can pass true/false/nil or a callable argument to
2259
- # associations. That will be going away in Sequel 5, but we'll still
2260
- # support it until then.
2676
+ # If a block is given, assign it as the :callback option in the hash, and return the hash.
2261
2677
  def load_association_objects_options(dynamic_opts, &block)
2262
- dynamic_opts = case dynamic_opts
2263
- when true, false, nil
2264
- {:reload=>dynamic_opts}
2265
- when Hash
2266
- Hash[dynamic_opts]
2267
- else
2268
- if dynamic_opts.respond_to?(:call)
2269
- {:callback=>dynamic_opts}
2270
- else
2271
- {:reload=>true}
2272
- end
2273
- end
2274
-
2275
- if block_given?
2678
+ if block
2679
+ dynamic_opts = Hash[dynamic_opts]
2276
2680
  dynamic_opts[:callback] = block
2277
2681
  end
2278
2682
 
@@ -2280,7 +2684,7 @@ module Sequel
2280
2684
  end
2281
2685
 
2282
2686
  # Load the associated objects using the dataset, handling callbacks, reciprocals, and caching.
2283
- def load_associated_objects(opts, dynamic_opts=nil, &block)
2687
+ def load_associated_objects(opts, dynamic_opts, &block)
2284
2688
  dynamic_opts = load_association_objects_options(dynamic_opts, &block)
2285
2689
  name = opts[:name]
2286
2690
  if associations.include?(name) && !dynamic_opts[:callback] && !dynamic_opts[:reload]
@@ -2334,11 +2738,15 @@ module Sequel
2334
2738
  # Remove all associated objects from the given association
2335
2739
  def remove_all_associated_objects(opts, *args)
2336
2740
  raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
2337
- send(opts._remove_all_method, *args)
2741
+ # Allow calling private _remove_all method
2742
+ send(opts[:_remove_all_method], *args)
2338
2743
  ret = associations[opts[:name]].each{|o| remove_reciprocal_object(opts, o)} if associations.include?(opts[:name])
2339
2744
  associations[opts[:name]] = []
2340
2745
  ret
2341
2746
  end
2747
+ # :nocov:
2748
+ ruby2_keywords(:remove_all_associated_objects) if respond_to?(:ruby2_keywords, true)
2749
+ # :nocov:
2342
2750
 
2343
2751
  # Remove the given associated object from the given association
2344
2752
  def remove_associated_object(opts, o, *args)
@@ -2347,18 +2755,22 @@ module Sequel
2347
2755
  o = remove_check_existing_object_from_pk(opts, o, *args)
2348
2756
  elsif !o.is_a?(klass)
2349
2757
  raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}")
2350
- elsif opts.remove_should_check_existing? && send(opts.dataset_method).where(o.pk_hash).empty?
2758
+ elsif opts.remove_should_check_existing? && public_send(opts.dataset_method).where(o.pk_hash).empty?
2351
2759
  raise(Sequel::Error, "associated object #{o.inspect} is not currently associated to #{inspect}")
2352
2760
  end
2353
2761
  raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
2354
2762
  raise(Sequel::Error, "associated object #{o.inspect} does not have a primary key") if opts.need_associated_primary_key? && !o.pk
2355
2763
  return if run_association_callbacks(opts, :before_remove, o) == false
2356
- return if !send(opts._remove_method, o, *args) && opts.handle_silent_modification_failure?
2764
+ # Allow calling private _remove method
2765
+ return if !send(opts[:_remove_method], o, *args) && opts.handle_silent_modification_failure?
2357
2766
  associations[opts[:name]].delete_if{|x| o === x} if associations.include?(opts[:name])
2358
2767
  remove_reciprocal_object(opts, o)
2359
2768
  run_association_callbacks(opts, :after_remove, o)
2360
2769
  o
2361
2770
  end
2771
+ # :nocov:
2772
+ ruby2_keywords(:remove_associated_object) if respond_to?(:ruby2_keywords, true)
2773
+ # :nocov:
2362
2774
 
2363
2775
  # Check that the object from the associated table specified by the primary key
2364
2776
  # is currently associated to the receiver. If it is associated, return the object, otherwise
@@ -2366,7 +2778,7 @@ module Sequel
2366
2778
  def remove_check_existing_object_from_pk(opts, o, *args)
2367
2779
  key = o
2368
2780
  pkh = opts.associated_class.qualified_primary_key_hash(key)
2369
- raise(Sequel::Error, "no object with key(s) #{key.inspect} is currently associated to #{inspect}") unless o = send(opts.dataset_method).first(pkh)
2781
+ raise(Sequel::Error, "no object with key(s) #{key.inspect} is currently associated to #{inspect}") unless o = public_send(opts.dataset_method).first(pkh)
2370
2782
  o
2371
2783
  end
2372
2784
 
@@ -2384,40 +2796,52 @@ module Sequel
2384
2796
 
2385
2797
  # Run the callback for the association with the object.
2386
2798
  def run_association_callbacks(reflection, callback_type, object)
2387
- # The reason we automatically set raise_error for singular associations is that
2388
- # assignment in ruby always returns the argument instead of the result of the
2389
- # method, so we can't return nil to signal that the association callback prevented
2390
- # the modification
2391
- raise_error = raise_on_save_failure || !reflection.returns_array?
2392
- stop_on_false = [:before_add, :before_remove, :before_set].include?(callback_type)
2393
- reflection[callback_type].each do |cb|
2394
- res = case cb
2395
- when Symbol
2396
- send(cb, object)
2397
- when Proc
2398
- cb.call(self, object)
2399
- else
2400
- raise Error, "callbacks should either be Procs or Symbols"
2401
- end
2402
-
2403
- if res == false and stop_on_false
2404
- raise(HookFailed, "Unable to modify association for #{inspect}: one of the #{callback_type} hooks returned false")
2799
+ return unless cbs = reflection[callback_type]
2800
+
2801
+ begin
2802
+ cbs.each do |cb|
2803
+ case cb
2804
+ when Symbol
2805
+ # Allow calling private methods in association callbacks
2806
+ send(cb, object)
2807
+ when Proc
2808
+ cb.call(self, object)
2809
+ else
2810
+ raise Error, "callbacks should either be Procs or Symbols"
2811
+ end
2405
2812
  end
2813
+ rescue HookFailed
2814
+ # The reason we automatically set raise_error for singular associations is that
2815
+ # assignment in ruby always returns the argument instead of the result of the
2816
+ # method, so we can't return nil to signal that the association callback prevented
2817
+ # the modification
2818
+ return false unless raise_on_save_failure || !reflection.returns_array?
2819
+ raise
2406
2820
  end
2407
- rescue HookFailed
2408
- return false unless raise_error
2409
- raise
2410
2821
  end
2411
2822
 
2412
2823
  # Set the given object as the associated object for the given *_to_one association reflection
2413
2824
  def _set_associated_object(opts, o)
2414
2825
  a = associations[opts[:name]]
2415
- return if a && a == o && !set_associated_object_if_same?
2826
+ reciprocal = opts.reciprocal
2827
+ if set_associated_object_if_same?
2828
+ if reciprocal
2829
+ remove_reciprocal = a && (a != o || a.associations[reciprocal] != self)
2830
+ add_reciprocal = o && o.associations[reciprocal] != self
2831
+ end
2832
+ else
2833
+ return if a && a == o
2834
+ if reciprocal
2835
+ remove_reciprocal = a
2836
+ add_reciprocal = o
2837
+ end
2838
+ end
2416
2839
  run_association_callbacks(opts, :before_set, o)
2417
- remove_reciprocal_object(opts, a) if a
2418
- send(opts._setter_method, o)
2840
+ remove_reciprocal_object(opts, a) if remove_reciprocal
2841
+ # Allow calling private _setter method
2842
+ send(opts[:_setter_method], o)
2419
2843
  associations[opts[:name]] = o
2420
- add_reciprocal_object(opts, o) if o
2844
+ add_reciprocal_object(opts, o) if add_reciprocal
2421
2845
  run_association_callbacks(opts, :after_set, o)
2422
2846
  o
2423
2847
  end
@@ -2472,48 +2896,112 @@ module Sequel
2472
2896
  # Album.eager(:artist, :genre).all
2473
2897
  # Album.eager_graph(:artist, :genre).all
2474
2898
  # Album.eager(:artist).eager(:genre).all
2475
- # Album.eager_graph(:artist).eager(:genre).all
2476
- # Artist.eager(:albums=>:tracks).all
2477
- # Artist.eager_graph(:albums=>:tracks).all
2478
- # Artist.eager(:albums=>{:tracks=>:genre}).all
2479
- # Artist.eager_graph(:albums=>{:tracks=>:genre}).all
2899
+ # Album.eager_graph(:artist).eager_graph(:genre).all
2900
+ # Artist.eager(albums: :tracks).all
2901
+ # Artist.eager_graph(albums: :tracks).all
2902
+ # Artist.eager(albums: {tracks: :genre}).all
2903
+ # Artist.eager_graph(albums: {tracks: :genre}).all
2480
2904
  #
2481
2905
  # You can also pass a callback as a hash value in order to customize the dataset being
2482
2906
  # eager loaded at query time, analogous to the way the :eager_block association option
2483
2907
  # allows you to customize it at association definition time. For example,
2484
2908
  # if you wanted artists with their albums since 1990:
2485
2909
  #
2486
- # Artist.eager(:albums => proc{|ds| ds.where{year > 1990}})
2910
+ # Artist.eager(albums: proc{|ds| ds.where{year > 1990}})
2487
2911
  #
2488
2912
  # Or if you needed albums and their artist's name only, using a single query:
2489
2913
  #
2490
- # Albums.eager_graph(:artist => proc{|ds| ds.select(:name)})
2914
+ # Albums.eager_graph(artist: proc{|ds| ds.select(:name)})
2491
2915
  #
2492
2916
  # To cascade eager loading while using a callback, you substitute the cascaded
2493
2917
  # associations with a single entry hash that has the proc callback as the key and
2494
2918
  # the cascaded associations as the value. This will load artists with their albums
2495
2919
  # since 1990, and also the tracks on those albums and the genre for those tracks:
2496
2920
  #
2497
- # Artist.eager(:albums => {proc{|ds| ds.where{year > 1990}}=>{:tracks => :genre}})
2921
+ # Artist.eager(albums: {proc{|ds| ds.where{year > 1990}}=>{tracks: :genre}})
2498
2922
  module DatasetMethods
2499
- Sequel::Dataset.def_mutation_method(:eager, :eager_graph, :module=>self)
2500
-
2501
2923
  %w'inner left right full'.each do |type|
2502
- class_eval <<END, __FILE__, __LINE__+1
2924
+ class_eval(<<-END, __FILE__, __LINE__+1)
2503
2925
  def association_#{type}_join(*associations)
2504
2926
  _association_join(:#{type}, associations)
2505
2927
  end
2506
- END
2928
+ END
2507
2929
  end
2508
2930
 
2509
2931
  # Adds one or more INNER JOINs to the existing dataset using the keys and conditions
2510
- # specified by the given association. The following methods also exist for specifying
2511
- # a different type of JOIN:
2932
+ # specified by the given association(s). Take the same arguments as eager_graph, and
2933
+ # operates similarly, but only adds the joins as opposed to making the other changes
2934
+ # (such as adding selected columns and setting up eager loading).
2935
+ #
2936
+ # The following methods also exist for specifying a different type of JOIN:
2512
2937
  #
2513
2938
  # association_full_join :: FULL JOIN
2514
2939
  # association_inner_join :: INNER JOIN
2515
2940
  # association_left_join :: LEFT JOIN
2516
2941
  # association_right_join :: RIGHT JOIN
2942
+ #
2943
+ # Examples:
2944
+ #
2945
+ # # For each album, association_join load the artist
2946
+ # Album.association_join(:artist).all
2947
+ # # SELECT *
2948
+ # # FROM albums
2949
+ # # INNER JOIN artists AS artist ON (artists.id = albums.artist_id)
2950
+ #
2951
+ # # For each album, association_join load the artist, using a specified alias
2952
+ # Album.association_join(Sequel[:artist].as(:a)).all
2953
+ # # SELECT *
2954
+ # # FROM albums
2955
+ # # INNER JOIN artists AS a ON (a.id = albums.artist_id)
2956
+ #
2957
+ # # For each album, association_join load the artist and genre
2958
+ # Album.association_join(:artist, :genre).all
2959
+ # Album.association_join(:artist).association_join(:genre).all
2960
+ # # SELECT *
2961
+ # # FROM albums
2962
+ # # INNER JOIN artists AS artist ON (artist.id = albums.artist_id)
2963
+ # # INNER JOIN genres AS genre ON (genre.id = albums.genre_id)
2964
+ #
2965
+ # # For each artist, association_join load albums and tracks for each album
2966
+ # Artist.association_join(albums: :tracks).all
2967
+ # # SELECT *
2968
+ # # FROM artists
2969
+ # # INNER JOIN albums ON (albums.artist_id = artists.id)
2970
+ # # INNER JOIN tracks ON (tracks.album_id = albums.id)
2971
+ #
2972
+ # # For each artist, association_join load albums, tracks for each album, and genre for each track
2973
+ # Artist.association_join(albums: {tracks: :genre}).all
2974
+ # # SELECT *
2975
+ # # FROM artists
2976
+ # # INNER JOIN albums ON (albums.artist_id = artists.id)
2977
+ # # INNER JOIN tracks ON (tracks.album_id = albums.id)
2978
+ # # INNER JOIN genres AS genre ON (genre.id = tracks.genre_id)
2979
+ #
2980
+ # # For each artist, association_join load albums with year > 1990
2981
+ # Artist.association_join(albums: proc{|ds| ds.where{year > 1990}}).all
2982
+ # # SELECT *
2983
+ # # FROM artists
2984
+ # # INNER JOIN (
2985
+ # # SELECT * FROM albums WHERE (year > 1990)
2986
+ # # ) AS albums ON (albums.artist_id = artists.id)
2987
+ #
2988
+ # # For each artist, association_join load albums and tracks 1-10 for each album
2989
+ # Artist.association_join(albums: {tracks: proc{|ds| ds.where(number: 1..10)}}).all
2990
+ # # SELECT *
2991
+ # # FROM artists
2992
+ # # INNER JOIN albums ON (albums.artist_id = artists.id)
2993
+ # # INNER JOIN (
2994
+ # # SELECT * FROM tracks WHERE ((number >= 1) AND (number <= 10))
2995
+ # # ) AS tracks ON (tracks.albums_id = albums.id)
2996
+ #
2997
+ # # For each artist, association_join load albums with year > 1990, and tracks for those albums
2998
+ # Artist.association_join(albums: {proc{|ds| ds.where{year > 1990}}=>:tracks}).all
2999
+ # # SELECT *
3000
+ # # FROM artists
3001
+ # # INNER JOIN (
3002
+ # # SELECT * FROM albums WHERE (year > 1990)
3003
+ # # ) AS albums ON (albums.artist_id = artists.id)
3004
+ # # INNER JOIN tracks ON (tracks.album_id = albums.id)
2517
3005
  def association_join(*associations)
2518
3006
  association_inner_join(*associations)
2519
3007
  end
@@ -2526,11 +3014,13 @@ END
2526
3014
  # types, this is a simple transformation, but for +many_to_many+ associations this
2527
3015
  # creates a subquery to the join table.
2528
3016
  def complex_expression_sql_append(sql, op, args)
2529
- r = args.at(1)
2530
- if (((op == :'=' || op == :'!=') and r.is_a?(Sequel::Model)) ||
2531
- (multiple = ((op == :IN || op == :'NOT IN') and ((is_ds = r.is_a?(Sequel::Dataset)) or r.all?{|x| x.is_a?(Sequel::Model)}))))
2532
- l = args.at(0)
3017
+ r = args[1]
3018
+ if (((op == :'=' || op == :'!=') && r.is_a?(Sequel::Model)) ||
3019
+ (multiple = ((op == :IN || op == :'NOT IN') && ((is_ds = r.is_a?(Sequel::Dataset)) || (r.respond_to?(:all?) && r.all?{|x| x.is_a?(Sequel::Model)})))))
3020
+ l = args[0]
2533
3021
  if ar = model.association_reflections[l]
3022
+ raise Error, "filtering by associations is not allowed for #{ar.inspect}" if ar[:allow_filtering_by] == false
3023
+
2534
3024
  if multiple
2535
3025
  klass = ar.associated_class
2536
3026
  if is_ds
@@ -2584,7 +3074,7 @@ END
2584
3074
  # it avoids problems such as aliasing conflicts and creating cartesian product
2585
3075
  # result sets if multiple one_to_many or many_to_many eager associations are requested.
2586
3076
  #
2587
- # One limitation of using this method is that you cannot filter the dataset
3077
+ # One limitation of using this method is that you cannot filter the current dataset
2588
3078
  # based on values of columns in an associated table, since the associations are loaded
2589
3079
  # in separate queries. To do that you need to load all associations in the
2590
3080
  # same query, and extract an object graph from the results of that query. If you
@@ -2593,15 +3083,66 @@ END
2593
3083
  #
2594
3084
  # Each association's order, if defined, is respected.
2595
3085
  # If the association uses a block or has an :eager_block argument, it is used.
3086
+ #
3087
+ # To modify the associated dataset that will be used for the eager load, you should use a
3088
+ # hash for the association, with the key being the association name symbol, and the value being
3089
+ # a callable object that is called with the associated dataset and should return a modified
3090
+ # dataset. If that association also has dependent associations, instead of a callable object,
3091
+ # use a hash with the callable object being the key, and the dependent association(s) as the value.
3092
+ #
3093
+ # Examples:
3094
+ #
3095
+ # # For each album, eager load the artist
3096
+ # Album.eager(:artist).all
3097
+ # # SELECT * FROM albums
3098
+ # # SELECT * FROM artists WHERE (id IN (...))
3099
+ #
3100
+ # # For each album, eager load the artist and genre
3101
+ # Album.eager(:artist, :genre).all
3102
+ # Album.eager(:artist).eager(:genre).all
3103
+ # # SELECT * FROM albums
3104
+ # # SELECT * FROM artists WHERE (id IN (...))
3105
+ # # SELECT * FROM genres WHERE (id IN (...))
3106
+ #
3107
+ # # For each artist, eager load albums and tracks for each album
3108
+ # Artist.eager(albums: :tracks).all
3109
+ # # SELECT * FROM artists
3110
+ # # SELECT * FROM albums WHERE (artist_id IN (...))
3111
+ # # SELECT * FROM tracks WHERE (album_id IN (...))
3112
+ #
3113
+ # # For each artist, eager load albums, tracks for each album, and genre for each track
3114
+ # Artist.eager(albums: {tracks: :genre}).all
3115
+ # # SELECT * FROM artists
3116
+ # # SELECT * FROM albums WHERE (artist_id IN (...))
3117
+ # # SELECT * FROM tracks WHERE (album_id IN (...))
3118
+ # # SELECT * FROM genre WHERE (id IN (...))
3119
+ #
3120
+ # # For each artist, eager load albums with year > 1990
3121
+ # Artist.eager(albums: proc{|ds| ds.where{year > 1990}}).all
3122
+ # # SELECT * FROM artists
3123
+ # # SELECT * FROM albums WHERE ((year > 1990) AND (artist_id IN (...)))
3124
+ #
3125
+ # # For each artist, eager load albums and tracks 1-10 for each album
3126
+ # Artist.eager(albums: {tracks: proc{|ds| ds.where(number: 1..10)}}).all
3127
+ # # SELECT * FROM artists
3128
+ # # SELECT * FROM albums WHERE (artist_id IN (...))
3129
+ # # SELECT * FROM tracks WHERE ((number >= 1) AND (number <= 10) AND (album_id IN (...)))
3130
+ #
3131
+ # # For each artist, eager load albums with year > 1990, and tracks for those albums
3132
+ # Artist.eager(albums: {proc{|ds| ds.where{year > 1990}}=>:tracks}).all
3133
+ # # SELECT * FROM artists
3134
+ # # SELECT * FROM albums WHERE ((year > 1990) AND (artist_id IN (...)))
3135
+ # # SELECT * FROM albums WHERE (artist_id IN (...))
2596
3136
  def eager(*associations)
2597
3137
  opts = @opts[:eager]
2598
3138
  association_opts = eager_options_for_associations(associations)
2599
- opts = opts ? Hash[opts].merge!(association_opts) : association_opts
2600
- clone(:eager=>opts)
3139
+ opts = opts ? opts.merge(association_opts) : association_opts
3140
+ clone(:eager=>opts.freeze)
2601
3141
  end
2602
3142
 
2603
3143
  # The secondary eager loading method. Loads all associations in a single query. This
2604
- # method should only be used if you need to filter or order based on columns in associated tables.
3144
+ # method should only be used if you need to filter or order based on columns in associated tables,
3145
+ # or if you have done comparative benchmarking and determined it is faster.
2605
3146
  #
2606
3147
  # This method uses <tt>Dataset#graph</tt> to create appropriate aliases for columns in all the
2607
3148
  # tables. Then it uses the graph's metadata to build the associations from the single hash, and
@@ -2609,7 +3150,7 @@ END
2609
3150
  #
2610
3151
  # Be very careful when using this with multiple one_to_many or many_to_many associations, as you can
2611
3152
  # create large cartesian products. If you must graph multiple one_to_many and many_to_many associations,
2612
- # make sure your filters are narrow if you have a large database.
3153
+ # make sure your filters are narrow if the datasets are large.
2613
3154
  #
2614
3155
  # Each association's order, if defined, is respected. +eager_graph+ probably
2615
3156
  # won't work correctly on a limited dataset, unless you are
@@ -2620,6 +3161,88 @@ END
2620
3161
  #
2621
3162
  # Like +eager+, you need to call +all+ on the dataset for the eager loading to work. If you just
2622
3163
  # call +each+, it will yield plain hashes, each containing all columns from all the tables.
3164
+ #
3165
+ # To modify the associated dataset that will be joined to the current dataset, you should use a
3166
+ # hash for the association, with the key being the association name symbol, and the value being
3167
+ # a callable object that is called with the associated dataset and should return a modified
3168
+ # dataset. If that association also has dependent associations, instead of a callable object,
3169
+ # use a hash with the callable object being the key, and the dependent association(s) as the value.
3170
+ #
3171
+ # You can specify an custom alias and/or join type on a per-association basis by providing an
3172
+ # Sequel::SQL::AliasedExpression object instead of an a Symbol for the association name.
3173
+ #
3174
+ # You cannot mix calls to +eager_graph+ and +graph+ on the same dataset.
3175
+ #
3176
+ # Examples:
3177
+ #
3178
+ # # For each album, eager_graph load the artist
3179
+ # Album.eager_graph(:artist).all
3180
+ # # SELECT ...
3181
+ # # FROM albums
3182
+ # # LEFT OUTER JOIN artists AS artist ON (artists.id = albums.artist_id)
3183
+ #
3184
+ # # For each album, eager_graph load the artist, using a specified alias
3185
+ # Album.eager_graph(Sequel[:artist].as(:a)).all
3186
+ # # SELECT ...
3187
+ # # FROM albums
3188
+ # # LEFT OUTER JOIN artists AS a ON (a.id = albums.artist_id)
3189
+ #
3190
+ # # For each album, eager_graph load the artist, using a specified alias
3191
+ # # and custom join type
3192
+ #
3193
+ # Album.eager_graph(Sequel[:artist].as(:a, join_type: :inner)).all
3194
+ # # SELECT ...
3195
+ # # FROM albums
3196
+ # # INNER JOIN artists AS a ON (a.id = albums.artist_id)
3197
+ #
3198
+ # # For each album, eager_graph load the artist and genre
3199
+ # Album.eager_graph(:artist, :genre).all
3200
+ # Album.eager_graph(:artist).eager_graph(:genre).all
3201
+ # # SELECT ...
3202
+ # # FROM albums
3203
+ # # LEFT OUTER JOIN artists AS artist ON (artist.id = albums.artist_id)
3204
+ # # LEFT OUTER JOIN genres AS genre ON (genre.id = albums.genre_id)
3205
+ #
3206
+ # # For each artist, eager_graph load albums and tracks for each album
3207
+ # Artist.eager_graph(albums: :tracks).all
3208
+ # # SELECT ...
3209
+ # # FROM artists
3210
+ # # LEFT OUTER JOIN albums ON (albums.artist_id = artists.id)
3211
+ # # LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)
3212
+ #
3213
+ # # For each artist, eager_graph load albums, tracks for each album, and genre for each track
3214
+ # Artist.eager_graph(albums: {tracks: :genre}).all
3215
+ # # SELECT ...
3216
+ # # FROM artists
3217
+ # # LEFT OUTER JOIN albums ON (albums.artist_id = artists.id)
3218
+ # # LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)
3219
+ # # LEFT OUTER JOIN genres AS genre ON (genre.id = tracks.genre_id)
3220
+ #
3221
+ # # For each artist, eager_graph load albums with year > 1990
3222
+ # Artist.eager_graph(albums: proc{|ds| ds.where{year > 1990}}).all
3223
+ # # SELECT ...
3224
+ # # FROM artists
3225
+ # # LEFT OUTER JOIN (
3226
+ # # SELECT * FROM albums WHERE (year > 1990)
3227
+ # # ) AS albums ON (albums.artist_id = artists.id)
3228
+ #
3229
+ # # For each artist, eager_graph load albums and tracks 1-10 for each album
3230
+ # Artist.eager_graph(albums: {tracks: proc{|ds| ds.where(number: 1..10)}}).all
3231
+ # # SELECT ...
3232
+ # # FROM artists
3233
+ # # LEFT OUTER JOIN albums ON (albums.artist_id = artists.id)
3234
+ # # LEFT OUTER JOIN (
3235
+ # # SELECT * FROM tracks WHERE ((number >= 1) AND (number <= 10))
3236
+ # # ) AS tracks ON (tracks.albums_id = albums.id)
3237
+ #
3238
+ # # For each artist, eager_graph load albums with year > 1990, and tracks for those albums
3239
+ # Artist.eager_graph(albums: {proc{|ds| ds.where{year > 1990}}=>:tracks}).all
3240
+ # # SELECT ...
3241
+ # # FROM artists
3242
+ # # LEFT OUTER JOIN (
3243
+ # # SELECT * FROM albums WHERE (year > 1990)
3244
+ # # ) AS albums ON (albums.artist_id = artists.id)
3245
+ # # LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)
2623
3246
  def eager_graph(*associations)
2624
3247
  eager_graph_with_options(associations)
2625
3248
  end
@@ -2645,8 +3268,11 @@ END
2645
3268
  # significantly slower in some cases (perhaps even the majority of cases), so you should
2646
3269
  # only use this if you have benchmarked that it is faster for your use cases.
2647
3270
  def eager_graph_with_options(associations, opts=OPTS)
3271
+ return self if associations.empty?
3272
+
3273
+ opts = opts.dup unless opts.frozen?
2648
3274
  associations = [associations] unless associations.is_a?(Array)
2649
- if eg = @opts[:eager_graph]
3275
+ ds = if eg = @opts[:eager_graph]
2650
3276
  eg = eg.dup
2651
3277
  [:requirements, :reflections, :reciprocals, :limits].each{|k| eg[k] = eg[k].dup}
2652
3278
  eg[:local] = opts
@@ -2660,13 +3286,17 @@ END
2660
3286
  # :limits :: Any limit/offset array slicing that need to be handled in ruby land after loading
2661
3287
  opts = {:requirements=>{}, :master=>alias_symbol(first_source), :reflections=>{}, :reciprocals=>{}, :limits=>{}, :local=>opts, :cartesian_product_number=>0, :row_proc=>row_proc}
2662
3288
  ds = clone(:eager_graph=>opts)
2663
- ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations).naked
3289
+ ds = ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations).naked
2664
3290
  end
3291
+
3292
+ ds.opts[:eager_graph].freeze
3293
+ ds.opts[:eager_graph].each_value{|v| v.freeze if v.is_a?(Hash)}
3294
+ ds
2665
3295
  end
2666
3296
 
2667
3297
  # If the dataset is being eagerly loaded, default to calling all
2668
3298
  # instead of each.
2669
- def to_hash(key_column=nil, value_column=nil, opts=OPTS)
3299
+ def as_hash(key_column=nil, value_column=nil, opts=OPTS)
2670
3300
  if (@opts[:eager_graph] || @opts[:eager]) && !opts.has_key?(:all)
2671
3301
  opts = Hash[opts]
2672
3302
  opts[:all] = true
@@ -2691,7 +3321,7 @@ END
2691
3321
  def ungraphed
2692
3322
  ds = super.clone(:eager_graph=>nil)
2693
3323
  if (eg = @opts[:eager_graph]) && (rp = eg[:row_proc])
2694
- ds.row_proc = rp
3324
+ ds = ds.with_row_proc(rp)
2695
3325
  end
2696
3326
  ds
2697
3327
  end
@@ -2709,11 +3339,16 @@ END
2709
3339
  # ta :: table_alias used for the parent association
2710
3340
  # requirements :: an array, used as a stack for requirements
2711
3341
  # r :: association reflection for the current association, or an SQL::AliasedExpression
2712
- # with the reflection as the expression and the alias base as the aliaz.
3342
+ # with the reflection as the expression, the alias base as the alias (or nil to
3343
+ # use the default alias), and an optional hash with a :join_type entry as the columns
3344
+ # to use a custom join type.
2713
3345
  # *associations :: any associations dependent on this one
2714
3346
  def eager_graph_association(ds, model, ta, requirements, r, *associations)
2715
3347
  if r.is_a?(SQL::AliasedExpression)
2716
3348
  alias_base = r.alias
3349
+ if r.columns.is_a?(Hash)
3350
+ join_type = r.columns[:join_type]
3351
+ end
2717
3352
  r = r.expression
2718
3353
  else
2719
3354
  alias_base = r[:graph_alias_base]
@@ -2731,9 +3366,14 @@ END
2731
3366
  end
2732
3367
  local_opts = ds.opts[:eager_graph][:local]
2733
3368
  limit_strategy = r.eager_graph_limit_strategy(local_opts[:limit_strategy])
2734
- ds = loader.call(:self=>ds, :table_alias=>assoc_table_alias, :implicit_qualifier=>(ta == ds.opts[:eager_graph][:master]) ? first_source : qualifier_from_alias_symbol(ta, first_source), :callback=>callback, :join_type=>local_opts[:join_type], :join_only=>local_opts[:join_only], :limit_strategy=>limit_strategy, :from_self_alias=>ds.opts[:eager_graph][:master])
3369
+
3370
+ if r[:conditions] && !Sequel.condition_specifier?(r[:conditions]) && !r[:orig_opts].has_key?(:graph_conditions) && !r[:orig_opts].has_key?(:graph_only_conditions) && !r.has_key?(:graph_block)
3371
+ raise Error, "Cannot eager_graph association when :conditions specified and not a hash or an array of pairs. Specify :graph_conditions, :graph_only_conditions, or :graph_block for the association. Model: #{r[:model]}, association: #{r[:name]}"
3372
+ end
3373
+
3374
+ ds = loader.call(:self=>ds, :table_alias=>assoc_table_alias, :implicit_qualifier=>(ta == ds.opts[:eager_graph][:master]) ? first_source : qualifier_from_alias_symbol(ta, first_source), :callback=>callback, :join_type=>join_type || local_opts[:join_type], :join_only=>local_opts[:join_only], :limit_strategy=>limit_strategy, :from_self_alias=>ds.opts[:eager_graph][:master])
2735
3375
  if r[:order_eager_graph] && (order = r.fetch(:graph_order, r[:order]))
2736
- ds = ds.order_more(*qualified_expression(order, assoc_table_alias))
3376
+ ds = ds.order_append(*qualified_expression(order, assoc_table_alias))
2737
3377
  end
2738
3378
  eager_graph = ds.opts[:eager_graph]
2739
3379
  eager_graph[:requirements][assoc_table_alias] = requirements.dup
@@ -2756,7 +3396,6 @@ END
2756
3396
  # requirements :: an array, used as a stack for requirements
2757
3397
  # *associations :: the associations to add to the graph
2758
3398
  def eager_graph_associations(ds, model, ta, requirements, *associations)
2759
- return ds if associations.empty?
2760
3399
  associations.flatten.each do |association|
2761
3400
  ds = case association
2762
3401
  when Symbol, SQL::AliasedExpression
@@ -2776,7 +3415,7 @@ END
2776
3415
  # Replace the array of plain hashes with an array of model objects will all eager_graphed
2777
3416
  # associations set in the associations cache for each object.
2778
3417
  def eager_graph_build_associations(hashes)
2779
- hashes.replace(EagerGraphLoader.new(self).load(hashes))
3418
+ hashes.replace(_eager_graph_build_associations(hashes, eager_graph_loader))
2780
3419
  end
2781
3420
 
2782
3421
  private
@@ -2787,12 +3426,18 @@ END
2787
3426
  clone(:join=>clone(:graph_from_self=>false).eager_graph_with_options(associations, :join_type=>type, :join_only=>true).opts[:join])
2788
3427
  end
2789
3428
 
3429
+ # Process the array of hashes using the eager graph loader to return an array
3430
+ # of model objects with the associations set.
3431
+ def _eager_graph_build_associations(hashes, egl)
3432
+ egl.load(hashes)
3433
+ end
3434
+
2790
3435
  # If the association has conditions itself, then it requires additional filters be
2791
3436
  # added to the current dataset to ensure that the passed in object would also be
2792
3437
  # included by the association's conditions.
2793
3438
  def add_association_filter_conditions(ref, obj, expr)
2794
3439
  if expr != SQL::Constants::FALSE && ref.filter_by_associations_add_conditions?
2795
- Sequel.expr(ref.filter_by_associations_conditions_expression(obj))
3440
+ Sequel[ref.filter_by_associations_conditions_expression(obj)]
2796
3441
  else
2797
3442
  expr
2798
3443
  end
@@ -2820,6 +3465,7 @@ END
2820
3465
  # Return an expression for filtering by the given association reflection and associated object.
2821
3466
  def association_filter_expression(op, ref, obj)
2822
3467
  meth = :"#{ref[:type]}_association_filter_expression"
3468
+ # Allow calling private association specific method to get filter expression
2823
3469
  send(meth, op, ref, obj) if respond_to?(meth, true)
2824
3470
  end
2825
3471
 
@@ -2870,23 +3516,60 @@ END
2870
3516
  # Allow associations that are eagerly graphed to be specified as an SQL::AliasedExpression, for
2871
3517
  # per-call determining of the alias base.
2872
3518
  def eager_graph_check_association(model, association)
2873
- if association.is_a?(SQL::AliasedExpression)
2874
- SQL::AliasedExpression.new(check_association(model, association.expression), association.alias)
3519
+ reflection = if association.is_a?(SQL::AliasedExpression)
3520
+ expr = association.expression
3521
+ if expr.is_a?(SQL::Identifier)
3522
+ expr = expr.value
3523
+ if expr.is_a?(String)
3524
+ expr = expr.to_sym
3525
+ end
3526
+ end
3527
+
3528
+ check_reflection = check_association(model, expr)
3529
+ SQL::AliasedExpression.new(check_reflection, association.alias || expr, association.columns)
2875
3530
  else
2876
- check_association(model, association)
3531
+ check_reflection = check_association(model, association)
3532
+ end
3533
+
3534
+ if check_reflection && check_reflection[:allow_eager_graph] == false
3535
+ raise Error, "eager_graph not allowed for #{reflection.inspect}"
2877
3536
  end
3537
+
3538
+ reflection
2878
3539
  end
2879
3540
 
2880
- # Eagerly load all specified associations
3541
+ # The EagerGraphLoader instance used for converting eager_graph results.
3542
+ def eager_graph_loader
3543
+ unless egl = cache_get(:_model_eager_graph_loader)
3544
+ egl = cache_set(:_model_eager_graph_loader, EagerGraphLoader.new(self))
3545
+ end
3546
+ egl.dup
3547
+ end
3548
+
3549
+ # Eagerly load all specified associations.
2881
3550
  def eager_load(a, eager_assoc=@opts[:eager])
2882
3551
  return if a.empty?
2883
- # Key is foreign/primary key name symbol
3552
+
3553
+ # Reflections for all associations to eager load
3554
+ reflections = eager_assoc.keys.map{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
3555
+
3556
+ perform_eager_loads(prepare_eager_load(a, reflections, eager_assoc))
3557
+
3558
+ reflections.each do |r|
3559
+ a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} if r[:after_load]
3560
+ end
3561
+
3562
+ nil
3563
+ end
3564
+
3565
+ # Prepare a hash loaders and eager options which will be used to implement the eager loading.
3566
+ def prepare_eager_load(a, reflections, eager_assoc)
3567
+ eager_load_data = {}
3568
+
3569
+ # Key is foreign/primary key name symbol.
2884
3570
  # Value is hash with keys being foreign/primary key values (generally integers)
2885
- # and values being an array of current model objects with that
2886
- # specific foreign/primary key
3571
+ # and values being an array of current model objects with that specific foreign/primary key
2887
3572
  key_hash = {}
2888
- # Reflections for all associations to eager load
2889
- reflections = eager_assoc.keys.collect{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
2890
3573
 
2891
3574
  # Populate the key_hash entry for each association being eagerly loaded
2892
3575
  reflections.each do |r|
@@ -2917,17 +3600,30 @@ END
2917
3600
  id_map = nil
2918
3601
  end
2919
3602
 
2920
- loader = r[:eager_loader]
2921
3603
  associations = eager_assoc[r[:name]]
2922
3604
  if associations.respond_to?(:call)
2923
3605
  eager_block = associations
2924
- associations = {}
3606
+ associations = OPTS
2925
3607
  elsif associations.is_a?(Hash) && associations.length == 1 && (pr_assoc = associations.to_a.first) && pr_assoc.first.respond_to?(:call)
2926
3608
  eager_block, associations = pr_assoc
2927
3609
  end
2928
- loader.call(:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block, :id_map=>id_map)
2929
- a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} unless r[:after_load].empty?
2930
- end
3610
+
3611
+ eager_load_data[r[:eager_loader]] = {:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block, :id_map=>id_map}
3612
+ end
3613
+
3614
+ eager_load_data
3615
+ end
3616
+
3617
+ # Using the hash of loaders and eager options, perform the eager loading.
3618
+ def perform_eager_loads(eager_load_data)
3619
+ eager_load_data.map do |loader, eo|
3620
+ perform_eager_load(loader, eo)
3621
+ end
3622
+ end
3623
+
3624
+ # Perform eager loading for a single association using the loader and eager options.
3625
+ def perform_eager_load(loader, eo)
3626
+ loader.call(eo)
2931
3627
  end
2932
3628
 
2933
3629
  # Return a subquery expression for filering by a many_to_many association
@@ -2982,6 +3678,10 @@ END
2982
3678
  end
2983
3679
  alias one_to_one_association_filter_expression one_to_many_association_filter_expression
2984
3680
 
3681
+ def non_sql_option?(key)
3682
+ super || key == :eager || key == :eager_graph
3683
+ end
3684
+
2985
3685
  # Build associations from the graph if #eager_graph was used,
2986
3686
  # and/or load other associations if #eager was used.
2987
3687
  def post_load(all_records)
@@ -3011,7 +3711,6 @@ END
3011
3711
  # Hash with table alias symbol keys and [limit, offset] values
3012
3712
  attr_reader :limit_map
3013
3713
 
3014
- # Hash with table alias symbol keys and callable values used to create model instances
3015
3714
  # The table alias symbol for the primary model
3016
3715
  attr_reader :master
3017
3716
 
@@ -3054,19 +3753,22 @@ END
3054
3753
  after_load_map = @after_load_map = {}
3055
3754
  reflection_map.each do |k, v|
3056
3755
  alias_map[k] = v[:name]
3057
- after_load_map[k] = v[:after_load] unless v[:after_load].empty?
3756
+ after_load_map[k] = v[:after_load] if v[:after_load]
3058
3757
  type_map[k] = if v.returns_array?
3059
3758
  true
3060
3759
  elsif (limit_and_offset = limit_map[k]) && !limit_and_offset.last.nil?
3061
3760
  :offset
3062
3761
  end
3063
3762
  end
3763
+ after_load_map.freeze
3764
+ alias_map.freeze
3765
+ type_map.freeze
3064
3766
 
3065
3767
  # Make dependency map hash out of requirements array for each association.
3066
3768
  # This builds a tree of dependencies that will be used for recursion
3067
3769
  # to ensure that all parts of the object graph are loaded into the
3068
3770
  # appropriate subordinate association.
3069
- @dependency_map = {}
3771
+ dependency_map = @dependency_map = {}
3070
3772
  # Sort the associations by requirements length, so that
3071
3773
  # requirements are added to the dependency hash before their
3072
3774
  # dependencies.
@@ -3082,20 +3784,14 @@ END
3082
3784
  hash[ta] = {}
3083
3785
  end
3084
3786
  end
3787
+ freezer = lambda do |h|
3788
+ h.freeze
3789
+ h.each_value(&freezer)
3790
+ end
3791
+ freezer.call(dependency_map)
3085
3792
 
3086
- # This mapping is used to make sure that duplicate entries in the
3087
- # result set are mapped to a single record. For example, using a
3088
- # single one_to_many association with 10 associated records,
3089
- # the main object column values appear in the object graph 10 times.
3090
- # We map by primary key, if available, or by the object's entire values,
3091
- # if not. The mapping must be per table, so create sub maps for each table
3092
- # alias.
3093
- records_map = {@master=>{}}
3094
- alias_map.keys.each{|ta| records_map[ta] = {}}
3095
- @records_map = records_map
3096
-
3097
3793
  datasets = opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
3098
- column_aliases = opts[:graph_aliases] || opts[:graph][:column_aliases]
3794
+ column_aliases = opts[:graph][:column_aliases]
3099
3795
  primary_keys = {}
3100
3796
  column_maps = {}
3101
3797
  models = {}
@@ -3119,9 +3815,9 @@ END
3119
3815
  h.select{|ca, c| primary_keys[ta] = ca if pk == c}
3120
3816
  end
3121
3817
  end
3122
- @column_maps = column_maps
3123
- @primary_keys = primary_keys
3124
- @row_procs = row_procs
3818
+ @column_maps = column_maps.freeze
3819
+ @primary_keys = primary_keys.freeze
3820
+ @row_procs = row_procs.freeze
3125
3821
 
3126
3822
  # For performance, create two special maps for the master table,
3127
3823
  # so you can skip a hash lookup.
@@ -3133,22 +3829,35 @@ END
3133
3829
  # used for performance, to get all values in one hash lookup instead of
3134
3830
  # separate hash lookups for each data structure.
3135
3831
  ta_map = {}
3136
- alias_map.keys.each do |ta|
3137
- ta_map[ta] = [records_map[ta], row_procs[ta], alias_map[ta], type_map[ta], reciprocal_map[ta]]
3832
+ alias_map.each_key do |ta|
3833
+ ta_map[ta] = [row_procs[ta], alias_map[ta], type_map[ta], reciprocal_map[ta]].freeze
3138
3834
  end
3139
- @ta_map = ta_map
3835
+ @ta_map = ta_map.freeze
3836
+ freeze
3140
3837
  end
3141
3838
 
3142
3839
  # Return an array of primary model instances with the associations cache prepopulated
3143
3840
  # for all model objects (both primary and associated).
3144
3841
  def load(hashes)
3842
+ # This mapping is used to make sure that duplicate entries in the
3843
+ # result set are mapped to a single record. For example, using a
3844
+ # single one_to_many association with 10 associated records,
3845
+ # the main object column values appear in the object graph 10 times.
3846
+ # We map by primary key, if available, or by the object's entire values,
3847
+ # if not. The mapping must be per table, so create sub maps for each table
3848
+ # alias.
3849
+ @records_map = records_map = {}
3850
+ alias_map.keys.each{|ta| records_map[ta] = {}}
3851
+
3145
3852
  master = master()
3146
3853
 
3147
3854
  # Assign to local variables for speed increase
3148
3855
  rp = row_procs[master]
3149
- rm = records_map[master]
3856
+ rm = records_map[master] = {}
3150
3857
  dm = dependency_map
3151
3858
 
3859
+ records_map.freeze
3860
+
3152
3861
  # This will hold the final record set that we will be replacing the object graph with.
3153
3862
  records = []
3154
3863
 
@@ -3169,6 +3878,9 @@ END
3169
3878
  # Run after_load procs if there are any
3170
3879
  post_process(records, dm) if @unique || !after_load_map.empty? || !limit_map.empty?
3171
3880
 
3881
+ records_map.each_value(&:freeze)
3882
+ freeze
3883
+
3172
3884
  records
3173
3885
  end
3174
3886
 
@@ -3188,7 +3900,17 @@ END
3188
3900
  end
3189
3901
  key = hkey(ta_h)
3190
3902
  end
3191
- rm, rp, assoc_name, tm, rcm = @ta_map[ta]
3903
+ rp, assoc_name, tm, rcm = @ta_map[ta]
3904
+ rm = records_map[ta]
3905
+
3906
+ # Check type map for all dependencies, and use a unique
3907
+ # object if any are dependencies for multiple objects,
3908
+ # to prevent duplicate objects from showing up in the case
3909
+ # the normal duplicate removal code is not being used.
3910
+ if !@unique && !deps.empty? && deps.any?{|dep_key,_| @ta_map[dep_key][2]}
3911
+ key = [current.object_id, key]
3912
+ end
3913
+
3192
3914
  unless rec = rm[key]
3193
3915
  rec = rm[key] = rp.call(hfor(ta, h))
3194
3916
  end
@@ -3217,7 +3939,7 @@ END
3217
3939
  # Return a suitable hash key for any subhash +h+, which is an array of values by column order.
3218
3940
  # This is only used if the primary key cannot be used.
3219
3941
  def hkey(h)
3220
- h.sort_by{|x| x[0].to_s}
3942
+ h.sort_by{|x| x[0]}
3221
3943
  end
3222
3944
 
3223
3945
  # Return the subhash for the master table by parsing the values out of the main hash +h+
@@ -3262,7 +3984,7 @@ END
3262
3984
  records.each do |record|
3263
3985
  dependency_map.each do |ta, deps|
3264
3986
  assoc_name = alias_map[ta]
3265
- list = record.send(assoc_name)
3987
+ list = record.public_send(assoc_name)
3266
3988
  rec_list = if type_map[ta]
3267
3989
  list.uniq!
3268
3990
  if lo = limit_map[ta]