sequel 4.26.0 → 5.37.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 (692) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG +405 -5656
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +232 -157
  5. data/bin/sequel +32 -9
  6. data/doc/advanced_associations.rdoc +252 -188
  7. data/doc/association_basics.rdoc +231 -273
  8. data/doc/bin_sequel.rdoc +5 -3
  9. data/doc/cheat_sheet.rdoc +75 -48
  10. data/doc/code_order.rdoc +28 -10
  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/mass_assignment.rdoc +74 -31
  16. data/doc/migration.rdoc +72 -46
  17. data/doc/model_dataset_method_design.rdoc +129 -0
  18. data/doc/model_hooks.rdoc +15 -25
  19. data/doc/model_plugins.rdoc +12 -12
  20. data/doc/mssql_stored_procedures.rdoc +3 -3
  21. data/doc/object_model.rdoc +59 -69
  22. data/doc/opening_databases.rdoc +84 -94
  23. data/doc/postgresql.rdoc +268 -38
  24. data/doc/prepared_statements.rdoc +29 -24
  25. data/doc/querying.rdoc +184 -164
  26. data/doc/reflection.rdoc +5 -6
  27. data/doc/release_notes/5.0.0.txt +159 -0
  28. data/doc/release_notes/5.1.0.txt +31 -0
  29. data/doc/release_notes/5.10.0.txt +84 -0
  30. data/doc/release_notes/5.11.0.txt +83 -0
  31. data/doc/release_notes/5.12.0.txt +141 -0
  32. data/doc/release_notes/5.13.0.txt +27 -0
  33. data/doc/release_notes/5.14.0.txt +63 -0
  34. data/doc/release_notes/5.15.0.txt +39 -0
  35. data/doc/release_notes/5.16.0.txt +110 -0
  36. data/doc/release_notes/5.17.0.txt +31 -0
  37. data/doc/release_notes/5.18.0.txt +69 -0
  38. data/doc/release_notes/5.19.0.txt +28 -0
  39. data/doc/release_notes/5.2.0.txt +33 -0
  40. data/doc/release_notes/5.20.0.txt +89 -0
  41. data/doc/release_notes/5.21.0.txt +87 -0
  42. data/doc/release_notes/5.22.0.txt +48 -0
  43. data/doc/release_notes/5.23.0.txt +56 -0
  44. data/doc/release_notes/5.24.0.txt +56 -0
  45. data/doc/release_notes/5.25.0.txt +32 -0
  46. data/doc/release_notes/5.26.0.txt +35 -0
  47. data/doc/release_notes/5.27.0.txt +21 -0
  48. data/doc/release_notes/5.28.0.txt +16 -0
  49. data/doc/release_notes/5.29.0.txt +22 -0
  50. data/doc/release_notes/5.3.0.txt +121 -0
  51. data/doc/release_notes/5.30.0.txt +20 -0
  52. data/doc/release_notes/5.31.0.txt +148 -0
  53. data/doc/release_notes/5.32.0.txt +46 -0
  54. data/doc/release_notes/5.33.0.txt +24 -0
  55. data/doc/release_notes/5.34.0.txt +40 -0
  56. data/doc/release_notes/5.35.0.txt +56 -0
  57. data/doc/release_notes/5.36.0.txt +60 -0
  58. data/doc/release_notes/5.37.0.txt +30 -0
  59. data/doc/release_notes/5.4.0.txt +80 -0
  60. data/doc/release_notes/5.5.0.txt +61 -0
  61. data/doc/release_notes/5.6.0.txt +31 -0
  62. data/doc/release_notes/5.7.0.txt +108 -0
  63. data/doc/release_notes/5.8.0.txt +170 -0
  64. data/doc/release_notes/5.9.0.txt +99 -0
  65. data/doc/schema_modification.rdoc +102 -77
  66. data/doc/security.rdoc +160 -87
  67. data/doc/sharding.rdoc +74 -47
  68. data/doc/sql.rdoc +135 -122
  69. data/doc/testing.rdoc +34 -18
  70. data/doc/thread_safety.rdoc +2 -4
  71. data/doc/transactions.rdoc +101 -19
  72. data/doc/validations.rdoc +64 -51
  73. data/doc/virtual_rows.rdoc +90 -109
  74. data/lib/sequel.rb +3 -1
  75. data/lib/sequel/adapters/ado.rb +154 -22
  76. data/lib/sequel/adapters/ado/access.rb +21 -21
  77. data/lib/sequel/adapters/ado/mssql.rb +8 -15
  78. data/lib/sequel/adapters/amalgalite.rb +17 -25
  79. data/lib/sequel/adapters/ibmdb.rb +52 -58
  80. data/lib/sequel/adapters/jdbc.rb +149 -127
  81. data/lib/sequel/adapters/jdbc/db2.rb +32 -40
  82. data/lib/sequel/adapters/jdbc/derby.rb +56 -58
  83. data/lib/sequel/adapters/jdbc/h2.rb +40 -30
  84. data/lib/sequel/adapters/jdbc/hsqldb.rb +22 -33
  85. data/lib/sequel/adapters/jdbc/jtds.rb +4 -10
  86. data/lib/sequel/adapters/jdbc/mssql.rb +6 -12
  87. data/lib/sequel/adapters/jdbc/mysql.rb +17 -18
  88. data/lib/sequel/adapters/jdbc/oracle.rb +25 -19
  89. data/lib/sequel/adapters/jdbc/postgresql.rb +90 -69
  90. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +14 -24
  91. data/lib/sequel/adapters/jdbc/sqlite.rb +50 -12
  92. data/lib/sequel/adapters/jdbc/sqlserver.rb +36 -9
  93. data/lib/sequel/adapters/jdbc/transactions.rb +25 -39
  94. data/lib/sequel/adapters/mock.rb +104 -113
  95. data/lib/sequel/adapters/mysql.rb +42 -61
  96. data/lib/sequel/adapters/mysql2.rb +126 -35
  97. data/lib/sequel/adapters/odbc.rb +21 -28
  98. data/lib/sequel/adapters/odbc/db2.rb +3 -1
  99. data/lib/sequel/adapters/odbc/mssql.rb +11 -15
  100. data/lib/sequel/adapters/odbc/oracle.rb +11 -0
  101. data/lib/sequel/adapters/oracle.rb +62 -68
  102. data/lib/sequel/adapters/postgres.rb +257 -311
  103. data/lib/sequel/adapters/postgresql.rb +3 -1
  104. data/lib/sequel/adapters/shared/access.rb +75 -79
  105. data/lib/sequel/adapters/shared/db2.rb +96 -74
  106. data/lib/sequel/adapters/shared/mssql.rb +258 -213
  107. data/lib/sequel/adapters/shared/mysql.rb +284 -216
  108. data/lib/sequel/adapters/shared/oracle.rb +175 -60
  109. data/lib/sequel/adapters/shared/postgres.rb +829 -383
  110. data/lib/sequel/adapters/shared/sqlanywhere.rb +105 -127
  111. data/lib/sequel/adapters/shared/sqlite.rb +382 -159
  112. data/lib/sequel/adapters/sqlanywhere.rb +53 -38
  113. data/lib/sequel/adapters/sqlite.rb +111 -105
  114. data/lib/sequel/adapters/tinytds.rb +38 -46
  115. data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +8 -9
  116. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +7 -5
  117. data/lib/sequel/adapters/utils/mysql_mysql2.rb +87 -0
  118. data/lib/sequel/adapters/utils/mysql_prepared_statements.rb +56 -0
  119. data/lib/sequel/adapters/utils/replace.rb +3 -4
  120. data/lib/sequel/adapters/utils/split_alter_table.rb +2 -0
  121. data/lib/sequel/adapters/utils/stored_procedures.rb +9 -22
  122. data/lib/sequel/adapters/utils/unmodified_identifiers.rb +28 -0
  123. data/lib/sequel/ast_transformer.rb +13 -89
  124. data/lib/sequel/connection_pool.rb +54 -26
  125. data/lib/sequel/connection_pool/sharded_single.rb +19 -12
  126. data/lib/sequel/connection_pool/sharded_threaded.rb +160 -111
  127. data/lib/sequel/connection_pool/single.rb +21 -12
  128. data/lib/sequel/connection_pool/threaded.rb +137 -119
  129. data/lib/sequel/core.rb +352 -320
  130. data/lib/sequel/database.rb +19 -2
  131. data/lib/sequel/database/connecting.rb +70 -55
  132. data/lib/sequel/database/dataset.rb +15 -5
  133. data/lib/sequel/database/dataset_defaults.rb +20 -102
  134. data/lib/sequel/database/features.rb +20 -4
  135. data/lib/sequel/database/logging.rb +25 -7
  136. data/lib/sequel/database/misc.rb +132 -118
  137. data/lib/sequel/database/query.rb +51 -28
  138. data/lib/sequel/database/schema_generator.rb +188 -75
  139. data/lib/sequel/database/schema_methods.rb +161 -92
  140. data/lib/sequel/database/transactions.rb +260 -58
  141. data/lib/sequel/dataset.rb +28 -12
  142. data/lib/sequel/dataset/actions.rb +354 -170
  143. data/lib/sequel/dataset/dataset_module.rb +46 -0
  144. data/lib/sequel/dataset/features.rb +81 -34
  145. data/lib/sequel/dataset/graph.rb +82 -58
  146. data/lib/sequel/dataset/misc.rb +139 -47
  147. data/lib/sequel/dataset/placeholder_literalizer.rb +66 -26
  148. data/lib/sequel/dataset/prepared_statements.rb +188 -85
  149. data/lib/sequel/dataset/query.rb +428 -214
  150. data/lib/sequel/dataset/sql.rb +446 -339
  151. data/lib/sequel/deprecated.rb +14 -2
  152. data/lib/sequel/exceptions.rb +48 -16
  153. data/lib/sequel/extensions/_model_constraint_validations.rb +16 -0
  154. data/lib/sequel/extensions/_model_pg_row.rb +43 -0
  155. data/lib/sequel/extensions/_pretty_table.rb +10 -9
  156. data/lib/sequel/extensions/any_not_empty.rb +45 -0
  157. data/lib/sequel/extensions/arbitrary_servers.rb +15 -11
  158. data/lib/sequel/extensions/auto_literal_strings.rb +74 -0
  159. data/lib/sequel/extensions/blank.rb +2 -0
  160. data/lib/sequel/extensions/caller_logging.rb +79 -0
  161. data/lib/sequel/extensions/columns_introspection.rb +9 -4
  162. data/lib/sequel/extensions/connection_expiration.rb +99 -0
  163. data/lib/sequel/extensions/connection_validator.rb +26 -13
  164. data/lib/sequel/extensions/constant_sql_override.rb +65 -0
  165. data/lib/sequel/extensions/constraint_validations.rb +93 -38
  166. data/lib/sequel/extensions/core_extensions.rb +45 -53
  167. data/lib/sequel/extensions/core_refinements.rb +44 -46
  168. data/lib/sequel/extensions/current_datetime_timestamp.rb +5 -4
  169. data/lib/sequel/extensions/dataset_source_alias.rb +4 -0
  170. data/lib/sequel/extensions/date_arithmetic.rb +42 -16
  171. data/lib/sequel/extensions/datetime_parse_to_time.rb +37 -0
  172. data/lib/sequel/extensions/duplicate_columns_handler.rb +94 -0
  173. data/lib/sequel/extensions/empty_array_consider_nulls.rb +7 -3
  174. data/lib/sequel/extensions/error_sql.rb +7 -3
  175. data/lib/sequel/extensions/escaped_like.rb +100 -0
  176. data/lib/sequel/extensions/eval_inspect.rb +14 -15
  177. data/lib/sequel/extensions/exclude_or_null.rb +68 -0
  178. data/lib/sequel/extensions/fiber_concurrency.rb +24 -0
  179. data/lib/sequel/extensions/freeze_datasets.rb +3 -0
  180. data/lib/sequel/extensions/from_block.rb +2 -31
  181. data/lib/sequel/extensions/graph_each.rb +19 -6
  182. data/lib/sequel/extensions/identifier_mangling.rb +180 -0
  183. data/lib/sequel/extensions/implicit_subquery.rb +48 -0
  184. data/lib/sequel/extensions/index_caching.rb +109 -0
  185. data/lib/sequel/extensions/inflector.rb +8 -4
  186. data/lib/sequel/extensions/integer64.rb +32 -0
  187. data/lib/sequel/extensions/looser_typecasting.rb +19 -9
  188. data/lib/sequel/extensions/migration.rb +132 -80
  189. data/lib/sequel/extensions/mssql_emulate_lateral_with_apply.rb +4 -0
  190. data/lib/sequel/extensions/named_timezones.rb +88 -23
  191. data/lib/sequel/extensions/no_auto_literal_strings.rb +4 -0
  192. data/lib/sequel/extensions/null_dataset.rb +12 -8
  193. data/lib/sequel/extensions/pagination.rb +35 -28
  194. data/lib/sequel/extensions/pg_array.rb +227 -316
  195. data/lib/sequel/extensions/pg_array_ops.rb +19 -7
  196. data/lib/sequel/extensions/pg_enum.rb +69 -24
  197. data/lib/sequel/extensions/pg_extended_date_support.rb +250 -0
  198. data/lib/sequel/extensions/pg_hstore.rb +50 -59
  199. data/lib/sequel/extensions/pg_hstore_ops.rb +9 -3
  200. data/lib/sequel/extensions/pg_inet.rb +34 -15
  201. data/lib/sequel/extensions/pg_inet_ops.rb +5 -1
  202. data/lib/sequel/extensions/pg_interval.rb +26 -26
  203. data/lib/sequel/extensions/pg_json.rb +422 -141
  204. data/lib/sequel/extensions/pg_json_ops.rb +248 -9
  205. data/lib/sequel/extensions/pg_loose_count.rb +5 -1
  206. data/lib/sequel/extensions/pg_range.rb +162 -146
  207. data/lib/sequel/extensions/pg_range_ops.rb +10 -5
  208. data/lib/sequel/extensions/pg_row.rb +53 -87
  209. data/lib/sequel/extensions/pg_row_ops.rb +36 -13
  210. data/lib/sequel/extensions/pg_static_cache_updater.rb +6 -2
  211. data/lib/sequel/extensions/pg_timestamptz.rb +28 -0
  212. data/lib/sequel/extensions/pretty_table.rb +4 -0
  213. data/lib/sequel/extensions/query.rb +12 -7
  214. data/lib/sequel/extensions/round_timestamps.rb +6 -9
  215. data/lib/sequel/extensions/run_transaction_hooks.rb +72 -0
  216. data/lib/sequel/extensions/s.rb +59 -0
  217. data/lib/sequel/extensions/schema_caching.rb +14 -1
  218. data/lib/sequel/extensions/schema_dumper.rb +83 -55
  219. data/lib/sequel/extensions/select_remove.rb +8 -4
  220. data/lib/sequel/extensions/sequel_4_dataset_methods.rb +85 -0
  221. data/lib/sequel/extensions/server_block.rb +50 -17
  222. data/lib/sequel/extensions/server_logging.rb +61 -0
  223. data/lib/sequel/extensions/split_array_nil.rb +8 -4
  224. data/lib/sequel/extensions/sql_comments.rb +96 -0
  225. data/lib/sequel/extensions/sql_expr.rb +4 -1
  226. data/lib/sequel/extensions/string_agg.rb +181 -0
  227. data/lib/sequel/extensions/string_date_time.rb +2 -0
  228. data/lib/sequel/extensions/symbol_aref.rb +53 -0
  229. data/lib/sequel/extensions/symbol_aref_refinement.rb +43 -0
  230. data/lib/sequel/extensions/symbol_as.rb +23 -0
  231. data/lib/sequel/extensions/symbol_as_refinement.rb +37 -0
  232. data/lib/sequel/extensions/synchronize_sql.rb +45 -0
  233. data/lib/sequel/extensions/thread_local_timezones.rb +4 -0
  234. data/lib/sequel/extensions/to_dot.rb +15 -5
  235. data/lib/sequel/extensions/virtual_row_method_block.rb +44 -0
  236. data/lib/sequel/model.rb +36 -126
  237. data/lib/sequel/model/associations.rb +850 -257
  238. data/lib/sequel/model/base.rb +652 -764
  239. data/lib/sequel/model/dataset_module.rb +13 -10
  240. data/lib/sequel/model/default_inflections.rb +3 -1
  241. data/lib/sequel/model/errors.rb +3 -3
  242. data/lib/sequel/model/exceptions.rb +12 -12
  243. data/lib/sequel/model/inflections.rb +8 -19
  244. data/lib/sequel/model/plugins.rb +111 -0
  245. data/lib/sequel/plugins/accessed_columns.rb +2 -0
  246. data/lib/sequel/plugins/active_model.rb +32 -7
  247. data/lib/sequel/plugins/after_initialize.rb +3 -1
  248. data/lib/sequel/plugins/association_dependencies.rb +27 -18
  249. data/lib/sequel/plugins/association_lazy_eager_option.rb +66 -0
  250. data/lib/sequel/plugins/association_multi_add_remove.rb +85 -0
  251. data/lib/sequel/plugins/association_pks.rb +181 -83
  252. data/lib/sequel/plugins/association_proxies.rb +33 -9
  253. data/lib/sequel/plugins/auto_validations.rb +58 -23
  254. data/lib/sequel/plugins/before_after_save.rb +8 -0
  255. data/lib/sequel/plugins/blacklist_security.rb +23 -12
  256. data/lib/sequel/plugins/boolean_readers.rb +9 -6
  257. data/lib/sequel/plugins/boolean_subsets.rb +64 -0
  258. data/lib/sequel/plugins/caching.rb +27 -16
  259. data/lib/sequel/plugins/class_table_inheritance.rb +192 -94
  260. data/lib/sequel/plugins/column_conflicts.rb +18 -3
  261. data/lib/sequel/plugins/column_select.rb +9 -5
  262. data/lib/sequel/plugins/columns_updated.rb +42 -0
  263. data/lib/sequel/plugins/composition.rb +36 -24
  264. data/lib/sequel/plugins/constraint_validations.rb +37 -16
  265. data/lib/sequel/plugins/csv_serializer.rb +58 -35
  266. data/lib/sequel/plugins/dataset_associations.rb +60 -18
  267. data/lib/sequel/plugins/def_dataset_method.rb +90 -0
  268. data/lib/sequel/plugins/defaults_setter.rb +74 -13
  269. data/lib/sequel/plugins/delay_add_association.rb +4 -1
  270. data/lib/sequel/plugins/dirty.rb +65 -24
  271. data/lib/sequel/plugins/eager_each.rb +27 -3
  272. data/lib/sequel/plugins/eager_graph_eager.rb +139 -0
  273. data/lib/sequel/plugins/empty_failure_backtraces.rb +38 -0
  274. data/lib/sequel/plugins/error_splitter.rb +19 -12
  275. data/lib/sequel/plugins/finder.rb +246 -0
  276. data/lib/sequel/plugins/forbid_lazy_load.rb +216 -0
  277. data/lib/sequel/plugins/force_encoding.rb +9 -12
  278. data/lib/sequel/plugins/hook_class_methods.rb +39 -54
  279. data/lib/sequel/plugins/input_transformer.rb +20 -10
  280. data/lib/sequel/plugins/insert_conflict.rb +72 -0
  281. data/lib/sequel/plugins/insert_returning_select.rb +4 -2
  282. data/lib/sequel/plugins/instance_filters.rb +12 -8
  283. data/lib/sequel/plugins/instance_hooks.rb +36 -17
  284. data/lib/sequel/plugins/instance_specific_default.rb +113 -0
  285. data/lib/sequel/plugins/inverted_subsets.rb +24 -13
  286. data/lib/sequel/plugins/json_serializer.rb +123 -47
  287. data/lib/sequel/plugins/lazy_attributes.rb +20 -14
  288. data/lib/sequel/plugins/list.rb +40 -26
  289. data/lib/sequel/plugins/many_through_many.rb +28 -12
  290. data/lib/sequel/plugins/modification_detection.rb +17 -5
  291. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -5
  292. data/lib/sequel/plugins/nested_attributes.rb +55 -28
  293. data/lib/sequel/plugins/optimistic_locking.rb +5 -3
  294. data/lib/sequel/plugins/pg_array_associations.rb +52 -18
  295. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +348 -0
  296. data/lib/sequel/plugins/pg_row.rb +7 -51
  297. data/lib/sequel/plugins/prepared_statements.rb +53 -72
  298. data/lib/sequel/plugins/prepared_statements_safe.rb +13 -5
  299. data/lib/sequel/plugins/rcte_tree.rb +43 -63
  300. data/lib/sequel/plugins/serialization.rb +37 -44
  301. data/lib/sequel/plugins/serialization_modification_detection.rb +3 -1
  302. data/lib/sequel/plugins/sharding.rb +17 -10
  303. data/lib/sequel/plugins/single_table_inheritance.rb +62 -28
  304. data/lib/sequel/plugins/singular_table_names.rb +2 -0
  305. data/lib/sequel/plugins/skip_create_refresh.rb +5 -3
  306. data/lib/sequel/plugins/skip_saving_columns.rb +108 -0
  307. data/lib/sequel/plugins/split_values.rb +13 -6
  308. data/lib/sequel/plugins/static_cache.rb +79 -53
  309. data/lib/sequel/plugins/static_cache_cache.rb +53 -0
  310. data/lib/sequel/plugins/string_stripper.rb +5 -3
  311. data/lib/sequel/plugins/subclasses.rb +20 -2
  312. data/lib/sequel/plugins/subset_conditions.rb +48 -0
  313. data/lib/sequel/plugins/table_select.rb +4 -2
  314. data/lib/sequel/plugins/tactical_eager_loading.rb +120 -6
  315. data/lib/sequel/plugins/throw_failures.rb +110 -0
  316. data/lib/sequel/plugins/timestamps.rb +22 -8
  317. data/lib/sequel/plugins/touch.rb +21 -8
  318. data/lib/sequel/plugins/tree.rb +57 -30
  319. data/lib/sequel/plugins/typecast_on_load.rb +14 -4
  320. data/lib/sequel/plugins/unlimited_update.rb +3 -7
  321. data/lib/sequel/plugins/update_or_create.rb +6 -4
  322. data/lib/sequel/plugins/update_primary_key.rb +3 -1
  323. data/lib/sequel/plugins/update_refresh.rb +28 -15
  324. data/lib/sequel/plugins/uuid.rb +70 -0
  325. data/lib/sequel/plugins/validate_associated.rb +20 -0
  326. data/lib/sequel/plugins/validation_class_methods.rb +40 -19
  327. data/lib/sequel/plugins/validation_contexts.rb +49 -0
  328. data/lib/sequel/plugins/validation_helpers.rb +49 -31
  329. data/lib/sequel/plugins/whitelist_security.rb +122 -0
  330. data/lib/sequel/plugins/xml_serializer.rb +31 -30
  331. data/lib/sequel/sql.rb +479 -329
  332. data/lib/sequel/timezones.rb +62 -32
  333. data/lib/sequel/version.rb +10 -3
  334. metadata +177 -477
  335. data/Rakefile +0 -165
  336. data/doc/active_record.rdoc +0 -912
  337. data/doc/release_notes/1.0.txt +0 -38
  338. data/doc/release_notes/1.1.txt +0 -143
  339. data/doc/release_notes/1.3.txt +0 -101
  340. data/doc/release_notes/1.4.0.txt +0 -53
  341. data/doc/release_notes/1.5.0.txt +0 -155
  342. data/doc/release_notes/2.0.0.txt +0 -298
  343. data/doc/release_notes/2.1.0.txt +0 -271
  344. data/doc/release_notes/2.10.0.txt +0 -328
  345. data/doc/release_notes/2.11.0.txt +0 -215
  346. data/doc/release_notes/2.12.0.txt +0 -534
  347. data/doc/release_notes/2.2.0.txt +0 -253
  348. data/doc/release_notes/2.3.0.txt +0 -88
  349. data/doc/release_notes/2.4.0.txt +0 -106
  350. data/doc/release_notes/2.5.0.txt +0 -137
  351. data/doc/release_notes/2.6.0.txt +0 -157
  352. data/doc/release_notes/2.7.0.txt +0 -166
  353. data/doc/release_notes/2.8.0.txt +0 -171
  354. data/doc/release_notes/2.9.0.txt +0 -97
  355. data/doc/release_notes/3.0.0.txt +0 -221
  356. data/doc/release_notes/3.1.0.txt +0 -406
  357. data/doc/release_notes/3.10.0.txt +0 -286
  358. data/doc/release_notes/3.11.0.txt +0 -254
  359. data/doc/release_notes/3.12.0.txt +0 -304
  360. data/doc/release_notes/3.13.0.txt +0 -210
  361. data/doc/release_notes/3.14.0.txt +0 -118
  362. data/doc/release_notes/3.15.0.txt +0 -78
  363. data/doc/release_notes/3.16.0.txt +0 -45
  364. data/doc/release_notes/3.17.0.txt +0 -58
  365. data/doc/release_notes/3.18.0.txt +0 -120
  366. data/doc/release_notes/3.19.0.txt +0 -67
  367. data/doc/release_notes/3.2.0.txt +0 -268
  368. data/doc/release_notes/3.20.0.txt +0 -41
  369. data/doc/release_notes/3.21.0.txt +0 -87
  370. data/doc/release_notes/3.22.0.txt +0 -39
  371. data/doc/release_notes/3.23.0.txt +0 -172
  372. data/doc/release_notes/3.24.0.txt +0 -420
  373. data/doc/release_notes/3.25.0.txt +0 -88
  374. data/doc/release_notes/3.26.0.txt +0 -88
  375. data/doc/release_notes/3.27.0.txt +0 -82
  376. data/doc/release_notes/3.28.0.txt +0 -304
  377. data/doc/release_notes/3.29.0.txt +0 -459
  378. data/doc/release_notes/3.3.0.txt +0 -192
  379. data/doc/release_notes/3.30.0.txt +0 -135
  380. data/doc/release_notes/3.31.0.txt +0 -146
  381. data/doc/release_notes/3.32.0.txt +0 -202
  382. data/doc/release_notes/3.33.0.txt +0 -157
  383. data/doc/release_notes/3.34.0.txt +0 -671
  384. data/doc/release_notes/3.35.0.txt +0 -144
  385. data/doc/release_notes/3.36.0.txt +0 -245
  386. data/doc/release_notes/3.37.0.txt +0 -338
  387. data/doc/release_notes/3.38.0.txt +0 -234
  388. data/doc/release_notes/3.39.0.txt +0 -237
  389. data/doc/release_notes/3.4.0.txt +0 -325
  390. data/doc/release_notes/3.40.0.txt +0 -73
  391. data/doc/release_notes/3.41.0.txt +0 -155
  392. data/doc/release_notes/3.42.0.txt +0 -74
  393. data/doc/release_notes/3.43.0.txt +0 -105
  394. data/doc/release_notes/3.44.0.txt +0 -152
  395. data/doc/release_notes/3.45.0.txt +0 -179
  396. data/doc/release_notes/3.46.0.txt +0 -122
  397. data/doc/release_notes/3.47.0.txt +0 -270
  398. data/doc/release_notes/3.48.0.txt +0 -477
  399. data/doc/release_notes/3.5.0.txt +0 -510
  400. data/doc/release_notes/3.6.0.txt +0 -366
  401. data/doc/release_notes/3.7.0.txt +0 -179
  402. data/doc/release_notes/3.8.0.txt +0 -151
  403. data/doc/release_notes/3.9.0.txt +0 -233
  404. data/doc/release_notes/4.0.0.txt +0 -262
  405. data/doc/release_notes/4.1.0.txt +0 -85
  406. data/doc/release_notes/4.10.0.txt +0 -226
  407. data/doc/release_notes/4.11.0.txt +0 -147
  408. data/doc/release_notes/4.12.0.txt +0 -105
  409. data/doc/release_notes/4.13.0.txt +0 -169
  410. data/doc/release_notes/4.14.0.txt +0 -68
  411. data/doc/release_notes/4.15.0.txt +0 -56
  412. data/doc/release_notes/4.16.0.txt +0 -36
  413. data/doc/release_notes/4.17.0.txt +0 -38
  414. data/doc/release_notes/4.18.0.txt +0 -36
  415. data/doc/release_notes/4.19.0.txt +0 -45
  416. data/doc/release_notes/4.2.0.txt +0 -129
  417. data/doc/release_notes/4.20.0.txt +0 -79
  418. data/doc/release_notes/4.21.0.txt +0 -94
  419. data/doc/release_notes/4.22.0.txt +0 -72
  420. data/doc/release_notes/4.23.0.txt +0 -65
  421. data/doc/release_notes/4.24.0.txt +0 -99
  422. data/doc/release_notes/4.25.0.txt +0 -181
  423. data/doc/release_notes/4.26.0.txt +0 -44
  424. data/doc/release_notes/4.3.0.txt +0 -40
  425. data/doc/release_notes/4.4.0.txt +0 -92
  426. data/doc/release_notes/4.5.0.txt +0 -34
  427. data/doc/release_notes/4.6.0.txt +0 -30
  428. data/doc/release_notes/4.7.0.txt +0 -103
  429. data/doc/release_notes/4.8.0.txt +0 -175
  430. data/doc/release_notes/4.9.0.txt +0 -190
  431. data/lib/sequel/adapters/cubrid.rb +0 -142
  432. data/lib/sequel/adapters/do.rb +0 -156
  433. data/lib/sequel/adapters/do/mysql.rb +0 -64
  434. data/lib/sequel/adapters/do/postgres.rb +0 -42
  435. data/lib/sequel/adapters/do/sqlite3.rb +0 -40
  436. data/lib/sequel/adapters/jdbc/as400.rb +0 -82
  437. data/lib/sequel/adapters/jdbc/cubrid.rb +0 -62
  438. data/lib/sequel/adapters/jdbc/firebirdsql.rb +0 -34
  439. data/lib/sequel/adapters/jdbc/informix-sqli.rb +0 -31
  440. data/lib/sequel/adapters/jdbc/jdbcprogress.rb +0 -31
  441. data/lib/sequel/adapters/odbc/progress.rb +0 -8
  442. data/lib/sequel/adapters/shared/cubrid.rb +0 -243
  443. data/lib/sequel/adapters/shared/firebird.rb +0 -245
  444. data/lib/sequel/adapters/shared/informix.rb +0 -52
  445. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +0 -150
  446. data/lib/sequel/adapters/shared/progress.rb +0 -38
  447. data/lib/sequel/adapters/swift.rb +0 -158
  448. data/lib/sequel/adapters/swift/mysql.rb +0 -47
  449. data/lib/sequel/adapters/swift/postgres.rb +0 -45
  450. data/lib/sequel/adapters/swift/sqlite.rb +0 -47
  451. data/lib/sequel/adapters/utils/pg_types.rb +0 -68
  452. data/lib/sequel/dataset/mutation.rb +0 -109
  453. data/lib/sequel/extensions/empty_array_ignore_nulls.rb +0 -3
  454. data/lib/sequel/extensions/filter_having.rb +0 -59
  455. data/lib/sequel/extensions/hash_aliases.rb +0 -45
  456. data/lib/sequel/extensions/meta_def.rb +0 -31
  457. data/lib/sequel/extensions/query_literals.rb +0 -80
  458. data/lib/sequel/extensions/ruby18_symbol_extensions.rb +0 -22
  459. data/lib/sequel/extensions/sequel_3_dataset_methods.rb +0 -118
  460. data/lib/sequel/extensions/set_overrides.rb +0 -72
  461. data/lib/sequel/no_core_ext.rb +0 -1
  462. data/lib/sequel/plugins/association_autoreloading.rb +0 -7
  463. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +0 -7
  464. data/lib/sequel/plugins/pg_typecast_on_load.rb +0 -78
  465. data/lib/sequel/plugins/prepared_statements_associations.rb +0 -117
  466. data/lib/sequel/plugins/prepared_statements_with_pk.rb +0 -59
  467. data/lib/sequel/plugins/schema.rb +0 -80
  468. data/lib/sequel/plugins/scissors.rb +0 -33
  469. data/spec/adapters/db2_spec.rb +0 -160
  470. data/spec/adapters/firebird_spec.rb +0 -411
  471. data/spec/adapters/informix_spec.rb +0 -100
  472. data/spec/adapters/mssql_spec.rb +0 -706
  473. data/spec/adapters/mysql_spec.rb +0 -1287
  474. data/spec/adapters/oracle_spec.rb +0 -313
  475. data/spec/adapters/postgres_spec.rb +0 -3725
  476. data/spec/adapters/spec_helper.rb +0 -43
  477. data/spec/adapters/sqlanywhere_spec.rb +0 -170
  478. data/spec/adapters/sqlite_spec.rb +0 -653
  479. data/spec/bin_spec.rb +0 -254
  480. data/spec/core/connection_pool_spec.rb +0 -1016
  481. data/spec/core/database_spec.rb +0 -2531
  482. data/spec/core/dataset_spec.rb +0 -5098
  483. data/spec/core/deprecated_spec.rb +0 -70
  484. data/spec/core/expression_filters_spec.rb +0 -1243
  485. data/spec/core/mock_adapter_spec.rb +0 -462
  486. data/spec/core/object_graph_spec.rb +0 -303
  487. data/spec/core/placeholder_literalizer_spec.rb +0 -163
  488. data/spec/core/schema_generator_spec.rb +0 -179
  489. data/spec/core/schema_spec.rb +0 -1659
  490. data/spec/core/spec_helper.rb +0 -34
  491. data/spec/core/version_spec.rb +0 -7
  492. data/spec/core_extensions_spec.rb +0 -699
  493. data/spec/extensions/accessed_columns_spec.rb +0 -51
  494. data/spec/extensions/active_model_spec.rb +0 -123
  495. data/spec/extensions/after_initialize_spec.rb +0 -24
  496. data/spec/extensions/arbitrary_servers_spec.rb +0 -109
  497. data/spec/extensions/association_dependencies_spec.rb +0 -117
  498. data/spec/extensions/association_pks_spec.rb +0 -365
  499. data/spec/extensions/association_proxies_spec.rb +0 -86
  500. data/spec/extensions/auto_validations_spec.rb +0 -192
  501. data/spec/extensions/blacklist_security_spec.rb +0 -88
  502. data/spec/extensions/blank_spec.rb +0 -69
  503. data/spec/extensions/boolean_readers_spec.rb +0 -93
  504. data/spec/extensions/caching_spec.rb +0 -270
  505. data/spec/extensions/class_table_inheritance_spec.rb +0 -420
  506. data/spec/extensions/column_conflicts_spec.rb +0 -60
  507. data/spec/extensions/column_select_spec.rb +0 -108
  508. data/spec/extensions/columns_introspection_spec.rb +0 -91
  509. data/spec/extensions/composition_spec.rb +0 -242
  510. data/spec/extensions/connection_validator_spec.rb +0 -120
  511. data/spec/extensions/constraint_validations_plugin_spec.rb +0 -274
  512. data/spec/extensions/constraint_validations_spec.rb +0 -325
  513. data/spec/extensions/core_refinements_spec.rb +0 -519
  514. data/spec/extensions/csv_serializer_spec.rb +0 -173
  515. data/spec/extensions/current_datetime_timestamp_spec.rb +0 -27
  516. data/spec/extensions/dataset_associations_spec.rb +0 -311
  517. data/spec/extensions/dataset_source_alias_spec.rb +0 -51
  518. data/spec/extensions/date_arithmetic_spec.rb +0 -150
  519. data/spec/extensions/defaults_setter_spec.rb +0 -101
  520. data/spec/extensions/delay_add_association_spec.rb +0 -52
  521. data/spec/extensions/dirty_spec.rb +0 -180
  522. data/spec/extensions/eager_each_spec.rb +0 -42
  523. data/spec/extensions/empty_array_consider_nulls_spec.rb +0 -24
  524. data/spec/extensions/error_splitter_spec.rb +0 -18
  525. data/spec/extensions/error_sql_spec.rb +0 -20
  526. data/spec/extensions/eval_inspect_spec.rb +0 -73
  527. data/spec/extensions/filter_having_spec.rb +0 -40
  528. data/spec/extensions/force_encoding_spec.rb +0 -114
  529. data/spec/extensions/from_block_spec.rb +0 -21
  530. data/spec/extensions/graph_each_spec.rb +0 -109
  531. data/spec/extensions/hash_aliases_spec.rb +0 -24
  532. data/spec/extensions/hook_class_methods_spec.rb +0 -429
  533. data/spec/extensions/inflector_spec.rb +0 -183
  534. data/spec/extensions/input_transformer_spec.rb +0 -54
  535. data/spec/extensions/insert_returning_select_spec.rb +0 -46
  536. data/spec/extensions/instance_filters_spec.rb +0 -79
  537. data/spec/extensions/instance_hooks_spec.rb +0 -276
  538. data/spec/extensions/inverted_subsets_spec.rb +0 -33
  539. data/spec/extensions/json_serializer_spec.rb +0 -291
  540. data/spec/extensions/lazy_attributes_spec.rb +0 -170
  541. data/spec/extensions/list_spec.rb +0 -267
  542. data/spec/extensions/looser_typecasting_spec.rb +0 -43
  543. data/spec/extensions/many_through_many_spec.rb +0 -2172
  544. data/spec/extensions/meta_def_spec.rb +0 -21
  545. data/spec/extensions/migration_spec.rb +0 -712
  546. data/spec/extensions/modification_detection_spec.rb +0 -80
  547. data/spec/extensions/mssql_optimistic_locking_spec.rb +0 -91
  548. data/spec/extensions/named_timezones_spec.rb +0 -108
  549. data/spec/extensions/nested_attributes_spec.rb +0 -697
  550. data/spec/extensions/null_dataset_spec.rb +0 -85
  551. data/spec/extensions/optimistic_locking_spec.rb +0 -128
  552. data/spec/extensions/pagination_spec.rb +0 -118
  553. data/spec/extensions/pg_array_associations_spec.rb +0 -736
  554. data/spec/extensions/pg_array_ops_spec.rb +0 -143
  555. data/spec/extensions/pg_array_spec.rb +0 -395
  556. data/spec/extensions/pg_enum_spec.rb +0 -92
  557. data/spec/extensions/pg_hstore_ops_spec.rb +0 -236
  558. data/spec/extensions/pg_hstore_spec.rb +0 -206
  559. data/spec/extensions/pg_inet_ops_spec.rb +0 -101
  560. data/spec/extensions/pg_inet_spec.rb +0 -52
  561. data/spec/extensions/pg_interval_spec.rb +0 -76
  562. data/spec/extensions/pg_json_ops_spec.rb +0 -229
  563. data/spec/extensions/pg_json_spec.rb +0 -218
  564. data/spec/extensions/pg_loose_count_spec.rb +0 -17
  565. data/spec/extensions/pg_range_ops_spec.rb +0 -58
  566. data/spec/extensions/pg_range_spec.rb +0 -404
  567. data/spec/extensions/pg_row_ops_spec.rb +0 -60
  568. data/spec/extensions/pg_row_plugin_spec.rb +0 -62
  569. data/spec/extensions/pg_row_spec.rb +0 -360
  570. data/spec/extensions/pg_static_cache_updater_spec.rb +0 -92
  571. data/spec/extensions/pg_typecast_on_load_spec.rb +0 -63
  572. data/spec/extensions/prepared_statements_associations_spec.rb +0 -159
  573. data/spec/extensions/prepared_statements_safe_spec.rb +0 -61
  574. data/spec/extensions/prepared_statements_spec.rb +0 -103
  575. data/spec/extensions/prepared_statements_with_pk_spec.rb +0 -31
  576. data/spec/extensions/pretty_table_spec.rb +0 -92
  577. data/spec/extensions/query_literals_spec.rb +0 -183
  578. data/spec/extensions/query_spec.rb +0 -102
  579. data/spec/extensions/rcte_tree_spec.rb +0 -392
  580. data/spec/extensions/round_timestamps_spec.rb +0 -43
  581. data/spec/extensions/schema_caching_spec.rb +0 -41
  582. data/spec/extensions/schema_dumper_spec.rb +0 -789
  583. data/spec/extensions/schema_spec.rb +0 -117
  584. data/spec/extensions/scissors_spec.rb +0 -26
  585. data/spec/extensions/select_remove_spec.rb +0 -38
  586. data/spec/extensions/sequel_3_dataset_methods_spec.rb +0 -101
  587. data/spec/extensions/serialization_modification_detection_spec.rb +0 -98
  588. data/spec/extensions/serialization_spec.rb +0 -362
  589. data/spec/extensions/server_block_spec.rb +0 -90
  590. data/spec/extensions/set_overrides_spec.rb +0 -61
  591. data/spec/extensions/sharding_spec.rb +0 -198
  592. data/spec/extensions/shared_caching_spec.rb +0 -175
  593. data/spec/extensions/single_table_inheritance_spec.rb +0 -297
  594. data/spec/extensions/singular_table_names_spec.rb +0 -22
  595. data/spec/extensions/skip_create_refresh_spec.rb +0 -17
  596. data/spec/extensions/spec_helper.rb +0 -71
  597. data/spec/extensions/split_array_nil_spec.rb +0 -24
  598. data/spec/extensions/split_values_spec.rb +0 -22
  599. data/spec/extensions/sql_expr_spec.rb +0 -60
  600. data/spec/extensions/static_cache_spec.rb +0 -361
  601. data/spec/extensions/string_date_time_spec.rb +0 -95
  602. data/spec/extensions/string_stripper_spec.rb +0 -68
  603. data/spec/extensions/subclasses_spec.rb +0 -66
  604. data/spec/extensions/table_select_spec.rb +0 -71
  605. data/spec/extensions/tactical_eager_loading_spec.rb +0 -82
  606. data/spec/extensions/thread_local_timezones_spec.rb +0 -67
  607. data/spec/extensions/timestamps_spec.rb +0 -175
  608. data/spec/extensions/to_dot_spec.rb +0 -154
  609. data/spec/extensions/touch_spec.rb +0 -203
  610. data/spec/extensions/tree_spec.rb +0 -274
  611. data/spec/extensions/typecast_on_load_spec.rb +0 -80
  612. data/spec/extensions/unlimited_update_spec.rb +0 -20
  613. data/spec/extensions/update_or_create_spec.rb +0 -87
  614. data/spec/extensions/update_primary_key_spec.rb +0 -100
  615. data/spec/extensions/update_refresh_spec.rb +0 -53
  616. data/spec/extensions/validate_associated_spec.rb +0 -52
  617. data/spec/extensions/validation_class_methods_spec.rb +0 -1027
  618. data/spec/extensions/validation_helpers_spec.rb +0 -541
  619. data/spec/extensions/xml_serializer_spec.rb +0 -207
  620. data/spec/files/bad_down_migration/001_create_alt_basic.rb +0 -4
  621. data/spec/files/bad_down_migration/002_create_alt_advanced.rb +0 -4
  622. data/spec/files/bad_timestamped_migrations/1273253849_create_sessions.rb +0 -9
  623. data/spec/files/bad_timestamped_migrations/1273253851_create_nodes.rb +0 -9
  624. data/spec/files/bad_timestamped_migrations/1273253853_3_create_users.rb +0 -3
  625. data/spec/files/bad_up_migration/001_create_alt_basic.rb +0 -4
  626. data/spec/files/bad_up_migration/002_create_alt_advanced.rb +0 -3
  627. data/spec/files/convert_to_timestamp_migrations/001_create_sessions.rb +0 -9
  628. data/spec/files/convert_to_timestamp_migrations/002_create_nodes.rb +0 -9
  629. data/spec/files/convert_to_timestamp_migrations/003_3_create_users.rb +0 -4
  630. data/spec/files/convert_to_timestamp_migrations/1273253850_create_artists.rb +0 -9
  631. data/spec/files/convert_to_timestamp_migrations/1273253852_create_albums.rb +0 -9
  632. data/spec/files/duplicate_integer_migrations/001_create_alt_advanced.rb +0 -4
  633. data/spec/files/duplicate_integer_migrations/001_create_alt_basic.rb +0 -4
  634. data/spec/files/duplicate_timestamped_migrations/1273253849_create_sessions.rb +0 -9
  635. data/spec/files/duplicate_timestamped_migrations/1273253853_create_nodes.rb +0 -9
  636. data/spec/files/duplicate_timestamped_migrations/1273253853_create_users.rb +0 -4
  637. data/spec/files/integer_migrations/001_create_sessions.rb +0 -9
  638. data/spec/files/integer_migrations/002_create_nodes.rb +0 -9
  639. data/spec/files/integer_migrations/003_3_create_users.rb +0 -4
  640. data/spec/files/interleaved_timestamped_migrations/1273253849_create_sessions.rb +0 -9
  641. data/spec/files/interleaved_timestamped_migrations/1273253850_create_artists.rb +0 -9
  642. data/spec/files/interleaved_timestamped_migrations/1273253851_create_nodes.rb +0 -9
  643. data/spec/files/interleaved_timestamped_migrations/1273253852_create_albums.rb +0 -9
  644. data/spec/files/interleaved_timestamped_migrations/1273253853_3_create_users.rb +0 -4
  645. data/spec/files/missing_integer_migrations/001_create_alt_basic.rb +0 -4
  646. data/spec/files/missing_integer_migrations/003_create_alt_advanced.rb +0 -4
  647. data/spec/files/missing_timestamped_migrations/1273253849_create_sessions.rb +0 -9
  648. data/spec/files/missing_timestamped_migrations/1273253853_3_create_users.rb +0 -4
  649. data/spec/files/reversible_migrations/001_reversible.rb +0 -5
  650. data/spec/files/reversible_migrations/002_reversible.rb +0 -5
  651. data/spec/files/reversible_migrations/003_reversible.rb +0 -5
  652. data/spec/files/reversible_migrations/004_reversible.rb +0 -5
  653. data/spec/files/reversible_migrations/005_reversible.rb +0 -10
  654. data/spec/files/timestamped_migrations/1273253849_create_sessions.rb +0 -9
  655. data/spec/files/timestamped_migrations/1273253851_create_nodes.rb +0 -9
  656. data/spec/files/timestamped_migrations/1273253853_3_create_users.rb +0 -4
  657. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +0 -4
  658. data/spec/files/transaction_specified_migrations/002_create_basic.rb +0 -4
  659. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +0 -3
  660. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +0 -3
  661. data/spec/files/uppercase_timestamped_migrations/1273253849_CREATE_SESSIONS.RB +0 -9
  662. data/spec/files/uppercase_timestamped_migrations/1273253851_CREATE_NODES.RB +0 -9
  663. data/spec/files/uppercase_timestamped_migrations/1273253853_3_CREATE_USERS.RB +0 -4
  664. data/spec/guards_helper.rb +0 -55
  665. data/spec/integration/associations_test.rb +0 -2454
  666. data/spec/integration/database_test.rb +0 -113
  667. data/spec/integration/dataset_test.rb +0 -1808
  668. data/spec/integration/eager_loader_test.rb +0 -687
  669. data/spec/integration/migrator_test.rb +0 -240
  670. data/spec/integration/model_test.rb +0 -226
  671. data/spec/integration/plugin_test.rb +0 -2240
  672. data/spec/integration/prepared_statement_test.rb +0 -467
  673. data/spec/integration/schema_test.rb +0 -817
  674. data/spec/integration/spec_helper.rb +0 -48
  675. data/spec/integration/timezone_test.rb +0 -86
  676. data/spec/integration/transaction_test.rb +0 -374
  677. data/spec/integration/type_test.rb +0 -133
  678. data/spec/model/association_reflection_spec.rb +0 -525
  679. data/spec/model/associations_spec.rb +0 -4426
  680. data/spec/model/base_spec.rb +0 -759
  681. data/spec/model/class_dataset_methods_spec.rb +0 -146
  682. data/spec/model/dataset_methods_spec.rb +0 -149
  683. data/spec/model/eager_loading_spec.rb +0 -2137
  684. data/spec/model/hooks_spec.rb +0 -604
  685. data/spec/model/inflector_spec.rb +0 -26
  686. data/spec/model/model_spec.rb +0 -982
  687. data/spec/model/plugins_spec.rb +0 -299
  688. data/spec/model/record_spec.rb +0 -2147
  689. data/spec/model/spec_helper.rb +0 -46
  690. data/spec/model/validations_spec.rb +0 -193
  691. data/spec/sequel_coverage.rb +0 -15
  692. data/spec/spec_config.rb +0 -10
@@ -0,0 +1,44 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # These modifies virtual row blocks so that you can pass a block
4
+ # when calling a method to change the behavior. It only exists
5
+ # for backwards compatibility with previous Sequel versions, and
6
+ # is not recommended for new applications.
7
+ #
8
+ # To load the extension:
9
+ #
10
+ # Sequel.extension :virtual_row_method_block
11
+
12
+ #
13
+ module Sequel
14
+ module SQL
15
+ class VirtualRow < BasicObject
16
+ include(Module.new do
17
+ # Handle blocks passed to methods and change the behavior.
18
+ def method_missing(m, *args, &block)
19
+ if block
20
+ if args.empty?
21
+ Function.new(m)
22
+ else
23
+ case args.shift
24
+ when :*
25
+ Function.new(m, *args).*
26
+ when :distinct
27
+ Function.new(m, *args).distinct
28
+ when :over
29
+ opts = args.shift || OPTS
30
+ f = Function.new(m, *::Kernel.Array(opts[:args]))
31
+ f = f.* if opts[:*]
32
+ f.over(opts)
33
+ else
34
+ Kernel.raise(Error, 'unsupported VirtualRow method argument used with block')
35
+ end
36
+ end
37
+ else
38
+ super
39
+ end
40
+ end
41
+ end)
42
+ end
43
+ end
44
+ end
@@ -1,67 +1,13 @@
1
- require 'sequel/core'
1
+ # frozen-string-literal: true
2
2
 
3
- module Sequel
4
- # Lets you create a Model subclass with its dataset already set.
5
- # +source+ should be an instance of one of the following classes:
6
- #
7
- # Database :: Sets the database for this model to +source+.
8
- # Generally only useful when subclassing directly
9
- # from the returned class, where the name of the
10
- # subclass sets the table name (which is combined
11
- # with the +Database+ in +source+ to create the
12
- # dataset to use)
13
- # Dataset :: Sets the dataset for this model to +source+.
14
- # other :: Sets the table name for this model to +source+. The
15
- # class will use the default database for model
16
- # classes in order to create the dataset.
17
- #
18
- # The purpose of this method is to set the dataset/database automatically
19
- # for a model class, if the table name doesn't match the implicit
20
- # name. This is neater than using set_dataset inside the class,
21
- # doesn't require a bogus query for the schema.
22
- #
23
- # # Using a symbol
24
- # class Comment < Sequel::Model(:something)
25
- # table_name # => :something
26
- # end
27
- #
28
- # # Using a dataset
29
- # class Comment < Sequel::Model(DB1[:something])
30
- # dataset # => DB1[:something]
31
- # end
32
- #
33
- # # Using a database
34
- # class Comment < Sequel::Model(DB1)
35
- # dataset # => DB1[:comments]
36
- # end
37
- def self.Model(source)
38
- if cache_anonymous_models && (klass = Model::ANONYMOUS_MODEL_CLASSES_MUTEX.synchronize{Model::ANONYMOUS_MODEL_CLASSES[source]})
39
- return klass
40
- end
41
- klass = if source.is_a?(Database)
42
- c = Class.new(Model)
43
- c.db = source
44
- c
45
- else
46
- Class.new(Model).set_dataset(source)
47
- end
48
- Model::ANONYMOUS_MODEL_CLASSES_MUTEX.synchronize{Model::ANONYMOUS_MODEL_CLASSES[source] = klass} if cache_anonymous_models
49
- klass
50
- end
51
-
52
- @cache_anonymous_models = true
53
-
54
- class << self
55
- # Whether to cache the anonymous models created by Sequel::Model(). This is
56
- # required for reloading them correctly (avoiding the superclass mismatch). True
57
- # by default for backwards compatibility.
58
- attr_accessor :cache_anonymous_models
59
- end
3
+ require_relative 'core'
60
4
 
5
+ module Sequel
61
6
  # <tt>Sequel::Model</tt> is an object relational mapper built on top of Sequel core. Each
62
7
  # model class is backed by a dataset instance, and many dataset methods can be
63
8
  # called directly on the class. Model datasets return rows as model instances,
64
- # which have fairly standard ORM instance behavior.
9
+ # which are wrappers around the underlying hash that allow easily updating or
10
+ # deleting the individual row.
65
11
  #
66
12
  # <tt>Sequel::Model</tt> is built completely out of plugins. Plugins can override any class,
67
13
  # instance, or dataset method defined by a previous plugin and call super to get the default
@@ -74,75 +20,30 @@ module Sequel
74
20
  class Model
75
21
  OPTS = Sequel::OPTS
76
22
 
77
- # Map that stores model classes created with <tt>Sequel::Model()</tt>, to allow the reopening
78
- # of classes when dealing with code reloading.
79
- ANONYMOUS_MODEL_CLASSES = {}
80
-
81
- # Mutex protecting access to ANONYMOUS_MODEL_CLASSES
82
- ANONYMOUS_MODEL_CLASSES_MUTEX = Mutex.new
83
-
84
- # Class methods added to model that call the method of the same name on the dataset
85
- DATASET_METHODS = (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS +
86
- [:each_server]) - [:and, :or, :[], :columns, :columns!, :delete, :update, :add_graph_aliases, :first, :first!]
87
-
88
- # Boolean settings that can be modified at the global, class, or instance level.
89
- BOOLEAN_SETTINGS = [:typecast_empty_string_to_nil, :typecast_on_assignment, :strict_param_setting, \
90
- :raise_on_save_failure, :raise_on_typecast_failure, :require_modification, :use_after_commit_rollback, :use_transactions]
91
-
92
- # Hooks that are called before an action. Can return false to not do the action. When
93
- # overriding these, it is recommended to call +super+ as the last line of your method,
94
- # so later hooks are called before earlier hooks.
95
- BEFORE_HOOKS = [:before_create, :before_update, :before_save, :before_destroy, :before_validation]
96
-
97
- # Hooks that are called after an action. When overriding these, it is recommended to call
98
- # +super+ on the first line of your method, so later hooks are called after earlier hooks.
99
- AFTER_HOOKS = [:after_create, :after_update, :after_save, :after_destroy,
100
- :after_validation, :after_commit, :after_rollback, :after_destroy_commit, :after_destroy_rollback]
101
-
102
- # Hooks that are called around an action. If overridden, these methods must call super
103
- # exactly once if the behavior they wrap is desired. The can be used to rescue exceptions
104
- # raised by the code they wrap or ensure that some behavior is executed no matter what.
105
- AROUND_HOOKS = [:around_create, :around_update, :around_save, :around_destroy, :around_validation]
106
-
107
- # Empty instance methods to create that the user can override to get hook/callback behavior.
23
+ # Empty instance methods to create that the user can override.
108
24
  # Just like any other method defined by Sequel, if you override one of these, you should
109
- # call +super+ to get the default behavior (while empty by default, they can also be defined
25
+ # call +super+ to get the default behavior (while empty by default, they are often overridden
110
26
  # by plugins). See the {"Model Hooks" guide}[rdoc-ref:doc/model_hooks.rdoc] for
111
27
  # more detail on hooks.
112
- HOOKS = BEFORE_HOOKS + AFTER_HOOKS
113
-
114
- # Class instance variables that are inherited in subclasses. If the value is <tt>:dup</tt>, dup is called
115
- # on the superclass's instance variable when creating the instance variable in the subclass.
116
- # If the value is +nil+, the superclass's instance variable is used directly in the subclass.
117
- INHERITED_INSTANCE_VARIABLES = {:@allowed_columns=>:dup,
118
- :@dataset_method_modules=>:dup, :@primary_key=>nil, :@use_transactions=>nil,
119
- :@raise_on_save_failure=>nil, :@require_modification=>nil,
120
- :@restricted_columns=>:dup, :@restrict_primary_key=>nil,
121
- :@simple_pk=>nil, :@simple_table=>nil, :@strict_param_setting=>nil,
122
- :@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil,
123
- :@raise_on_typecast_failure=>nil, :@plugins=>:dup, :@setter_methods=>nil,
124
- :@use_after_commit_rollback=>nil, :@fast_pk_lookup_sql=>nil,
125
- :@fast_instance_delete_sql=>nil, :@finders=>:dup, :@finder_loaders=>:dup,
126
- :@db=>nil, :@default_set_fields_options=>:dup}
127
-
128
- # Regular expression that determines if a method name is normal in the sense that
129
- # it could be used literally in ruby code without using send. Used to
130
- # avoid problems when using eval with a string to define methods.
131
- NORMAL_METHOD_NAME_REGEXP = /\A[A-Za-z_][A-Za-z0-9_]*\z/
132
-
133
- # Regular expression that determines if the method is a valid setter name
134
- # (i.e. it ends with =).
135
- SETTER_METHOD_REGEXP = /=\z/
136
-
137
- @allowed_columns = nil
28
+ HOOKS = [
29
+ :after_create,
30
+ :after_destroy,
31
+ :after_save,
32
+ :after_update,
33
+ :after_validation,
34
+ :before_create,
35
+ :before_destroy,
36
+ :before_save,
37
+ :before_update,
38
+ :before_validation
39
+ ].freeze
40
+
41
+ @cache_anonymous_models = true
138
42
  @db = nil
139
43
  @db_schema = nil
140
44
  @dataset = nil
141
45
  @dataset_method_modules = []
142
- @default_eager_limit_strategy = true
143
46
  @default_set_fields_options = {}
144
- @finders = {}
145
- @finder_loaders = {}
146
47
  @overridable_methods_module = nil
147
48
  @fast_pk_lookup_sql = nil
148
49
  @fast_instance_delete_sql = nil
@@ -151,25 +52,34 @@ module Sequel
151
52
  @raise_on_save_failure = true
152
53
  @raise_on_typecast_failure = false
153
54
  @require_modification = nil
55
+ @require_valid_table = true
154
56
  @restrict_primary_key = true
155
- @restricted_columns = nil
156
57
  @setter_methods = nil
157
58
  @simple_pk = nil
158
59
  @simple_table = nil
159
60
  @strict_param_setting = true
160
61
  @typecast_empty_string_to_nil = true
161
62
  @typecast_on_assignment = true
162
- @use_after_commit_rollback = true
163
63
  @use_transactions = true
164
64
 
165
- Sequel.require %w"default_inflections inflections plugins dataset_module base exceptions errors", "model"
65
+ require_relative "model/default_inflections"
66
+ require_relative "model/inflections"
67
+ require_relative "model/plugins"
68
+ require_relative "model/dataset_module"
69
+ require_relative "model/base"
70
+ require_relative "model/exceptions"
71
+ require_relative "model/errors"
72
+ # :nocov:
166
73
  if !defined?(::SEQUEL_NO_ASSOCIATIONS) && !ENV.has_key?('SEQUEL_NO_ASSOCIATIONS')
167
- Sequel.require 'associations', 'model'
74
+ # :nocov:
75
+ require_relative 'model/associations'
168
76
  plugin Model::Associations
169
77
  end
170
78
 
79
+ def_Model(::Sequel)
80
+
171
81
  # The setter methods (methods ending with =) that are never allowed
172
- # to be called automatically via +set+/+update+/+new+/etc..
173
- RESTRICTED_SETTER_METHODS = instance_methods.map(&:to_s).grep(SETTER_METHOD_REGEXP)
82
+ # to be called automatically via +set+/+update+/+new+/etc.
83
+ RESTRICTED_SETTER_METHODS = instance_methods.map(&:to_s).select{|l| l.end_with?('=')}.freeze
174
84
  end
175
85
  end
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module Sequel
2
4
  class Model
3
5
  # Associations are used in order to specify relationships between model classes
@@ -5,45 +7,54 @@ module Sequel
5
7
  module Associations
6
8
  # Map of association type symbols to association reflection classes.
7
9
  ASSOCIATION_TYPES = {}
8
-
10
+
9
11
  # Set an empty association reflection hash in the model
10
12
  def self.apply(model)
11
- model.instance_eval do
13
+ model.instance_exec do
12
14
  @association_reflections = {}
13
15
  @autoreloading_associations = {}
14
16
  @cache_associations = true
17
+ @default_eager_limit_strategy = true
18
+ @default_association_options = {}
19
+ @default_association_type_options = {}
20
+ @dataset_module_class = DatasetModule
15
21
  end
16
22
  end
17
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
+
18
29
  # AssociationReflection is a Hash subclass that keeps information on Sequel::Model associations. It
19
30
  # provides methods to reduce internal code duplication. It should not
20
31
  # be instantiated by the user.
21
32
  class AssociationReflection < Hash
22
33
  include Sequel::Inflections
23
-
34
+
24
35
  # Name symbol for the _add internal association method
25
36
  def _add_method
26
- :"_add_#{singularize(self[:name])}"
37
+ self[:_add_method]
27
38
  end
28
39
 
29
40
  # Name symbol for the _remove_all internal association method
30
41
  def _remove_all_method
31
- :"_remove_all_#{self[:name]}"
42
+ self[:_remove_all_method]
32
43
  end
33
44
 
34
45
  # Name symbol for the _remove internal association method
35
46
  def _remove_method
36
- :"_remove_#{singularize(self[:name])}"
47
+ self[:_remove_method]
37
48
  end
38
49
 
39
50
  # Name symbol for the _setter association method
40
51
  def _setter_method
41
- :"_#{self[:name]}="
52
+ self[:_setter_method]
42
53
  end
43
54
 
44
55
  # Name symbol for the add association method
45
56
  def add_method
46
- :"add_#{singularize(self[:name])}"
57
+ self[:add_method]
47
58
  end
48
59
 
49
60
  # Name symbol for association method, the same as the name of the association.
@@ -53,7 +64,13 @@ module Sequel
53
64
 
54
65
  # The class associated to the current model class via this association
55
66
  def associated_class
56
- 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
57
74
  end
58
75
 
59
76
  # The dataset associated via this association, with the non-instance specific
@@ -65,16 +82,17 @@ module Sequel
65
82
 
66
83
  # Apply all non-instance specific changes to the given dataset and return it.
67
84
  def apply_dataset_changes(ds)
68
- ds.extend(AssociationDatasetMethods)
69
- ds.association_reflection = self
70
- 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
71
89
  ds = ds.select(*select) if select
72
90
  if c = self[:conditions]
73
91
  ds = (c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.where(*c) : ds.where(c)
74
92
  end
75
93
  ds = ds.order(*self[:order]) if self[:order]
76
94
  ds = ds.limit(*self[:limit]) if self[:limit]
77
- ds = ds.limit(1) if limit_to_single_row?
95
+ ds = ds.limit(1).skip_limit_check if limit_to_single_row?
78
96
  ds = ds.eager(self[:eager]) if self[:eager]
79
97
  ds = ds.distinct if self[:distinct]
80
98
  ds
@@ -105,12 +123,12 @@ module Sequel
105
123
 
106
124
  # Apply an eager limit strategy to the dataset, or return the dataset
107
125
  # unmodified if it doesn't need an eager limit strategy.
108
- def apply_eager_limit_strategy(ds, strategy=eager_limit_strategy)
126
+ def apply_eager_limit_strategy(ds, strategy=eager_limit_strategy, limit_and_offset=limit_and_offset())
109
127
  case strategy
110
128
  when :distinct_on
111
129
  apply_distinct_on_eager_limit_strategy(ds)
112
130
  when :window_function
113
- apply_window_function_eager_limit_strategy(ds)
131
+ apply_window_function_eager_limit_strategy(ds, limit_and_offset)
114
132
  else
115
133
  ds
116
134
  end
@@ -123,10 +141,11 @@ module Sequel
123
141
  end
124
142
 
125
143
  # Use a window function to limit the results of the eager loading dataset.
126
- def apply_window_function_eager_limit_strategy(ds)
144
+ def apply_window_function_eager_limit_strategy(ds, limit_and_offset=limit_and_offset())
127
145
  rn = ds.row_number_column
128
146
  limit, offset = limit_and_offset
129
- 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
130
149
  ds = if !returns_array?
131
150
  ds.where(rn => offset ? offset+1 : 1)
132
151
  elsif offset
@@ -143,13 +162,13 @@ module Sequel
143
162
 
144
163
  # If the ruby eager limit strategy is being used, slice the array using the slice
145
164
  # range to return the object(s) at the correct offset/limit.
146
- def apply_ruby_eager_limit_strategy(rows)
165
+ def apply_ruby_eager_limit_strategy(rows, limit_and_offset = limit_and_offset())
147
166
  name = self[:name]
167
+ return unless range = slice_range(limit_and_offset)
148
168
  if returns_array?
149
- range = slice_range
150
169
  rows.each{|o| o.associations[name] = o.associations[name][range] || []}
151
- elsif slice_range
152
- offset = slice_range.begin
170
+ else
171
+ offset = range.begin
153
172
  rows.each{|o| o.associations[name] = o.associations[name][offset]}
154
173
  end
155
174
  end
@@ -175,7 +194,7 @@ module Sequel
175
194
 
176
195
  # Name symbol for the dataset association method
177
196
  def dataset_method
178
- :"#{self[:name]}_dataset"
197
+ self[:dataset_method]
179
198
  end
180
199
 
181
200
  # Whether the dataset needs a primary key to function, true by default.
@@ -194,7 +213,13 @@ module Sequel
194
213
  # Return an dataset that will load the appropriate associated objects for
195
214
  # the given object using this association.
196
215
  def association_dataset_for(object)
197
- 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)
198
223
  end
199
224
 
200
225
  ASSOCIATION_DATASET_PROC = proc{|r| r.association_dataset_for(self)}
@@ -245,12 +270,31 @@ module Sequel
245
270
  end
246
271
  strategy = eager_limit_strategy
247
272
  cascade = eo[:associations]
273
+ eager_limit = nil
248
274
 
249
275
  if eo[:eager_block] || eo[:loader] == false
276
+ ds = eager_loading_dataset(eo)
277
+
278
+ strategy = ds.opts[:eager_limit_strategy] || strategy
279
+
280
+ eager_limit =
281
+ if el = ds.opts[:eager_limit]
282
+ raise Error, "The :eager_limit dataset option is not supported for associations returning a single record" unless returns_array?
283
+ strategy ||= true_eager_graph_limit_strategy
284
+ if el.is_a?(Array)
285
+ el
286
+ else
287
+ [el, nil]
288
+ end
289
+ else
290
+ limit_and_offset
291
+ end
292
+
250
293
  strategy = true_eager_graph_limit_strategy if strategy == :union
251
294
  # Correlated subqueries are not supported for regular eager loading
252
295
  strategy = :ruby if strategy == :correlated_subquery
253
- objects = apply_eager_limit_strategy(eager_loading_dataset(eo), strategy).all
296
+ strategy = nil if strategy == :ruby && assign_singular?
297
+ objects = apply_eager_limit_strategy(ds, strategy, eager_limit).all
254
298
  elsif strategy == :union
255
299
  objects = []
256
300
  ds = associated_dataset
@@ -269,7 +313,7 @@ module Sequel
269
313
 
270
314
  objects.each(&block)
271
315
  if strategy == :ruby
272
- apply_ruby_eager_limit_strategy(rows)
316
+ apply_ruby_eager_limit_strategy(rows, eager_limit || limit_and_offset)
273
317
  end
274
318
  end
275
319
 
@@ -284,11 +328,6 @@ module Sequel
284
328
  false
285
329
  end
286
330
 
287
- # Alias of predicate_key, only for backwards compatibility.
288
- def eager_loading_predicate_key
289
- predicate_key
290
- end
291
-
292
331
  # Whether to eagerly graph a lazy dataset, true by default. If this
293
332
  # is false, the association won't respect the :eager_graph option
294
333
  # when loading the association for a single record.
@@ -311,6 +350,44 @@ module Sequel
311
350
  {filter_by_associations_conditions_key=>ds}
312
351
  end
313
352
 
353
+ # Finalize the association by first attempting to populate the thread-safe cache,
354
+ # and then transfering the thread-safe cache value to the association itself,
355
+ # so that a mutex is not needed to get the value.
356
+ def finalize
357
+ return unless cache = self[:cache]
358
+
359
+ finalizer = proc do |meth, key|
360
+ next if has_key?(key)
361
+
362
+ # Allow calling private methods to make sure caching is done appropriately
363
+ send(meth)
364
+ self[key] = cache.delete(key) if cache.has_key?(key)
365
+ end
366
+
367
+ finalize_settings.each(&finalizer)
368
+
369
+ unless self[:instance_specific]
370
+ finalizer.call(:associated_eager_dataset, :associated_eager_dataset)
371
+ finalizer.call(:filter_by_associations_conditions_dataset, :filter_by_associations_conditions_dataset)
372
+ end
373
+
374
+ nil
375
+ end
376
+
377
+ # Map of methods to cache keys used for finalizing associations.
378
+ FINALIZE_SETTINGS = {
379
+ :associated_class=>:class,
380
+ :associated_dataset=>:_dataset,
381
+ :eager_limit_strategy=>:_eager_limit_strategy,
382
+ :placeholder_loader=>:placeholder_loader,
383
+ :predicate_key=>:predicate_key,
384
+ :predicate_keys=>:predicate_keys,
385
+ :reciprocal=>:reciprocal,
386
+ }.freeze
387
+ def finalize_settings
388
+ FINALIZE_SETTINGS
389
+ end
390
+
314
391
  # Whether to handle silent modification failure when adding/removing
315
392
  # associated records, false by default.
316
393
  def handle_silent_modification_failure?
@@ -327,6 +404,18 @@ module Sequel
327
404
  end
328
405
  end
329
406
 
407
+ # Show which type of reflection this is, and a guess at what code was used to create the
408
+ # association.
409
+ def inspect
410
+ o = self[:orig_opts].dup
411
+ o.delete(:class)
412
+ o.delete(:class_name)
413
+ o.delete(:block) unless o[:block]
414
+ o[:class] = self[:orig_class] if self[:orig_class]
415
+
416
+ "#<#{self.class} #{self[:model]}.#{self[:type]} #{self[:name].inspect}#{", #{o.inspect[1...-1]}" unless o.empty?}>"
417
+ end
418
+
330
419
  # The limit and offset for this association (returned as a two element array).
331
420
  def limit_and_offset
332
421
  if (v = self[:limit]).is_a?(Array)
@@ -348,7 +437,11 @@ module Sequel
348
437
  if use_placeholder_loader?
349
438
  cached_fetch(:placeholder_loader) do
350
439
  Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds|
351
- ds.where(*predicate_keys.map{|k| SQL::BooleanExpression.new(:'=', k, pl.arg)})
440
+ ds = ds.where(Sequel.&(*predicate_keys.map{|k| SQL::BooleanExpression.new(:'=', k, pl.arg)}))
441
+ if self[:block]
442
+ ds = self[:block].call(ds)
443
+ end
444
+ ds
352
445
  end
353
446
  end
354
447
  end
@@ -364,16 +457,14 @@ module Sequel
364
457
  predicate_key_methods.map{|k| object.get_column_value(k)}
365
458
  end
366
459
 
367
- # Qualify +col+ with the given table name. If +col+ is an array of columns,
368
- # return an array of qualified columns. Only qualifies Symbols and SQL::Identifier
369
- # values, other values are not modified.
460
+ # Qualify +col+ with the given table name.
370
461
  def qualify(table, col)
371
462
  transform(col) do |k|
372
463
  case k
373
464
  when Symbol, SQL::Identifier
374
465
  SQL::QualifiedIdentifier.new(table, k)
375
466
  else
376
- Sequel::Qualifier.new(self[:model].dataset, table).transform(k)
467
+ Sequel::Qualifier.new(table).transform(k)
377
468
  end
378
469
  end
379
470
  end
@@ -419,7 +510,7 @@ module Sequel
419
510
 
420
511
  # Name symbol for the remove_all_ association method
421
512
  def remove_all_method
422
- :"remove_all_#{self[:name]}"
513
+ self[:remove_all_method]
423
514
  end
424
515
 
425
516
  # Whether associated objects need to be removed from the association before
@@ -430,7 +521,7 @@ module Sequel
430
521
 
431
522
  # Name symbol for the remove_ association method
432
523
  def remove_method
433
- :"remove_#{singularize(self[:name])}"
524
+ self[:remove_method]
434
525
  end
435
526
 
436
527
  # Whether to check that an object to be disassociated is already associated to this object, false by default.
@@ -457,11 +548,11 @@ module Sequel
457
548
 
458
549
  # Name symbol for the setter association method
459
550
  def setter_method
460
- :"#{self[:name]}="
551
+ self[:setter_method]
461
552
  end
462
553
 
463
554
  # The range used for slicing when using the :ruby eager limit strategy.
464
- def slice_range
555
+ def slice_range(limit_and_offset = limit_and_offset())
465
556
  limit, offset = limit_and_offset
466
557
  if limit || offset
467
558
  (offset||0)..(limit ? (offset||0)+limit-1 : -1)
@@ -470,49 +561,32 @@ module Sequel
470
561
 
471
562
  private
472
563
 
473
- if defined?(RUBY_ENGINE) && RUBY_ENGINE != 'ruby'
474
- # :nocov:
475
- # On non-GVL rubies, assume the need to synchronize access. Store the key
476
- # in a special sub-hash that always uses this method to synchronize access.
477
- def cached_fetch(key)
478
- fetch(key) do
479
- return yield unless h = self[:cache]
480
- Sequel.synchronize{return h[key] if h.has_key?(key)}
481
- value = yield
482
- Sequel.synchronize{h[key] = value}
483
- end
484
- end
485
-
486
- # Cache the value at the given key, synchronizing access.
487
- def cached_set(key, value)
488
- return unless h = self[:cache]
564
+ # If the key exists in the reflection hash, return it.
565
+ # If the key doesn't exist and association reflections are uncached, then yield to get the value.
566
+ # If the key doesn't exist and association reflection are cached, check the cache and return
567
+ # the value if present, or yield to get the value, cache the value, and return it.
568
+ def cached_fetch(key)
569
+ fetch(key) do
570
+ return yield unless h = self[:cache]
571
+ Sequel.synchronize{return h[key] if h.has_key?(key)}
572
+ value = yield
489
573
  Sequel.synchronize{h[key] = value}
490
574
  end
491
- # :nocov:
492
- else
493
- # On MRI, use a plain fetch, since the GVL will synchronize access.
494
- def cached_fetch(key)
495
- fetch(key) do
496
- return yield unless h = self[:cache]
497
- h.fetch(key){h[key] = yield}
498
- end
499
- end
575
+ end
500
576
 
501
- # On MRI, just set the value at the key in the cache, since the GVL
502
- # will synchronize access.
503
- def cached_set(key, value)
504
- return unless h = self[:cache]
505
- h[key] = value
506
- end
577
+ # Cache the value at the given key if caching.
578
+ def cached_set(key, value)
579
+ return unless h = self[:cache]
580
+ Sequel.synchronize{h[key] = value}
507
581
  end
508
582
 
509
583
  # The base dataset used for the association, before any order/conditions
510
584
  # options have been applied.
511
585
  def _associated_dataset
512
- associated_class.dataset.clone
586
+ associated_class.dataset
513
587
  end
514
588
 
515
- # Whether for the reciprocal type for the given association can not be
589
+ # Whether for the reciprocal type for the given association cannot be
516
590
  # known in advantage, false by default.
517
591
  def ambiguous_reciprocal_type?
518
592
  false
@@ -568,10 +642,15 @@ module Sequel
568
642
  ds = ds.eager(associations)
569
643
  end
570
644
  if block = eo[:eager_block]
645
+ orig_ds = ds
571
646
  ds = block.call(ds)
572
647
  end
573
648
  if eager_loading_use_associated_key?
574
- ds = ds.select_append(*associated_key_array)
649
+ ds = if ds.opts[:eager_graph] && !orig_ds.opts[:eager_graph]
650
+ block.call(orig_ds.select_append(*associated_key_array))
651
+ else
652
+ ds.select_append(*associated_key_array)
653
+ end
575
654
  end
576
655
  if self[:eager_graph]
577
656
  raise(Error, "cannot eagerly load a #{self[:type]} association that uses :eager_graph") if eager_loading_use_associated_key?
@@ -629,14 +708,12 @@ module Sequel
629
708
  v = fetch(:filter_limit_strategy, self[:eager_limit_strategy])
630
709
  if v || self[:limit] || !returns_array?
631
710
  case v ||= self[:model].default_eager_limit_strategy
632
- when :union, :ruby
711
+ when true, :union, :ruby
633
712
  # Can't use a union or ruby-based strategy for filtering by associations, switch to default eager graph limit
634
713
  # strategy.
635
714
  true_eager_graph_limit_strategy
636
715
  when Symbol
637
716
  v
638
- when true
639
- true_eager_graph_limit_strategy
640
717
  end
641
718
  end
642
719
  end
@@ -684,8 +761,8 @@ module Sequel
684
761
 
685
762
  # If +s+ is an array, map +s+ over the block. Otherwise, just call the
686
763
  # block with +s+.
687
- def transform(s)
688
- s.is_a?(Array) ? s.map(&Proc.new) : (yield s)
764
+ def transform(s, &block)
765
+ s.is_a?(Array) ? s.map(&block) : (yield s)
689
766
  end
690
767
 
691
768
  # What eager limit strategy should be used when true is given as the value,
@@ -728,7 +805,7 @@ module Sequel
728
805
 
729
806
  # Whether the placeholder loader can be used to load the association.
730
807
  def use_placeholder_loader?
731
- !self[:instance_specific] && !self[:eager_graph]
808
+ self[:use_placeholder_loader]
732
809
  end
733
810
  end
734
811
 
@@ -773,6 +850,18 @@ module Sequel
773
850
  nil
774
851
  end
775
852
 
853
+ FINALIZE_SETTINGS = superclass::FINALIZE_SETTINGS.merge(
854
+ :primary_key=>:primary_key,
855
+ :primary_keys=>:primary_keys,
856
+ :primary_key_method=>:primary_key_method,
857
+ :primary_key_methods=>:primary_key_methods,
858
+ :qualified_primary_key=>:qualified_primary_key,
859
+ :reciprocal_type=>:reciprocal_type
860
+ ).freeze
861
+ def finalize_settings
862
+ FINALIZE_SETTINGS
863
+ end
864
+
776
865
  # The expression to use on the left hand side of the IN lookup when eager loading
777
866
  def predicate_key
778
867
  cached_fetch(:predicate_key){qualified_primary_key}
@@ -917,6 +1006,13 @@ module Sequel
917
1006
  :"#{underscore(demodulize(self[:model].name))}_id"
918
1007
  end
919
1008
 
1009
+ FINALIZE_SETTINGS = superclass::FINALIZE_SETTINGS.merge(
1010
+ :qualified_primary_key=>:qualified_primary_key
1011
+ ).freeze
1012
+ def finalize_settings
1013
+ FINALIZE_SETTINGS
1014
+ end
1015
+
920
1016
  # Handle silent failure of add/remove methods if raise_on_save_failure is false.
921
1017
  def handle_silent_modification_failure?
922
1018
  self[:raise_on_save_failure] == false
@@ -1022,7 +1118,7 @@ module Sequel
1022
1118
  end
1023
1119
 
1024
1120
  # Support automatic use of correlated subqueries if :ruby option is best available option,
1025
- # MySQL is not being used, and either the associated class has a non-composite primary key
1121
+ # the database supports them, and either the associated class has a non-composite primary key
1026
1122
  # or the database supports multiple columns in IN.
1027
1123
  def true_eager_graph_limit_strategy
1028
1124
  r = super
@@ -1157,7 +1253,9 @@ module Sequel
1157
1253
  else
1158
1254
  assoc_record.values.delete(left_key_alias)
1159
1255
  end
1160
- next unless objects = h[hash_key]
1256
+
1257
+ objects = h[hash_key]
1258
+
1161
1259
  if assign_singular
1162
1260
  objects.each do |object|
1163
1261
  object.associations[name] ||= assoc_record
@@ -1187,6 +1285,22 @@ module Sequel
1187
1285
  :"#{singularize(self[:name])}_id"
1188
1286
  end
1189
1287
 
1288
+ FINALIZE_SETTINGS = superclass::FINALIZE_SETTINGS.merge(
1289
+ :associated_key_array=>:associated_key_array,
1290
+ :qualified_right_key=>:qualified_right_key,
1291
+ :join_table_source=>:join_table_source,
1292
+ :join_table_alias=>:join_table_alias,
1293
+ :qualified_right_primary_key=>:qualified_right_primary_key,
1294
+ :right_primary_key=>:right_primary_key,
1295
+ :right_primary_keys=>:right_primary_keys,
1296
+ :right_primary_key_method=>:right_primary_key_method,
1297
+ :right_primary_key_methods=>:right_primary_key_methods,
1298
+ :select=>:select
1299
+ ).freeze
1300
+ def finalize_settings
1301
+ FINALIZE_SETTINGS
1302
+ end
1303
+
1190
1304
  # The hash key to use for the eager loading predicate (left side of IN (1, 2, 3)).
1191
1305
  # The left key qualified by the join table.
1192
1306
  def predicate_key
@@ -1352,10 +1466,20 @@ module Sequel
1352
1466
  # This module contains methods added to all association datasets
1353
1467
  module AssociationDatasetMethods
1354
1468
  # The model object that created the association dataset
1355
- attr_accessor :model_object
1469
+ def model_object
1470
+ @opts[:model_object]
1471
+ end
1356
1472
 
1357
1473
  # The association reflection related to the association dataset
1358
- attr_accessor :association_reflection
1474
+ def association_reflection
1475
+ @opts[:association_reflection]
1476
+ end
1477
+
1478
+ private
1479
+
1480
+ def non_sql_option?(key)
1481
+ super || key == :model_object || key == :association_reflection
1482
+ end
1359
1483
  end
1360
1484
 
1361
1485
  # Each kind of association adds a number of instance methods to the model class which
@@ -1396,7 +1520,7 @@ module Sequel
1396
1520
  # Project.associations
1397
1521
  # => [:portfolio, :milestones]
1398
1522
  # Project.association_reflection(:portfolio)
1399
- # => {:type => :many_to_one, :name => :portfolio, ...}
1523
+ # => #<Sequel::Model::Associations::ManyToOneAssociationReflection Project.many_to_one :portfolio>
1400
1524
  #
1401
1525
  # Associations should not have the same names as any of the columns in the
1402
1526
  # model's current table they reference. If you are dealing with an existing schema that
@@ -1417,11 +1541,20 @@ module Sequel
1417
1541
  attr_reader :autoreloading_associations
1418
1542
 
1419
1543
  # Whether association metadata should be cached in the association reflection. If not cached, it will be computed
1420
- # on demand. In general you only want to set this to default when using code reloading. When using code reloading,
1421
- # setting this will make sure that if an associated class is removed or modified, this class will not hang on to
1544
+ # on demand. In general you only want to set this to false when using code reloading. When using code reloading,
1545
+ # setting this will make sure that if an associated class is removed or modified, this class will not have a reference to
1422
1546
  # the previous class.
1423
1547
  attr_accessor :cache_associations
1424
1548
 
1549
+ # The default options to use for all associations. This hash is merged into the association reflection hash for
1550
+ # all association reflections.
1551
+ attr_accessor :default_association_options
1552
+
1553
+ # The default options to use for all associations of a given type. This is a hash keyed by association type
1554
+ # symbol. If there is a value for the association type symbol key, the resulting hash will be merged into the
1555
+ # association reflection hash for all association reflections of that type.
1556
+ attr_accessor :default_association_type_options
1557
+
1425
1558
  # The default :eager_limit_strategy option to use for limited or offset associations (default: true, causing Sequel
1426
1559
  # to use what it considers the most appropriate strategy).
1427
1560
  attr_accessor :default_eager_limit_strategy
@@ -1482,16 +1615,23 @@ module Sequel
1482
1615
  # :class :: The associated class or its name as a string or symbol. If not
1483
1616
  # given, uses the association's name, which is camelized (and
1484
1617
  # singularized unless the type is :many_to_one, :one_to_one, or one_through_one). If this is specified
1485
- # as a string or symbol, you must specify the full class name (e.g. "SomeModule::MyModel").
1618
+ # as a string or symbol, you must specify the full class name (e.g. "::SomeModule::MyModel").
1619
+ # :class_namespace :: If :class is given as a string or symbol, sets the default namespace in which to look for
1620
+ # the class. <tt>class: 'Foo', class_namespace: 'Bar'</tt> looks for <tt>::Bar::Foo</tt>.)
1486
1621
  # :clearer :: Proc used to define the private _remove_all_* method for doing the database work
1487
1622
  # to remove all objects associated to the current object (*_to_many assocations).
1488
1623
  # :clone :: Merge the current options and block into the options and block used in defining
1489
1624
  # the given association. Can be used to DRY up a bunch of similar associations that
1490
1625
  # all share the same options such as :class and :key, while changing the order and block used.
1491
1626
  # :conditions :: The conditions to use to filter the association, can be any argument passed to where.
1492
- # :dataset :: A proc that is instance_execed to get the base dataset to use (before the other
1627
+ # This option is not respected when using eager_graph or association_join, unless it
1628
+ # is hash or array of two element arrays. Consider also specifying the :graph_block
1629
+ # option if the value for this option is not a hash or array of two element arrays
1630
+ # and you plan to use this association in eager_graph or association_join.
1631
+ # :dataset :: A proc that is used to define the method to get the base dataset to use (before the other
1493
1632
  # options are applied). If the proc accepts an argument, it is passed the related
1494
- # association reflection.
1633
+ # association reflection. It is a best practice to always have the dataset accept an argument
1634
+ # and use the argument to return the appropriate dataset.
1495
1635
  # :distinct :: Use the DISTINCT clause when selecting associating object, both when
1496
1636
  # lazy loading and eager loading via .eager (but not when using .eager_graph).
1497
1637
  # :eager :: The associations to eagerly load via +eager+ when loading the associated object(s).
@@ -1551,8 +1691,7 @@ module Sequel
1551
1691
  # :order_eager_graph :: Whether to add the association's order to the graphed dataset's order when graphing
1552
1692
  # via +eager_graph+. Defaults to true, so set to false to disable.
1553
1693
  # :read_only :: Do not add a setter method (for many_to_one or one_to_one associations),
1554
- # or add_/remove_/remove_all_ methods (for one_to_many and many_to_many associations). Always
1555
- # true for one_through_one associations.
1694
+ # or add_/remove_/remove_all_ methods (for one_to_many and many_to_many associations).
1556
1695
  # :reciprocal :: the symbol name of the reciprocal association,
1557
1696
  # if it exists. By default, Sequel will try to determine it by looking at the
1558
1697
  # associated model's assocations for a association that matches
@@ -1583,7 +1722,7 @@ module Sequel
1583
1722
  # array of symbols for a composite key association.
1584
1723
  # :primary_key_method :: the method symbol or array of method symbols to call on the associated
1585
1724
  # object to get the foreign key values. Defaults to :primary_key option.
1586
- # :qualify :: Whether to use qualifier primary keys when loading the association. The default
1725
+ # :qualify :: Whether to use qualified primary keys when loading the association. The default
1587
1726
  # is true, so you must set to false to not qualify. Qualification rarely causes
1588
1727
  # problems, but it's necessary to disable in some cases, such as when you are doing
1589
1728
  # a JOIN USING operation on the column on Oracle.
@@ -1644,7 +1783,7 @@ module Sequel
1644
1783
  # Defaults to :right_primary_key option.
1645
1784
  # :uniq :: Adds a after_load callback that makes the array of objects unique.
1646
1785
  def associate(type, name, opts = OPTS, &block)
1647
- raise(Error, 'invalid association type') unless assoc_class = ASSOCIATION_TYPES[type]
1786
+ raise(Error, 'invalid association type') unless assoc_class = Sequel.synchronize{ASSOCIATION_TYPES[type]}
1648
1787
  raise(Error, 'Model.associate name argument must be a symbol') unless name.is_a?(Symbol)
1649
1788
 
1650
1789
  # dup early so we don't modify opts
@@ -1655,13 +1794,20 @@ module Sequel
1655
1794
  orig_opts = cloned_assoc[:orig_opts].merge(orig_opts)
1656
1795
  end
1657
1796
 
1658
- opts = orig_opts.merge(:type => type, :name => name, :cache=>({} if cache_associations), :model => self)
1797
+ opts = Hash[default_association_options]
1798
+ if type_options = default_association_type_options[type]
1799
+ opts.merge!(type_options)
1800
+ end
1801
+ opts.merge!(orig_opts)
1802
+ opts.merge!(:type => type, :name => name, :cache=>({} if cache_associations), :model => self)
1803
+
1659
1804
  opts[:block] = block if block
1660
- if !opts.has_key?(:instance_specific) && (block || orig_opts[:block] || orig_opts[:dataset])
1805
+ opts[:instance_specific] = true if orig_opts[:dataset]
1806
+ if !opts.has_key?(:instance_specific) && (block || orig_opts[:block])
1661
1807
  # It's possible the association is instance specific, in that it depends on
1662
1808
  # values other than the foreign key value. This needs to be checked for
1663
1809
  # in certain places to disable optimizations.
1664
- opts[:instance_specific] = true
1810
+ opts[:instance_specific] = _association_instance_specific_default(name)
1665
1811
  end
1666
1812
  opts = assoc_class.new.merge!(opts)
1667
1813
 
@@ -1669,10 +1815,8 @@ module Sequel
1669
1815
  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]})")
1670
1816
  end
1671
1817
 
1818
+ opts[:use_placeholder_loader] = !opts[:instance_specific] && !opts[:eager_graph]
1672
1819
  opts[:eager_block] = opts[:block] unless opts.include?(:eager_block)
1673
- if !opts.has_key?(:predicate_key) && opts.has_key?(:eager_loading_predicate_key)
1674
- opts[:predicate_key] = opts[:eager_loading_predicate_key]
1675
- end
1676
1820
  opts[:graph_join_type] ||= :left_outer
1677
1821
  opts[:order_eager_graph] = true unless opts.include?(:order_eager_graph)
1678
1822
  conds = opts[:conditions]
@@ -1680,9 +1824,15 @@ module Sequel
1680
1824
  opts[:graph_conditions] = conds if !opts.include?(:graph_conditions) and Sequel.condition_specifier?(conds)
1681
1825
  opts[:graph_conditions] = opts.fetch(:graph_conditions, []).to_a
1682
1826
  opts[:graph_select] = Array(opts[:graph_select]) if opts[:graph_select]
1683
- [:before_add, :before_remove, :after_add, :after_remove, :after_load, :before_set, :after_set, :extend].each do |cb_type|
1684
- opts[cb_type] = Array(opts[cb_type])
1827
+ [:before_add, :before_remove, :after_add, :after_remove, :after_load, :before_set, :after_set].each do |cb_type|
1828
+ opts[cb_type] = Array(opts[cb_type]) if opts[cb_type]
1685
1829
  end
1830
+
1831
+ if opts[:extend]
1832
+ opts[:extend] = Array(opts[:extend])
1833
+ opts[:reverse_extend] = opts[:extend].reverse
1834
+ end
1835
+
1686
1836
  late_binding_class_option(opts, opts.returns_array? ? singularize(name) : name)
1687
1837
 
1688
1838
  # Remove :class entry if it exists and is nil, to work with cached_fetch
@@ -1692,6 +1842,7 @@ module Sequel
1692
1842
  def_association_instance_methods(opts)
1693
1843
 
1694
1844
  orig_opts.delete(:clone)
1845
+ opts[:orig_class] = orig_opts[:class] || orig_opts[:class_name]
1695
1846
  orig_opts.merge!(:class_name=>opts[:class_name], :class=>opts[:class], :block=>opts[:block])
1696
1847
  opts[:orig_opts] = orig_opts
1697
1848
  # don't add to association_reflections until we are sure there are no errors
@@ -1713,6 +1864,25 @@ module Sequel
1713
1864
  opts.eager_load_results(eo, &block)
1714
1865
  end
1715
1866
 
1867
+ # Freeze association related metadata when freezing model class.
1868
+ def freeze
1869
+ @association_reflections.freeze.each_value(&:freeze)
1870
+ @autoreloading_associations.freeze.each_value(&:freeze)
1871
+ @default_association_options.freeze
1872
+ @default_association_type_options.freeze
1873
+ @default_association_type_options.each_value(&:freeze)
1874
+
1875
+ super
1876
+ end
1877
+
1878
+ # Finalize all associations such that values that are looked up
1879
+ # dynamically in associated classes are set statically.
1880
+ # As this modifies the associations, it must be done before
1881
+ # calling freeze.
1882
+ def finalize_associations
1883
+ @association_reflections.each_value(&:finalize)
1884
+ end
1885
+
1716
1886
  # Shortcut for adding a many_to_many association, see #associate
1717
1887
  def many_to_many(name, opts=OPTS, &block)
1718
1888
  associate(:many_to_many, name, opts, &block)
@@ -1723,7 +1893,7 @@ module Sequel
1723
1893
  associate(:many_to_one, name, opts, &block)
1724
1894
  end
1725
1895
 
1726
- # Shortcut for adding a one_through_one association, see #associate.
1896
+ # Shortcut for adding a one_through_one association, see #associate
1727
1897
  def one_through_one(name, opts=OPTS, &block)
1728
1898
  associate(:one_through_one, name, opts, &block)
1729
1899
  end
@@ -1733,15 +1903,21 @@ module Sequel
1733
1903
  associate(:one_to_many, name, opts, &block)
1734
1904
  end
1735
1905
 
1736
- # Shortcut for adding a one_to_one association, see #associate.
1906
+ # Shortcut for adding a one_to_one association, see #associate
1737
1907
  def one_to_one(name, opts=OPTS, &block)
1738
1908
  associate(:one_to_one, name, opts, &block)
1739
1909
  end
1740
1910
 
1741
- Plugins.inherited_instance_variables(self, :@association_reflections=>:dup, :@autoreloading_associations=>:hash_dup, :@cache_associations=>nil, :@default_eager_limit_strategy=>nil)
1911
+ 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)
1742
1912
  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])
1743
1913
 
1744
1914
  private
1915
+
1916
+ # The default value for the instance_specific option, if the association
1917
+ # could be instance specific and the :instance_specific option is not specified.
1918
+ def _association_instance_specific_default(_)
1919
+ true
1920
+ end
1745
1921
 
1746
1922
  # The module to use for the association's methods. Defaults to
1747
1923
  # the overridable_methods_module.
@@ -1753,7 +1929,7 @@ module Sequel
1753
1929
  # can be easily overridden in the class itself while allowing for
1754
1930
  # super to be called.
1755
1931
  def association_module_def(name, opts=OPTS, &block)
1756
- association_module(opts).module_eval{define_method(name, &block)}
1932
+ association_module(opts).send(:define_method, name, &block)
1757
1933
  end
1758
1934
 
1759
1935
  # Add a private method to the module included in the class.
@@ -1764,42 +1940,64 @@ module Sequel
1764
1940
 
1765
1941
  # Adds the association method to the association methods module.
1766
1942
  def def_association_method(opts)
1767
- association_module_def(opts.association_method, opts){|*dynamic_opts, &block| load_associated_objects(opts, dynamic_opts[0], &block)}
1943
+ association_module_def(opts.association_method, opts) do |dynamic_opts=OPTS, &block|
1944
+ load_associated_objects(opts, dynamic_opts, &block)
1945
+ end
1768
1946
  end
1769
1947
 
1770
1948
  # Define all of the association instance methods for this association.
1771
1949
  def def_association_instance_methods(opts)
1950
+ # Always set the method names in the association reflection, even if they
1951
+ # are not used, for backwards compatibility.
1952
+ opts[:dataset_method] = :"#{opts[:name]}_dataset"
1953
+ if opts.returns_array?
1954
+ sname = singularize(opts[:name])
1955
+ opts[:_add_method] = :"_add_#{sname}"
1956
+ opts[:add_method] = :"add_#{sname}"
1957
+ opts[:_remove_method] = :"_remove_#{sname}"
1958
+ opts[:remove_method] = :"remove_#{sname}"
1959
+ opts[:_remove_all_method] = :"_remove_all_#{opts[:name]}"
1960
+ opts[:remove_all_method] = :"remove_all_#{opts[:name]}"
1961
+ else
1962
+ opts[:_setter_method] = :"_#{opts[:name]}="
1963
+ opts[:setter_method] = :"#{opts[:name]}="
1964
+ end
1965
+
1772
1966
  association_module_def(opts.dataset_method, opts){_dataset(opts)}
1967
+ if opts[:block]
1968
+ opts[:block_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_block", 1, &opts[:block])
1969
+ end
1970
+ opts[:dataset_opt_arity] = opts[:dataset].arity == 0 ? 0 : 1
1971
+ opts[:dataset_opt_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_dataset_opt", opts[:dataset_opt_arity], &opts[:dataset])
1773
1972
  def_association_method(opts)
1774
1973
 
1775
1974
  return if opts[:read_only]
1776
1975
 
1777
1976
  if opts[:setter] && opts[:_setter]
1778
1977
  # This is backwards due to backwards compatibility
1779
- association_module_private_def(opts._setter_method, opts, &opts[:setter])
1780
- association_module_def(opts.setter_method, opts, &opts[:_setter])
1978
+ association_module_private_def(opts[:_setter_method], opts, &opts[:setter])
1979
+ association_module_def(opts[:setter_method], opts, &opts[:_setter])
1781
1980
  end
1782
1981
 
1783
1982
  if adder = opts[:adder]
1784
- association_module_private_def(opts._add_method, opts, &adder)
1785
- association_module_def(opts.add_method, opts){|o,*args| add_associated_object(opts, o, *args)}
1983
+ association_module_private_def(opts[:_add_method], opts, &adder)
1984
+ association_module_def(opts[:add_method], opts){|o,*args| add_associated_object(opts, o, *args)}
1786
1985
  end
1787
1986
 
1788
1987
  if remover = opts[:remover]
1789
- association_module_private_def(opts._remove_method, opts, &remover)
1790
- association_module_def(opts.remove_method, opts){|o,*args| remove_associated_object(opts, o, *args)}
1988
+ association_module_private_def(opts[:_remove_method], opts, &remover)
1989
+ association_module_def(opts[:remove_method], opts){|o,*args| remove_associated_object(opts, o, *args)}
1791
1990
  end
1792
1991
 
1793
1992
  if clearer = opts[:clearer]
1794
- association_module_private_def(opts._remove_all_method, opts, &clearer)
1795
- association_module_def(opts.remove_all_method, opts){|*args| remove_all_associated_objects(opts, *args)}
1993
+ association_module_private_def(opts[:_remove_all_method], opts, &clearer)
1994
+ association_module_def(opts[:remove_all_method], opts){|*args| remove_all_associated_objects(opts, *args)}
1796
1995
  end
1797
1996
  end
1798
1997
 
1799
1998
  # Configures many_to_many and one_through_one association reflection and adds the related association methods
1800
1999
  def def_many_to_many(opts)
1801
2000
  one_through_one = opts[:type] == :one_through_one
1802
- opts[:read_only] = true if one_through_one
1803
2001
  left = (opts[:left_key] ||= opts.default_left_key)
1804
2002
  lcks = opts[:left_keys] = Array(left)
1805
2003
  right = (opts[:right_key] ||= opts.default_right_key)
@@ -1820,7 +2018,10 @@ module Sequel
1820
2018
  join_table = (opts[:join_table] ||= opts.default_join_table)
1821
2019
  opts[:left_key_alias] ||= opts.default_associated_key_alias
1822
2020
  opts[:graph_join_table_join_type] ||= opts[:graph_join_type]
1823
- opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
2021
+ if opts[:uniq]
2022
+ opts[:after_load] ||= []
2023
+ opts[:after_load].unshift(:array_uniq!)
2024
+ end
1824
2025
  opts[:dataset] ||= opts.association_dataset_proc
1825
2026
  opts[:eager_loader] ||= opts.method(:default_eager_loader)
1826
2027
 
@@ -1853,21 +2054,54 @@ module Sequel
1853
2054
  end
1854
2055
  end
1855
2056
 
1856
- return if opts[:read_only] || one_through_one
2057
+ return if opts[:read_only]
1857
2058
 
1858
- opts[:adder] ||= proc do |o|
1859
- h = {}
1860
- lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
1861
- rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
1862
- _join_table_dataset(opts).insert(h)
1863
- end
2059
+ if one_through_one
2060
+ opts[:setter] ||= proc do |o|
2061
+ h = {}
2062
+ lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
2063
+ jtds = _join_table_dataset(opts).where(lh)
1864
2064
 
1865
- opts[:remover] ||= proc do |o|
1866
- _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
1867
- end
2065
+ checked_transaction do
2066
+ current = jtds.first
2067
+
2068
+ if o
2069
+ new_values = []
2070
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
2071
+ end
2072
+
2073
+ if current
2074
+ current_values = rcks.map{|k| current[k]}
2075
+ jtds = jtds.where(rcks.zip(current_values))
2076
+ if o
2077
+ if current_values != new_values
2078
+ jtds.update(h)
2079
+ end
2080
+ else
2081
+ jtds.delete
2082
+ end
2083
+ elsif o
2084
+ lh.each{|k,v| h[k] = v}
2085
+ jtds.insert(h)
2086
+ end
2087
+ end
2088
+ end
2089
+ opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
2090
+ else
2091
+ opts[:adder] ||= proc do |o|
2092
+ h = {}
2093
+ lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
2094
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
2095
+ _join_table_dataset(opts).insert(h)
2096
+ end
2097
+
2098
+ opts[:remover] ||= proc do |o|
2099
+ _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
2100
+ end
1868
2101
 
1869
- opts[:clearer] ||= proc do
1870
- _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
2102
+ opts[:clearer] ||= proc do
2103
+ _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
2104
+ end
1871
2105
  end
1872
2106
  end
1873
2107
 
@@ -1905,9 +2139,7 @@ module Sequel
1905
2139
 
1906
2140
  eager_load_results(opts, eo) do |assoc_record|
1907
2141
  hash_key = uses_cks ? pk_meths.map{|k| assoc_record.get_column_value(k)} : assoc_record.get_column_value(opts.primary_key_method)
1908
- if objects = h[hash_key]
1909
- objects.each{|object| object.associations[name] = assoc_record}
1910
- end
2142
+ h[hash_key].each{|object| object.associations[name] = assoc_record}
1911
2143
  end
1912
2144
  end
1913
2145
 
@@ -1920,7 +2152,7 @@ module Sequel
1920
2152
  graph_cks = opts[:graph_keys]
1921
2153
  opts[:eager_grapher] ||= proc do |eo|
1922
2154
  ds = eo[:self]
1923
- 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)
2155
+ 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)
1924
2156
  end
1925
2157
 
1926
2158
  return if opts[:read_only]
@@ -1954,7 +2186,7 @@ module Sequel
1954
2186
  eager_load_results(opts, eo) do |assoc_record|
1955
2187
  assoc_record.values.delete(delete_rn) if delete_rn
1956
2188
  hash_key = uses_cks ? km.map{|k| assoc_record.get_column_value(k)} : assoc_record.get_column_value(km)
1957
- next unless objects = h[hash_key]
2189
+ objects = h[hash_key]
1958
2190
  if assign_singular
1959
2191
  objects.each do |object|
1960
2192
  unless object.associations[name]
@@ -1980,7 +2212,7 @@ module Sequel
1980
2212
  graph_block = opts[:graph_block]
1981
2213
  opts[:eager_grapher] ||= proc do |eo|
1982
2214
  ds = eo[:self]
1983
- 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)
2215
+ 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)
1984
2216
  # We only load reciprocals for one_to_many associations, as other reciprocals don't make sense
1985
2217
  ds.opts[:eager_graph][:reciprocals][eo[:table_alias]] = opts.reciprocal
1986
2218
  ds
@@ -1995,12 +2227,28 @@ module Sequel
1995
2227
  if one_to_one
1996
2228
  opts[:setter] ||= proc do |o|
1997
2229
  up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
2230
+
2231
+ 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)))
2232
+ if old = up_ds.first
2233
+ cks.each{|k| old.set_column_value(:"#{k}=", nil)}
2234
+ end
2235
+ save_old = true
2236
+ end
2237
+
1998
2238
  if o
1999
- up_ds = up_ds.exclude(o.pk_hash) unless o.new?
2239
+ if !o.new? && !save_old
2240
+ up_ds = up_ds.exclude(o.pk_hash)
2241
+ end
2000
2242
  cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2001
2243
  end
2244
+
2002
2245
  checked_transaction do
2003
- up_ds.update(ck_nil_hash)
2246
+ if save_old
2247
+ old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
2248
+ else
2249
+ up_ds.skip_limit_check.update(ck_nil_hash)
2250
+ end
2251
+
2004
2252
  o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2005
2253
  end
2006
2254
  end
@@ -2063,8 +2311,10 @@ module Sequel
2063
2311
  # retrieving associations after freezing will still work in most cases,
2064
2312
  # but the associations will not be cached in the association cache.
2065
2313
  def freeze
2066
- associations.freeze
2314
+ associations
2067
2315
  super
2316
+ associations.freeze
2317
+ self
2068
2318
  end
2069
2319
 
2070
2320
  private
@@ -2074,15 +2324,16 @@ module Sequel
2074
2324
  unless ds.kind_of?(AssociationDatasetMethods)
2075
2325
  ds = opts.apply_dataset_changes(ds)
2076
2326
  end
2077
- ds.model_object = self
2327
+ ds = ds.clone(:model_object => self)
2078
2328
  ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph] && opts.eager_graph_lazy_dataset?
2079
- ds = instance_exec(ds, &opts[:block]) if opts[:block]
2329
+ # block method is private
2330
+ ds = send(opts[:block_method], ds) if opts[:block_method]
2080
2331
  ds
2081
2332
  end
2082
2333
 
2083
2334
  # Return a dataset for the association after applying any dynamic callback.
2084
2335
  def _associated_dataset(opts, dynamic_opts)
2085
- ds = send(opts.dataset_method)
2336
+ ds = public_send(opts.dataset_method)
2086
2337
  if callback = dynamic_opts[:callback]
2087
2338
  ds = callback.call(ds)
2088
2339
  end
@@ -2099,10 +2350,11 @@ module Sequel
2099
2350
  # Return an association dataset for the given association reflection
2100
2351
  def _dataset(opts)
2101
2352
  raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
2102
- ds = if opts[:dataset].arity == 1
2103
- instance_exec(opts, &opts[:dataset])
2353
+ ds = if opts[:dataset_opt_arity] == 1
2354
+ # dataset_opt_method is private
2355
+ send(opts[:dataset_opt_method], opts)
2104
2356
  else
2105
- instance_exec(&opts[:dataset])
2357
+ send(opts[:dataset_opt_method])
2106
2358
  end
2107
2359
  _apply_association_options(opts, ds)
2108
2360
  end
@@ -2158,18 +2410,12 @@ module Sequel
2158
2410
 
2159
2411
  # Add the given associated object to the given association
2160
2412
  def add_associated_object(opts, o, *args)
2161
- klass = opts.associated_class
2162
- if o.is_a?(Hash)
2163
- o = klass.new(o)
2164
- elsif o.is_a?(Integer) || o.is_a?(String) || o.is_a?(Array)
2165
- o = klass.with_pk!(o)
2166
- elsif !o.is_a?(klass)
2167
- raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}")
2168
- end
2413
+ o = make_add_associated_object(opts, o)
2169
2414
  raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
2170
2415
  ensure_associated_primary_key(opts, o, *args)
2171
2416
  return if run_association_callbacks(opts, :before_add, o) == false
2172
- return if !send(opts._add_method, o, *args) && opts.handle_silent_modification_failure?
2417
+ # Allow calling private _add method
2418
+ return if !send(opts[:_add_method], o, *args) && opts.handle_silent_modification_failure?
2173
2419
  if array = associations[opts[:name]] and !array.include?(o)
2174
2420
  array.push(o)
2175
2421
  end
@@ -2201,7 +2447,32 @@ module Sequel
2201
2447
  # cached associations.
2202
2448
  def change_column_value(column, value)
2203
2449
  if assocs = model.autoreloading_associations[column]
2204
- assocs.each{|a| associations.delete(a)}
2450
+ vals = @values
2451
+ if new?
2452
+ # Do deeper checking for new objects, so that associations are
2453
+ # not deleted when values do not change. This code is run at
2454
+ # a higher level for existing objects.
2455
+ if value == (c = vals[column]) && value.class == c.class
2456
+ # If the value is the same, there is no reason to delete
2457
+ # the related associations, so exit early in that case.
2458
+ return super
2459
+ end
2460
+
2461
+ only_delete_nil = c.nil?
2462
+ elsif vals[column].nil?
2463
+ only_delete_nil = true
2464
+ end
2465
+
2466
+ if only_delete_nil
2467
+ # If the current foreign key value is nil, but the association
2468
+ # is already present in the cache, it was probably added to the
2469
+ # cache for a reason, and we do not want to delete it in that case.
2470
+ # However, we still want to delete associations with nil values
2471
+ # to remove the cached false negative.
2472
+ assocs.each{|a| associations.delete(a) if associations[a].nil?}
2473
+ else
2474
+ assocs.each{|a| associations.delete(a)}
2475
+ end
2205
2476
  end
2206
2477
  super
2207
2478
  end
@@ -2223,18 +2494,21 @@ module Sequel
2223
2494
  self
2224
2495
  end
2225
2496
 
2226
- # Load the associated objects using the dataset, handling callbacks, reciprocals, and caching.
2227
- def load_associated_objects(opts, dynamic_opts=nil)
2228
- if dynamic_opts == true or dynamic_opts == false or dynamic_opts == nil
2229
- dynamic_opts = {:reload=>dynamic_opts}
2230
- elsif dynamic_opts.respond_to?(:call)
2231
- dynamic_opts = {:callback=>dynamic_opts}
2232
- end
2233
- if block_given?
2234
- dynamic_opts = Hash[dynamic_opts].merge!(:callback=>Proc.new)
2497
+ # If a block is given, assign it as the :callback option in the hash, and return the hash.
2498
+ def load_association_objects_options(dynamic_opts, &block)
2499
+ if block
2500
+ dynamic_opts = Hash[dynamic_opts]
2501
+ dynamic_opts[:callback] = block
2235
2502
  end
2503
+
2504
+ dynamic_opts
2505
+ end
2506
+
2507
+ # Load the associated objects using the dataset, handling callbacks, reciprocals, and caching.
2508
+ def load_associated_objects(opts, dynamic_opts, &block)
2509
+ dynamic_opts = load_association_objects_options(dynamic_opts, &block)
2236
2510
  name = opts[:name]
2237
- if associations.include?(name) and !dynamic_opts[:callback] and !dynamic_opts[:reload]
2511
+ if associations.include?(name) && !dynamic_opts[:callback] && !dynamic_opts[:reload]
2238
2512
  associations[name]
2239
2513
  else
2240
2514
  objs = _load_associated_objects(opts, dynamic_opts)
@@ -2263,10 +2537,30 @@ module Sequel
2263
2537
  opts.send(:cached_fetch, :many_to_one_pk_lookup){opts.primary_key == opts.associated_class.primary_key}
2264
2538
  end
2265
2539
 
2540
+ # Convert the input of the add_* association method into an associated object. For
2541
+ # hashes, this creates a new object using the hash. For integers, strings, and arrays,
2542
+ # assume the value specifies a primary key, and lookup an existing object with that primary key.
2543
+ # Otherwise, if the object is not already an instance of the class, raise an exception.
2544
+ def make_add_associated_object(opts, o)
2545
+ klass = opts.associated_class
2546
+
2547
+ case o
2548
+ when Hash
2549
+ klass.new(o)
2550
+ when Integer, String, Array
2551
+ klass.with_pk!(o)
2552
+ when klass
2553
+ o
2554
+ else
2555
+ raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}")
2556
+ end
2557
+ end
2558
+
2266
2559
  # Remove all associated objects from the given association
2267
2560
  def remove_all_associated_objects(opts, *args)
2268
2561
  raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
2269
- send(opts._remove_all_method, *args)
2562
+ # Allow calling private _remove_all method
2563
+ send(opts[:_remove_all_method], *args)
2270
2564
  ret = associations[opts[:name]].each{|o| remove_reciprocal_object(opts, o)} if associations.include?(opts[:name])
2271
2565
  associations[opts[:name]] = []
2272
2566
  ret
@@ -2279,13 +2573,14 @@ module Sequel
2279
2573
  o = remove_check_existing_object_from_pk(opts, o, *args)
2280
2574
  elsif !o.is_a?(klass)
2281
2575
  raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}")
2282
- elsif opts.remove_should_check_existing? && send(opts.dataset_method).where(o.pk_hash).empty?
2576
+ elsif opts.remove_should_check_existing? && public_send(opts.dataset_method).where(o.pk_hash).empty?
2283
2577
  raise(Sequel::Error, "associated object #{o.inspect} is not currently associated to #{inspect}")
2284
2578
  end
2285
2579
  raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
2286
2580
  raise(Sequel::Error, "associated object #{o.inspect} does not have a primary key") if opts.need_associated_primary_key? && !o.pk
2287
2581
  return if run_association_callbacks(opts, :before_remove, o) == false
2288
- return if !send(opts._remove_method, o, *args) && opts.handle_silent_modification_failure?
2582
+ # Allow calling private _remove method
2583
+ return if !send(opts[:_remove_method], o, *args) && opts.handle_silent_modification_failure?
2289
2584
  associations[opts[:name]].delete_if{|x| o === x} if associations.include?(opts[:name])
2290
2585
  remove_reciprocal_object(opts, o)
2291
2586
  run_association_callbacks(opts, :after_remove, o)
@@ -2298,7 +2593,7 @@ module Sequel
2298
2593
  def remove_check_existing_object_from_pk(opts, o, *args)
2299
2594
  key = o
2300
2595
  pkh = opts.associated_class.qualified_primary_key_hash(key)
2301
- raise(Sequel::Error, "no object with key(s) #{key.inspect} is currently associated to #{inspect}") unless o = send(opts.dataset_method).first(pkh)
2596
+ raise(Sequel::Error, "no object with key(s) #{key.inspect} is currently associated to #{inspect}") unless o = public_send(opts.dataset_method).first(pkh)
2302
2597
  o
2303
2598
  end
2304
2599
 
@@ -2316,40 +2611,52 @@ module Sequel
2316
2611
 
2317
2612
  # Run the callback for the association with the object.
2318
2613
  def run_association_callbacks(reflection, callback_type, object)
2319
- # The reason we automatically set raise_error for singular associations is that
2320
- # assignment in ruby always returns the argument instead of the result of the
2321
- # method, so we can't return nil to signal that the association callback prevented
2322
- # the modification
2323
- raise_error = raise_on_save_failure || !reflection.returns_array?
2324
- stop_on_false = [:before_add, :before_remove, :before_set].include?(callback_type)
2325
- reflection[callback_type].each do |cb|
2326
- res = case cb
2327
- when Symbol
2328
- send(cb, object)
2329
- when Proc
2330
- cb.call(self, object)
2331
- else
2332
- raise Error, "callbacks should either be Procs or Symbols"
2333
- end
2334
-
2335
- if res == false and stop_on_false
2336
- raise(HookFailed, "Unable to modify association for #{inspect}: one of the #{callback_type} hooks returned false")
2614
+ return unless cbs = reflection[callback_type]
2615
+
2616
+ begin
2617
+ cbs.each do |cb|
2618
+ case cb
2619
+ when Symbol
2620
+ # Allow calling private methods in association callbacks
2621
+ send(cb, object)
2622
+ when Proc
2623
+ cb.call(self, object)
2624
+ else
2625
+ raise Error, "callbacks should either be Procs or Symbols"
2626
+ end
2337
2627
  end
2628
+ rescue HookFailed
2629
+ # The reason we automatically set raise_error for singular associations is that
2630
+ # assignment in ruby always returns the argument instead of the result of the
2631
+ # method, so we can't return nil to signal that the association callback prevented
2632
+ # the modification
2633
+ return false unless raise_on_save_failure || !reflection.returns_array?
2634
+ raise
2338
2635
  end
2339
- rescue HookFailed
2340
- return false unless raise_error
2341
- raise
2342
2636
  end
2343
2637
 
2344
2638
  # Set the given object as the associated object for the given *_to_one association reflection
2345
2639
  def _set_associated_object(opts, o)
2346
2640
  a = associations[opts[:name]]
2347
- return if a && a == o && !set_associated_object_if_same?
2641
+ reciprocal = opts.reciprocal
2642
+ if set_associated_object_if_same?
2643
+ if reciprocal
2644
+ remove_reciprocal = a && (a != o || a.associations[reciprocal] != self)
2645
+ add_reciprocal = o && o.associations[reciprocal] != self
2646
+ end
2647
+ else
2648
+ return if a && a == o
2649
+ if reciprocal
2650
+ remove_reciprocal = a
2651
+ add_reciprocal = o
2652
+ end
2653
+ end
2348
2654
  run_association_callbacks(opts, :before_set, o)
2349
- remove_reciprocal_object(opts, a) if a
2350
- send(opts._setter_method, o)
2655
+ remove_reciprocal_object(opts, a) if remove_reciprocal
2656
+ # Allow calling private _setter method
2657
+ send(opts[:_setter_method], o)
2351
2658
  associations[opts[:name]] = o
2352
- add_reciprocal_object(opts, o) if o
2659
+ add_reciprocal_object(opts, o) if add_reciprocal
2353
2660
  run_association_callbacks(opts, :after_set, o)
2354
2661
  o
2355
2662
  end
@@ -2367,6 +2674,13 @@ module Sequel
2367
2674
  _set_associated_object(opts, o)
2368
2675
  end
2369
2676
 
2677
+ # Set the given object as the associated object for the given one_through_one association reflection
2678
+ def set_one_through_one_associated_object(opts, o)
2679
+ raise(Error, "object #{inspect} does not have a primary key") unless pk
2680
+ raise(Error, "associated object #{o.inspect} does not have a primary key") if o && !o.pk
2681
+ _set_associated_object(opts, o)
2682
+ end
2683
+
2370
2684
  # Set the given object as the associated object for the given one_to_one association reflection
2371
2685
  def set_one_to_one_associated_object(opts, o)
2372
2686
  raise(Error, "object #{inspect} does not have a primary key") unless pk
@@ -2397,48 +2711,112 @@ module Sequel
2397
2711
  # Album.eager(:artist, :genre).all
2398
2712
  # Album.eager_graph(:artist, :genre).all
2399
2713
  # Album.eager(:artist).eager(:genre).all
2400
- # Album.eager_graph(:artist).eager(:genre).all
2401
- # Artist.eager(:albums=>:tracks).all
2402
- # Artist.eager_graph(:albums=>:tracks).all
2403
- # Artist.eager(:albums=>{:tracks=>:genre}).all
2404
- # Artist.eager_graph(:albums=>{:tracks=>:genre}).all
2714
+ # Album.eager_graph(:artist).eager_graph(:genre).all
2715
+ # Artist.eager(albums: :tracks).all
2716
+ # Artist.eager_graph(albums: :tracks).all
2717
+ # Artist.eager(albums: {tracks: :genre}).all
2718
+ # Artist.eager_graph(albums: {tracks: :genre}).all
2405
2719
  #
2406
2720
  # You can also pass a callback as a hash value in order to customize the dataset being
2407
2721
  # eager loaded at query time, analogous to the way the :eager_block association option
2408
2722
  # allows you to customize it at association definition time. For example,
2409
2723
  # if you wanted artists with their albums since 1990:
2410
2724
  #
2411
- # Artist.eager(:albums => proc{|ds| ds.where{year > 1990}})
2725
+ # Artist.eager(albums: proc{|ds| ds.where{year > 1990}})
2412
2726
  #
2413
2727
  # Or if you needed albums and their artist's name only, using a single query:
2414
2728
  #
2415
- # Albums.eager_graph(:artist => proc{|ds| ds.select(:name)})
2729
+ # Albums.eager_graph(artist: proc{|ds| ds.select(:name)})
2416
2730
  #
2417
2731
  # To cascade eager loading while using a callback, you substitute the cascaded
2418
2732
  # associations with a single entry hash that has the proc callback as the key and
2419
2733
  # the cascaded associations as the value. This will load artists with their albums
2420
2734
  # since 1990, and also the tracks on those albums and the genre for those tracks:
2421
2735
  #
2422
- # Artist.eager(:albums => {proc{|ds| ds.where{year > 1990}}=>{:tracks => :genre}})
2736
+ # Artist.eager(albums: {proc{|ds| ds.where{year > 1990}}=>{tracks: :genre}})
2423
2737
  module DatasetMethods
2424
- Sequel::Dataset.def_mutation_method(:eager, :eager_graph, :module=>self)
2425
-
2426
2738
  %w'inner left right full'.each do |type|
2427
- class_eval <<END, __FILE__, __LINE__+1
2739
+ class_eval(<<-END, __FILE__, __LINE__+1)
2428
2740
  def association_#{type}_join(*associations)
2429
2741
  _association_join(:#{type}, associations)
2430
2742
  end
2431
- END
2743
+ END
2432
2744
  end
2433
2745
 
2434
2746
  # Adds one or more INNER JOINs to the existing dataset using the keys and conditions
2435
- # specified by the given association. The following methods also exist for specifying
2436
- # a different type of JOIN:
2747
+ # specified by the given association(s). Take the same arguments as eager_graph, and
2748
+ # operates similarly, but only adds the joins as opposed to making the other changes
2749
+ # (such as adding selected columns and setting up eager loading).
2750
+ #
2751
+ # The following methods also exist for specifying a different type of JOIN:
2437
2752
  #
2438
2753
  # association_full_join :: FULL JOIN
2439
2754
  # association_inner_join :: INNER JOIN
2440
2755
  # association_left_join :: LEFT JOIN
2441
2756
  # association_right_join :: RIGHT JOIN
2757
+ #
2758
+ # Examples:
2759
+ #
2760
+ # # For each album, association_join load the artist
2761
+ # Album.association_join(:artist).all
2762
+ # # SELECT *
2763
+ # # FROM albums
2764
+ # # INNER JOIN artists AS artist ON (artists.id = albums.artist_id)
2765
+ #
2766
+ # # For each album, association_join load the artist, using a specified alias
2767
+ # Album.association_join(Sequel[:artist].as(:a)).all
2768
+ # # SELECT *
2769
+ # # FROM albums
2770
+ # # INNER JOIN artists AS a ON (a.id = albums.artist_id)
2771
+ #
2772
+ # # For each album, association_join load the artist and genre
2773
+ # Album.association_join(:artist, :genre).all
2774
+ # Album.association_join(:artist).association_join(:genre).all
2775
+ # # SELECT *
2776
+ # # FROM albums
2777
+ # # INNER JOIN artists AS artist ON (artist.id = albums.artist_id)
2778
+ # # INNER JOIN genres AS genre ON (genre.id = albums.genre_id)
2779
+ #
2780
+ # # For each artist, association_join load albums and tracks for each album
2781
+ # Artist.association_join(albums: :tracks).all
2782
+ # # SELECT *
2783
+ # # FROM artists
2784
+ # # INNER JOIN albums ON (albums.artist_id = artists.id)
2785
+ # # INNER JOIN tracks ON (tracks.album_id = albums.id)
2786
+ #
2787
+ # # For each artist, association_join load albums, tracks for each album, and genre for each track
2788
+ # Artist.association_join(albums: {tracks: :genre}).all
2789
+ # # SELECT *
2790
+ # # FROM artists
2791
+ # # INNER JOIN albums ON (albums.artist_id = artists.id)
2792
+ # # INNER JOIN tracks ON (tracks.album_id = albums.id)
2793
+ # # INNER JOIN genres AS genre ON (genre.id = tracks.genre_id)
2794
+ #
2795
+ # # For each artist, association_join load albums with year > 1990
2796
+ # Artist.association_join(albums: proc{|ds| ds.where{year > 1990}}).all
2797
+ # # SELECT *
2798
+ # # FROM artists
2799
+ # # INNER JOIN (
2800
+ # # SELECT * FROM albums WHERE (year > 1990)
2801
+ # # ) AS albums ON (albums.artist_id = artists.id)
2802
+ #
2803
+ # # For each artist, association_join load albums and tracks 1-10 for each album
2804
+ # Artist.association_join(albums: {tracks: proc{|ds| ds.where(number: 1..10)}}).all
2805
+ # # SELECT *
2806
+ # # FROM artists
2807
+ # # INNER JOIN albums ON (albums.artist_id = artists.id)
2808
+ # # INNER JOIN (
2809
+ # # SELECT * FROM tracks WHERE ((number >= 1) AND (number <= 10))
2810
+ # # ) AS tracks ON (tracks.albums_id = albums.id)
2811
+ #
2812
+ # # For each artist, association_join load albums with year > 1990, and tracks for those albums
2813
+ # Artist.association_join(albums: {proc{|ds| ds.where{year > 1990}}=>:tracks}).all
2814
+ # # SELECT *
2815
+ # # FROM artists
2816
+ # # INNER JOIN (
2817
+ # # SELECT * FROM albums WHERE (year > 1990)
2818
+ # # ) AS albums ON (albums.artist_id = artists.id)
2819
+ # # INNER JOIN tracks ON (tracks.album_id = albums.id)
2442
2820
  def association_join(*associations)
2443
2821
  association_inner_join(*associations)
2444
2822
  end
@@ -2451,10 +2829,10 @@ END
2451
2829
  # types, this is a simple transformation, but for +many_to_many+ associations this
2452
2830
  # creates a subquery to the join table.
2453
2831
  def complex_expression_sql_append(sql, op, args)
2454
- r = args.at(1)
2455
- if (((op == :'=' || op == :'!=') and r.is_a?(Sequel::Model)) ||
2456
- (multiple = ((op == :IN || op == :'NOT IN') and ((is_ds = r.is_a?(Sequel::Dataset)) or r.all?{|x| x.is_a?(Sequel::Model)}))))
2457
- l = args.at(0)
2832
+ r = args[1]
2833
+ if (((op == :'=' || op == :'!=') && r.is_a?(Sequel::Model)) ||
2834
+ (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)})))))
2835
+ l = args[0]
2458
2836
  if ar = model.association_reflections[l]
2459
2837
  if multiple
2460
2838
  klass = ar.associated_class
@@ -2509,7 +2887,7 @@ END
2509
2887
  # it avoids problems such as aliasing conflicts and creating cartesian product
2510
2888
  # result sets if multiple one_to_many or many_to_many eager associations are requested.
2511
2889
  #
2512
- # One limitation of using this method is that you cannot filter the dataset
2890
+ # One limitation of using this method is that you cannot filter the current dataset
2513
2891
  # based on values of columns in an associated table, since the associations are loaded
2514
2892
  # in separate queries. To do that you need to load all associations in the
2515
2893
  # same query, and extract an object graph from the results of that query. If you
@@ -2518,15 +2896,66 @@ END
2518
2896
  #
2519
2897
  # Each association's order, if defined, is respected.
2520
2898
  # If the association uses a block or has an :eager_block argument, it is used.
2899
+ #
2900
+ # To modify the associated dataset that will be used for the eager load, you should use a
2901
+ # hash for the association, with the key being the association name symbol, and the value being
2902
+ # a callable object that is called with the associated dataset and should return a modified
2903
+ # dataset. If that association also has dependent associations, instead of a callable object,
2904
+ # use a hash with the callable object being the key, and the dependent association(s) as the value.
2905
+ #
2906
+ # Examples:
2907
+ #
2908
+ # # For each album, eager load the artist
2909
+ # Album.eager(:artist).all
2910
+ # # SELECT * FROM albums
2911
+ # # SELECT * FROM artists WHERE (id IN (...))
2912
+ #
2913
+ # # For each album, eager load the artist and genre
2914
+ # Album.eager(:artist, :genre).all
2915
+ # Album.eager(:artist).eager(:genre).all
2916
+ # # SELECT * FROM albums
2917
+ # # SELECT * FROM artists WHERE (id IN (...))
2918
+ # # SELECT * FROM genres WHERE (id IN (...))
2919
+ #
2920
+ # # For each artist, eager load albums and tracks for each album
2921
+ # Artist.eager(albums: :tracks).all
2922
+ # # SELECT * FROM artists
2923
+ # # SELECT * FROM albums WHERE (artist_id IN (...))
2924
+ # # SELECT * FROM tracks WHERE (album_id IN (...))
2925
+ #
2926
+ # # For each artist, eager load albums, tracks for each album, and genre for each track
2927
+ # Artist.eager(albums: {tracks: :genre}).all
2928
+ # # SELECT * FROM artists
2929
+ # # SELECT * FROM albums WHERE (artist_id IN (...))
2930
+ # # SELECT * FROM tracks WHERE (album_id IN (...))
2931
+ # # SELECT * FROM genre WHERE (id IN (...))
2932
+ #
2933
+ # # For each artist, eager load albums with year > 1990
2934
+ # Artist.eager(albums: proc{|ds| ds.where{year > 1990}}).all
2935
+ # # SELECT * FROM artists
2936
+ # # SELECT * FROM albums WHERE ((year > 1990) AND (artist_id IN (...)))
2937
+ #
2938
+ # # For each artist, eager load albums and tracks 1-10 for each album
2939
+ # Artist.eager(albums: {tracks: proc{|ds| ds.where(number: 1..10)}}).all
2940
+ # # SELECT * FROM artists
2941
+ # # SELECT * FROM albums WHERE (artist_id IN (...))
2942
+ # # SELECT * FROM tracks WHERE ((number >= 1) AND (number <= 10) AND (album_id IN (...)))
2943
+ #
2944
+ # # For each artist, eager load albums with year > 1990, and tracks for those albums
2945
+ # Artist.eager(albums: {proc{|ds| ds.where{year > 1990}}=>:tracks}).all
2946
+ # # SELECT * FROM artists
2947
+ # # SELECT * FROM albums WHERE ((year > 1990) AND (artist_id IN (...)))
2948
+ # # SELECT * FROM albums WHERE (artist_id IN (...))
2521
2949
  def eager(*associations)
2522
2950
  opts = @opts[:eager]
2523
2951
  association_opts = eager_options_for_associations(associations)
2524
- opts = opts ? Hash[opts].merge!(association_opts) : association_opts
2525
- clone(:eager=>opts)
2952
+ opts = opts ? opts.merge(association_opts) : association_opts
2953
+ clone(:eager=>opts.freeze)
2526
2954
  end
2527
2955
 
2528
2956
  # The secondary eager loading method. Loads all associations in a single query. This
2529
- # method should only be used if you need to filter or order based on columns in associated tables.
2957
+ # method should only be used if you need to filter or order based on columns in associated tables,
2958
+ # or if you have done comparative benchmarking it and determined it is faster.
2530
2959
  #
2531
2960
  # This method uses <tt>Dataset#graph</tt> to create appropriate aliases for columns in all the
2532
2961
  # tables. Then it uses the graph's metadata to build the associations from the single hash, and
@@ -2534,9 +2963,9 @@ END
2534
2963
  #
2535
2964
  # Be very careful when using this with multiple one_to_many or many_to_many associations, as you can
2536
2965
  # create large cartesian products. If you must graph multiple one_to_many and many_to_many associations,
2537
- # make sure your filters are narrow if you have a large database.
2966
+ # make sure your filters are narrow if the datasets are large.
2538
2967
  #
2539
- # Each association's order, if definied, is respected. +eager_graph+ probably
2968
+ # Each association's order, if defined, is respected. +eager_graph+ probably
2540
2969
  # won't work correctly on a limited dataset, unless you are
2541
2970
  # only graphing many_to_one, one_to_one, and one_through_one associations.
2542
2971
  #
@@ -2545,6 +2974,86 @@ END
2545
2974
  #
2546
2975
  # Like +eager+, you need to call +all+ on the dataset for the eager loading to work. If you just
2547
2976
  # call +each+, it will yield plain hashes, each containing all columns from all the tables.
2977
+ #
2978
+ # To modify the associated dataset that will be joined to the current dataset, you should use a
2979
+ # hash for the association, with the key being the association name symbol, and the value being
2980
+ # a callable object that is called with the associated dataset and should return a modified
2981
+ # dataset. If that association also has dependent associations, instead of a callable object,
2982
+ # use a hash with the callable object being the key, and the dependent association(s) as the value.
2983
+ #
2984
+ # You can specify an custom alias and/or join type on a per-association basis by providing an
2985
+ # Sequel::SQL::AliasedExpression object instead of an a Symbol for the association name.
2986
+ #
2987
+ # Examples:
2988
+ #
2989
+ # # For each album, eager_graph load the artist
2990
+ # Album.eager_graph(:artist).all
2991
+ # # SELECT ...
2992
+ # # FROM albums
2993
+ # # LEFT OUTER JOIN artists AS artist ON (artists.id = albums.artist_id)
2994
+ #
2995
+ # # For each album, eager_graph load the artist, using a specified alias
2996
+ # Album.eager_graph(Sequel[:artist].as(:a)).all
2997
+ # # SELECT ...
2998
+ # # FROM albums
2999
+ # # LEFT OUTER JOIN artists AS a ON (a.id = albums.artist_id)
3000
+ #
3001
+ # # For each album, eager_graph load the artist, using a specified alias
3002
+ # # and custom join type
3003
+ #
3004
+ # Album.eager_graph(Sequel[:artist].as(:a, join_type: :inner)).all
3005
+ # # SELECT ...
3006
+ # # FROM albums
3007
+ # # INNER JOIN artists AS a ON (a.id = albums.artist_id)
3008
+ #
3009
+ # # For each album, eager_graph load the artist and genre
3010
+ # Album.eager_graph(:artist, :genre).all
3011
+ # Album.eager_graph(:artist).eager_graph(:genre).all
3012
+ # # SELECT ...
3013
+ # # FROM albums
3014
+ # # LEFT OUTER JOIN artists AS artist ON (artist.id = albums.artist_id)
3015
+ # # LEFT OUTER JOIN genres AS genre ON (genre.id = albums.genre_id)
3016
+ #
3017
+ # # For each artist, eager_graph load albums and tracks for each album
3018
+ # Artist.eager_graph(albums: :tracks).all
3019
+ # # SELECT ...
3020
+ # # FROM artists
3021
+ # # LEFT OUTER JOIN albums ON (albums.artist_id = artists.id)
3022
+ # # LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)
3023
+ #
3024
+ # # For each artist, eager_graph load albums, tracks for each album, and genre for each track
3025
+ # Artist.eager_graph(albums: {tracks: :genre}).all
3026
+ # # SELECT ...
3027
+ # # FROM artists
3028
+ # # LEFT OUTER JOIN albums ON (albums.artist_id = artists.id)
3029
+ # # LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)
3030
+ # # LEFT OUTER JOIN genres AS genre ON (genre.id = tracks.genre_id)
3031
+ #
3032
+ # # For each artist, eager_graph load albums with year > 1990
3033
+ # Artist.eager_graph(albums: proc{|ds| ds.where{year > 1990}}).all
3034
+ # # SELECT ...
3035
+ # # FROM artists
3036
+ # # LEFT OUTER JOIN (
3037
+ # # SELECT * FROM albums WHERE (year > 1990)
3038
+ # # ) AS albums ON (albums.artist_id = artists.id)
3039
+ #
3040
+ # # For each artist, eager_graph load albums and tracks 1-10 for each album
3041
+ # Artist.eager_graph(albums: {tracks: proc{|ds| ds.where(number: 1..10)}}).all
3042
+ # # SELECT ...
3043
+ # # FROM artists
3044
+ # # LEFT OUTER JOIN albums ON (albums.artist_id = artists.id)
3045
+ # # LEFT OUTER JOIN (
3046
+ # # SELECT * FROM tracks WHERE ((number >= 1) AND (number <= 10))
3047
+ # # ) AS tracks ON (tracks.albums_id = albums.id)
3048
+ #
3049
+ # # For each artist, eager_graph load albums with year > 1990, and tracks for those albums
3050
+ # Artist.eager_graph(albums: {proc{|ds| ds.where{year > 1990}}=>:tracks}).all
3051
+ # # SELECT ...
3052
+ # # FROM artists
3053
+ # # LEFT OUTER JOIN (
3054
+ # # SELECT * FROM albums WHERE (year > 1990)
3055
+ # # ) AS albums ON (albums.artist_id = artists.id)
3056
+ # # LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)
2548
3057
  def eager_graph(*associations)
2549
3058
  eager_graph_with_options(associations)
2550
3059
  end
@@ -2570,8 +3079,11 @@ END
2570
3079
  # significantly slower in some cases (perhaps even the majority of cases), so you should
2571
3080
  # only use this if you have benchmarked that it is faster for your use cases.
2572
3081
  def eager_graph_with_options(associations, opts=OPTS)
3082
+ return self if associations.empty?
3083
+
3084
+ opts = opts.dup unless opts.frozen?
2573
3085
  associations = [associations] unless associations.is_a?(Array)
2574
- if eg = @opts[:eager_graph]
3086
+ ds = if eg = @opts[:eager_graph]
2575
3087
  eg = eg.dup
2576
3088
  [:requirements, :reflections, :reciprocals, :limits].each{|k| eg[k] = eg[k].dup}
2577
3089
  eg[:local] = opts
@@ -2585,8 +3097,32 @@ END
2585
3097
  # :limits :: Any limit/offset array slicing that need to be handled in ruby land after loading
2586
3098
  opts = {:requirements=>{}, :master=>alias_symbol(first_source), :reflections=>{}, :reciprocals=>{}, :limits=>{}, :local=>opts, :cartesian_product_number=>0, :row_proc=>row_proc}
2587
3099
  ds = clone(:eager_graph=>opts)
2588
- ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations).naked
3100
+ ds = ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations).naked
2589
3101
  end
3102
+
3103
+ ds.opts[:eager_graph].freeze
3104
+ ds.opts[:eager_graph].each_value{|v| v.freeze if v.is_a?(Hash)}
3105
+ ds
3106
+ end
3107
+
3108
+ # If the dataset is being eagerly loaded, default to calling all
3109
+ # instead of each.
3110
+ def as_hash(key_column=nil, value_column=nil, opts=OPTS)
3111
+ if (@opts[:eager_graph] || @opts[:eager]) && !opts.has_key?(:all)
3112
+ opts = Hash[opts]
3113
+ opts[:all] = true
3114
+ end
3115
+ super
3116
+ end
3117
+
3118
+ # If the dataset is being eagerly loaded, default to calling all
3119
+ # instead of each.
3120
+ def to_hash_groups(key_column, value_column=nil, opts=OPTS)
3121
+ if (@opts[:eager_graph] || @opts[:eager]) && !opts.has_key?(:all)
3122
+ opts = Hash[opts]
3123
+ opts[:all] = true
3124
+ end
3125
+ super
2590
3126
  end
2591
3127
 
2592
3128
  # Do not attempt to split the result set into associations,
@@ -2596,7 +3132,7 @@ END
2596
3132
  def ungraphed
2597
3133
  ds = super.clone(:eager_graph=>nil)
2598
3134
  if (eg = @opts[:eager_graph]) && (rp = eg[:row_proc])
2599
- ds.row_proc = rp
3135
+ ds = ds.with_row_proc(rp)
2600
3136
  end
2601
3137
  ds
2602
3138
  end
@@ -2614,11 +3150,16 @@ END
2614
3150
  # ta :: table_alias used for the parent association
2615
3151
  # requirements :: an array, used as a stack for requirements
2616
3152
  # r :: association reflection for the current association, or an SQL::AliasedExpression
2617
- # with the reflection as the expression and the alias base as the aliaz.
3153
+ # with the reflection as the expression, the alias base as the alias (or nil to
3154
+ # use the default alias), and an optional hash with a :join_type entry as the columns
3155
+ # to use a custom join type.
2618
3156
  # *associations :: any associations dependent on this one
2619
3157
  def eager_graph_association(ds, model, ta, requirements, r, *associations)
2620
3158
  if r.is_a?(SQL::AliasedExpression)
2621
3159
  alias_base = r.alias
3160
+ if r.columns.is_a?(Hash)
3161
+ join_type = r.columns[:join_type]
3162
+ end
2622
3163
  r = r.expression
2623
3164
  else
2624
3165
  alias_base = r[:graph_alias_base]
@@ -2636,9 +3177,14 @@ END
2636
3177
  end
2637
3178
  local_opts = ds.opts[:eager_graph][:local]
2638
3179
  limit_strategy = r.eager_graph_limit_strategy(local_opts[:limit_strategy])
2639
- 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])
3180
+
3181
+ 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)
3182
+ 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]}"
3183
+ end
3184
+
3185
+ 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])
2640
3186
  if r[:order_eager_graph] && (order = r.fetch(:graph_order, r[:order]))
2641
- ds = ds.order_more(*qualified_expression(order, assoc_table_alias))
3187
+ ds = ds.order_append(*qualified_expression(order, assoc_table_alias))
2642
3188
  end
2643
3189
  eager_graph = ds.opts[:eager_graph]
2644
3190
  eager_graph[:requirements][assoc_table_alias] = requirements.dup
@@ -2661,7 +3207,6 @@ END
2661
3207
  # requirements :: an array, used as a stack for requirements
2662
3208
  # *associations :: the associations to add to the graph
2663
3209
  def eager_graph_associations(ds, model, ta, requirements, *associations)
2664
- return ds if associations.empty?
2665
3210
  associations.flatten.each do |association|
2666
3211
  ds = case association
2667
3212
  when Symbol, SQL::AliasedExpression
@@ -2681,7 +3226,7 @@ END
2681
3226
  # Replace the array of plain hashes with an array of model objects will all eager_graphed
2682
3227
  # associations set in the associations cache for each object.
2683
3228
  def eager_graph_build_associations(hashes)
2684
- hashes.replace(EagerGraphLoader.new(self).load(hashes))
3229
+ hashes.replace(_eager_graph_build_associations(hashes, eager_graph_loader))
2685
3230
  end
2686
3231
 
2687
3232
  private
@@ -2692,12 +3237,18 @@ END
2692
3237
  clone(:join=>clone(:graph_from_self=>false).eager_graph_with_options(associations, :join_type=>type, :join_only=>true).opts[:join])
2693
3238
  end
2694
3239
 
3240
+ # Process the array of hashes using the eager graph loader to return an array
3241
+ # of model objects with the associations set.
3242
+ def _eager_graph_build_associations(hashes, egl)
3243
+ egl.load(hashes)
3244
+ end
3245
+
2695
3246
  # If the association has conditions itself, then it requires additional filters be
2696
3247
  # added to the current dataset to ensure that the passed in object would also be
2697
3248
  # included by the association's conditions.
2698
3249
  def add_association_filter_conditions(ref, obj, expr)
2699
3250
  if expr != SQL::Constants::FALSE && ref.filter_by_associations_add_conditions?
2700
- Sequel.expr(ref.filter_by_associations_conditions_expression(obj))
3251
+ Sequel[ref.filter_by_associations_conditions_expression(obj)]
2701
3252
  else
2702
3253
  expr
2703
3254
  end
@@ -2725,6 +3276,7 @@ END
2725
3276
  # Return an expression for filtering by the given association reflection and associated object.
2726
3277
  def association_filter_expression(op, ref, obj)
2727
3278
  meth = :"#{ref[:type]}_association_filter_expression"
3279
+ # Allow calling private association specific method to get filter expression
2728
3280
  send(meth, op, ref, obj) if respond_to?(meth, true)
2729
3281
  end
2730
3282
 
@@ -2776,22 +3328,37 @@ END
2776
3328
  # per-call determining of the alias base.
2777
3329
  def eager_graph_check_association(model, association)
2778
3330
  if association.is_a?(SQL::AliasedExpression)
2779
- SQL::AliasedExpression.new(check_association(model, association.expression), association.alias)
3331
+ expr = association.expression
3332
+ if expr.is_a?(SQL::Identifier)
3333
+ expr = expr.value
3334
+ if expr.is_a?(String)
3335
+ expr = expr.to_sym
3336
+ end
3337
+ end
3338
+
3339
+ SQL::AliasedExpression.new(check_association(model, expr), association.alias || expr, association.columns)
2780
3340
  else
2781
3341
  check_association(model, association)
2782
3342
  end
2783
3343
  end
2784
3344
 
3345
+ # The EagerGraphLoader instance used for converting eager_graph results.
3346
+ def eager_graph_loader
3347
+ unless egl = cache_get(:_model_eager_graph_loader)
3348
+ egl = cache_set(:_model_eager_graph_loader, EagerGraphLoader.new(self))
3349
+ end
3350
+ egl.dup
3351
+ end
3352
+
2785
3353
  # Eagerly load all specified associations
2786
3354
  def eager_load(a, eager_assoc=@opts[:eager])
2787
3355
  return if a.empty?
2788
- # Key is foreign/primary key name symbol
3356
+ # Key is foreign/primary key name symbol.
2789
3357
  # Value is hash with keys being foreign/primary key values (generally integers)
2790
- # and values being an array of current model objects with that
2791
- # specific foreign/primary key
3358
+ # and values being an array of current model objects with that specific foreign/primary key
2792
3359
  key_hash = {}
2793
3360
  # Reflections for all associations to eager load
2794
- reflections = eager_assoc.keys.collect{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
3361
+ reflections = eager_assoc.keys.map{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
2795
3362
 
2796
3363
  # Populate the key_hash entry for each association being eagerly loaded
2797
3364
  reflections.each do |r|
@@ -2826,12 +3393,12 @@ END
2826
3393
  associations = eager_assoc[r[:name]]
2827
3394
  if associations.respond_to?(:call)
2828
3395
  eager_block = associations
2829
- associations = {}
3396
+ associations = OPTS
2830
3397
  elsif associations.is_a?(Hash) && associations.length == 1 && (pr_assoc = associations.to_a.first) && pr_assoc.first.respond_to?(:call)
2831
3398
  eager_block, associations = pr_assoc
2832
3399
  end
2833
3400
  loader.call(:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block, :id_map=>id_map)
2834
- a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} unless r[:after_load].empty?
3401
+ a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} if r[:after_load]
2835
3402
  end
2836
3403
  end
2837
3404
 
@@ -2887,6 +3454,10 @@ END
2887
3454
  end
2888
3455
  alias one_to_one_association_filter_expression one_to_many_association_filter_expression
2889
3456
 
3457
+ def non_sql_option?(key)
3458
+ super || key == :eager || key == :eager_graph
3459
+ end
3460
+
2890
3461
  # Build associations from the graph if #eager_graph was used,
2891
3462
  # and/or load other associations if #eager was used.
2892
3463
  def post_load(all_records)
@@ -2916,7 +3487,6 @@ END
2916
3487
  # Hash with table alias symbol keys and [limit, offset] values
2917
3488
  attr_reader :limit_map
2918
3489
 
2919
- # Hash with table alias symbol keys and callable values used to create model instances
2920
3490
  # The table alias symbol for the primary model
2921
3491
  attr_reader :master
2922
3492
 
@@ -2959,19 +3529,22 @@ END
2959
3529
  after_load_map = @after_load_map = {}
2960
3530
  reflection_map.each do |k, v|
2961
3531
  alias_map[k] = v[:name]
2962
- after_load_map[k] = v[:after_load] unless v[:after_load].empty?
3532
+ after_load_map[k] = v[:after_load] if v[:after_load]
2963
3533
  type_map[k] = if v.returns_array?
2964
3534
  true
2965
3535
  elsif (limit_and_offset = limit_map[k]) && !limit_and_offset.last.nil?
2966
3536
  :offset
2967
3537
  end
2968
3538
  end
3539
+ after_load_map.freeze
3540
+ alias_map.freeze
3541
+ type_map.freeze
2969
3542
 
2970
3543
  # Make dependency map hash out of requirements array for each association.
2971
3544
  # This builds a tree of dependencies that will be used for recursion
2972
3545
  # to ensure that all parts of the object graph are loaded into the
2973
3546
  # appropriate subordinate association.
2974
- @dependency_map = {}
3547
+ dependency_map = @dependency_map = {}
2975
3548
  # Sort the associations by requirements length, so that
2976
3549
  # requirements are added to the dependency hash before their
2977
3550
  # dependencies.
@@ -2987,20 +3560,14 @@ END
2987
3560
  hash[ta] = {}
2988
3561
  end
2989
3562
  end
3563
+ freezer = lambda do |h|
3564
+ h.freeze
3565
+ h.each_value(&freezer)
3566
+ end
3567
+ freezer.call(dependency_map)
2990
3568
 
2991
- # This mapping is used to make sure that duplicate entries in the
2992
- # result set are mapped to a single record. For example, using a
2993
- # single one_to_many association with 10 associated records,
2994
- # the main object column values appear in the object graph 10 times.
2995
- # We map by primary key, if available, or by the object's entire values,
2996
- # if not. The mapping must be per table, so create sub maps for each table
2997
- # alias.
2998
- records_map = {@master=>{}}
2999
- alias_map.keys.each{|ta| records_map[ta] = {}}
3000
- @records_map = records_map
3001
-
3002
3569
  datasets = opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
3003
- column_aliases = opts[:graph_aliases] || opts[:graph][:column_aliases]
3570
+ column_aliases = opts[:graph][:column_aliases]
3004
3571
  primary_keys = {}
3005
3572
  column_maps = {}
3006
3573
  models = {}
@@ -3024,9 +3591,9 @@ END
3024
3591
  h.select{|ca, c| primary_keys[ta] = ca if pk == c}
3025
3592
  end
3026
3593
  end
3027
- @column_maps = column_maps
3028
- @primary_keys = primary_keys
3029
- @row_procs = row_procs
3594
+ @column_maps = column_maps.freeze
3595
+ @primary_keys = primary_keys.freeze
3596
+ @row_procs = row_procs.freeze
3030
3597
 
3031
3598
  # For performance, create two special maps for the master table,
3032
3599
  # so you can skip a hash lookup.
@@ -3038,22 +3605,35 @@ END
3038
3605
  # used for performance, to get all values in one hash lookup instead of
3039
3606
  # separate hash lookups for each data structure.
3040
3607
  ta_map = {}
3041
- alias_map.keys.each do |ta|
3042
- ta_map[ta] = [records_map[ta], row_procs[ta], alias_map[ta], type_map[ta], reciprocal_map[ta]]
3608
+ alias_map.each_key do |ta|
3609
+ ta_map[ta] = [row_procs[ta], alias_map[ta], type_map[ta], reciprocal_map[ta]].freeze
3043
3610
  end
3044
- @ta_map = ta_map
3611
+ @ta_map = ta_map.freeze
3612
+ freeze
3045
3613
  end
3046
3614
 
3047
3615
  # Return an array of primary model instances with the associations cache prepopulated
3048
3616
  # for all model objects (both primary and associated).
3049
3617
  def load(hashes)
3618
+ # This mapping is used to make sure that duplicate entries in the
3619
+ # result set are mapped to a single record. For example, using a
3620
+ # single one_to_many association with 10 associated records,
3621
+ # the main object column values appear in the object graph 10 times.
3622
+ # We map by primary key, if available, or by the object's entire values,
3623
+ # if not. The mapping must be per table, so create sub maps for each table
3624
+ # alias.
3625
+ @records_map = records_map = {}
3626
+ alias_map.keys.each{|ta| records_map[ta] = {}}
3627
+
3050
3628
  master = master()
3051
3629
 
3052
3630
  # Assign to local variables for speed increase
3053
3631
  rp = row_procs[master]
3054
- rm = records_map[master]
3632
+ rm = records_map[master] = {}
3055
3633
  dm = dependency_map
3056
3634
 
3635
+ records_map.freeze
3636
+
3057
3637
  # This will hold the final record set that we will be replacing the object graph with.
3058
3638
  records = []
3059
3639
 
@@ -3074,6 +3654,9 @@ END
3074
3654
  # Run after_load procs if there are any
3075
3655
  post_process(records, dm) if @unique || !after_load_map.empty? || !limit_map.empty?
3076
3656
 
3657
+ records_map.each_value(&:freeze)
3658
+ freeze
3659
+
3077
3660
  records
3078
3661
  end
3079
3662
 
@@ -3093,7 +3676,17 @@ END
3093
3676
  end
3094
3677
  key = hkey(ta_h)
3095
3678
  end
3096
- rm, rp, assoc_name, tm, rcm = @ta_map[ta]
3679
+ rp, assoc_name, tm, rcm = @ta_map[ta]
3680
+ rm = records_map[ta]
3681
+
3682
+ # Check type map for all dependencies, and use a unique
3683
+ # object if any are dependencies for multiple objects,
3684
+ # to prevent duplicate objects from showing up in the case
3685
+ # the normal duplicate removal code is not being used.
3686
+ if !@unique && !deps.empty? && deps.any?{|dep_key,_| @ta_map[dep_key][2]}
3687
+ key = [current.object_id, key]
3688
+ end
3689
+
3097
3690
  unless rec = rm[key]
3098
3691
  rec = rm[key] = rp.call(hfor(ta, h))
3099
3692
  end
@@ -3122,7 +3715,7 @@ END
3122
3715
  # Return a suitable hash key for any subhash +h+, which is an array of values by column order.
3123
3716
  # This is only used if the primary key cannot be used.
3124
3717
  def hkey(h)
3125
- h.sort_by{|x| x[0].to_s}
3718
+ h.sort_by{|x| x[0]}
3126
3719
  end
3127
3720
 
3128
3721
  # Return the subhash for the master table by parsing the values out of the main hash +h+
@@ -3167,7 +3760,7 @@ END
3167
3760
  records.each do |record|
3168
3761
  dependency_map.each do |ta, deps|
3169
3762
  assoc_name = alias_map[ta]
3170
- list = record.send(assoc_name)
3763
+ list = record.public_send(assoc_name)
3171
3764
  rec_list = if type_map[ta]
3172
3765
  list.uniq!
3173
3766
  if lo = limit_map[ta]