sequel 3.21.0 → 3.39.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 (352) hide show
  1. data/CHANGELOG +1061 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +103 -63
  4. data/Rakefile +59 -27
  5. data/bin/sequel +50 -26
  6. data/doc/active_record.rdoc +67 -51
  7. data/doc/advanced_associations.rdoc +189 -75
  8. data/doc/association_basics.rdoc +327 -79
  9. data/doc/cheat_sheet.rdoc +21 -21
  10. data/doc/core_extensions.rdoc +374 -0
  11. data/doc/dataset_basics.rdoc +10 -10
  12. data/doc/dataset_filtering.rdoc +54 -42
  13. data/doc/mass_assignment.rdoc +56 -0
  14. data/doc/migration.rdoc +89 -557
  15. data/doc/model_hooks.rdoc +80 -29
  16. data/doc/object_model.rdoc +547 -0
  17. data/doc/opening_databases.rdoc +114 -53
  18. data/doc/prepared_statements.rdoc +41 -15
  19. data/doc/querying.rdoc +137 -56
  20. data/doc/reflection.rdoc +36 -10
  21. data/doc/release_notes/3.22.0.txt +39 -0
  22. data/doc/release_notes/3.23.0.txt +172 -0
  23. data/doc/release_notes/3.24.0.txt +420 -0
  24. data/doc/release_notes/3.25.0.txt +88 -0
  25. data/doc/release_notes/3.26.0.txt +88 -0
  26. data/doc/release_notes/3.27.0.txt +82 -0
  27. data/doc/release_notes/3.28.0.txt +304 -0
  28. data/doc/release_notes/3.29.0.txt +459 -0
  29. data/doc/release_notes/3.30.0.txt +135 -0
  30. data/doc/release_notes/3.31.0.txt +146 -0
  31. data/doc/release_notes/3.32.0.txt +202 -0
  32. data/doc/release_notes/3.33.0.txt +157 -0
  33. data/doc/release_notes/3.34.0.txt +671 -0
  34. data/doc/release_notes/3.35.0.txt +144 -0
  35. data/doc/release_notes/3.36.0.txt +245 -0
  36. data/doc/release_notes/3.37.0.txt +338 -0
  37. data/doc/release_notes/3.38.0.txt +234 -0
  38. data/doc/release_notes/3.39.0.txt +237 -0
  39. data/doc/schema_modification.rdoc +585 -0
  40. data/doc/sharding.rdoc +99 -8
  41. data/doc/sql.rdoc +154 -112
  42. data/doc/testing.rdoc +169 -0
  43. data/doc/thread_safety.rdoc +17 -0
  44. data/doc/transactions.rdoc +137 -0
  45. data/doc/validations.rdoc +1 -1
  46. data/doc/virtual_rows.rdoc +78 -43
  47. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  48. data/lib/sequel/adapters/ado.rb +40 -18
  49. data/lib/sequel/adapters/amalgalite.rb +17 -9
  50. data/lib/sequel/adapters/db2.rb +175 -86
  51. data/lib/sequel/adapters/dbi.rb +15 -15
  52. data/lib/sequel/adapters/do/mysql.rb +17 -11
  53. data/lib/sequel/adapters/do/postgres.rb +2 -61
  54. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  55. data/lib/sequel/adapters/do.rb +18 -36
  56. data/lib/sequel/adapters/firebird.rb +27 -208
  57. data/lib/sequel/adapters/ibmdb.rb +453 -0
  58. data/lib/sequel/adapters/informix.rb +6 -23
  59. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  60. data/lib/sequel/adapters/jdbc/db2.rb +56 -0
  61. data/lib/sequel/adapters/jdbc/derby.rb +325 -0
  62. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  63. data/lib/sequel/adapters/jdbc/h2.rb +73 -31
  64. data/lib/sequel/adapters/jdbc/hsqldb.rb +184 -0
  65. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  66. data/lib/sequel/adapters/jdbc/jtds.rb +40 -0
  67. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  68. data/lib/sequel/adapters/jdbc/mysql.rb +7 -24
  69. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  70. data/lib/sequel/adapters/jdbc/postgresql.rb +77 -52
  71. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  72. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  73. data/lib/sequel/adapters/jdbc/sqlserver.rb +66 -0
  74. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  75. data/lib/sequel/adapters/jdbc.rb +279 -97
  76. data/lib/sequel/adapters/mock.rb +372 -0
  77. data/lib/sequel/adapters/mysql.rb +125 -222
  78. data/lib/sequel/adapters/mysql2.rb +65 -56
  79. data/lib/sequel/adapters/odbc/mssql.rb +17 -9
  80. data/lib/sequel/adapters/odbc.rb +28 -9
  81. data/lib/sequel/adapters/openbase.rb +3 -5
  82. data/lib/sequel/adapters/oracle.rb +349 -53
  83. data/lib/sequel/adapters/postgres.rb +368 -138
  84. data/lib/sequel/adapters/shared/access.rb +32 -9
  85. data/lib/sequel/adapters/shared/db2.rb +343 -0
  86. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  87. data/lib/sequel/adapters/shared/informix.rb +53 -0
  88. data/lib/sequel/adapters/shared/mssql.rb +401 -116
  89. data/lib/sequel/adapters/shared/mysql.rb +424 -89
  90. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +155 -0
  91. data/lib/sequel/adapters/shared/oracle.rb +257 -45
  92. data/lib/sequel/adapters/shared/postgres.rb +675 -279
  93. data/lib/sequel/adapters/shared/progress.rb +5 -8
  94. data/lib/sequel/adapters/shared/sqlite.rb +285 -96
  95. data/lib/sequel/adapters/sqlite.rb +101 -39
  96. data/lib/sequel/adapters/swift/mysql.rb +10 -12
  97. data/lib/sequel/adapters/swift/postgres.rb +13 -79
  98. data/lib/sequel/adapters/swift/sqlite.rb +9 -6
  99. data/lib/sequel/adapters/swift.rb +28 -19
  100. data/lib/sequel/adapters/tinytds.rb +174 -22
  101. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +77 -0
  102. data/lib/sequel/adapters/utils/pg_types.rb +81 -0
  103. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  104. data/lib/sequel/ast_transformer.rb +194 -0
  105. data/lib/sequel/connection_pool/sharded_single.rb +11 -1
  106. data/lib/sequel/connection_pool/sharded_threaded.rb +33 -2
  107. data/lib/sequel/connection_pool/single.rb +5 -0
  108. data/lib/sequel/connection_pool/threaded.rb +43 -14
  109. data/lib/sequel/connection_pool.rb +10 -2
  110. data/lib/sequel/core.rb +143 -11
  111. data/lib/sequel/database/connecting.rb +38 -28
  112. data/lib/sequel/database/dataset.rb +4 -4
  113. data/lib/sequel/database/dataset_defaults.rb +63 -2
  114. data/lib/sequel/database/logging.rb +7 -2
  115. data/lib/sequel/database/misc.rb +190 -46
  116. data/lib/sequel/database/query.rb +272 -110
  117. data/lib/sequel/database/schema_generator.rb +103 -36
  118. data/lib/sequel/database/schema_methods.rb +300 -67
  119. data/lib/sequel/dataset/actions.rb +344 -90
  120. data/lib/sequel/dataset/features.rb +101 -8
  121. data/lib/sequel/dataset/graph.rb +42 -15
  122. data/lib/sequel/dataset/misc.rb +52 -25
  123. data/lib/sequel/dataset/mutation.rb +12 -7
  124. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  125. data/lib/sequel/dataset/query.rb +305 -54
  126. data/lib/sequel/dataset/sql.rb +720 -292
  127. data/lib/sequel/dataset.rb +8 -0
  128. data/lib/sequel/exceptions.rb +4 -0
  129. data/lib/sequel/extensions/_pretty_table.rb +83 -0
  130. data/lib/sequel/extensions/arbitrary_servers.rb +109 -0
  131. data/lib/sequel/extensions/blank.rb +4 -0
  132. data/lib/sequel/extensions/columns_introspection.rb +72 -0
  133. data/lib/sequel/extensions/constraint_validations.rb +451 -0
  134. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +30 -46
  135. data/lib/sequel/extensions/eval_inspect.rb +173 -0
  136. data/lib/sequel/extensions/inflector.rb +4 -0
  137. data/lib/sequel/extensions/looser_typecasting.rb +5 -4
  138. data/lib/sequel/extensions/migration.rb +100 -11
  139. data/lib/sequel/extensions/named_timezones.rb +9 -0
  140. data/lib/sequel/extensions/null_dataset.rb +94 -0
  141. data/lib/sequel/extensions/pagination.rb +4 -0
  142. data/lib/sequel/extensions/pg_array.rb +537 -0
  143. data/lib/sequel/extensions/pg_array_ops.rb +263 -0
  144. data/lib/sequel/extensions/pg_auto_parameterize.rb +175 -0
  145. data/lib/sequel/extensions/pg_hstore.rb +313 -0
  146. data/lib/sequel/extensions/pg_hstore_ops.rb +293 -0
  147. data/lib/sequel/extensions/pg_inet.rb +113 -0
  148. data/lib/sequel/extensions/pg_interval.rb +191 -0
  149. data/lib/sequel/extensions/pg_json.rb +208 -0
  150. data/lib/sequel/extensions/pg_range.rb +512 -0
  151. data/lib/sequel/extensions/pg_range_ops.rb +150 -0
  152. data/lib/sequel/extensions/pg_row.rb +572 -0
  153. data/lib/sequel/extensions/pg_row_ops.rb +182 -0
  154. data/lib/sequel/extensions/pg_statement_cache.rb +317 -0
  155. data/lib/sequel/extensions/pretty_table.rb +17 -72
  156. data/lib/sequel/extensions/query.rb +8 -4
  157. data/lib/sequel/extensions/query_literals.rb +79 -0
  158. data/lib/sequel/extensions/schema_caching.rb +76 -0
  159. data/lib/sequel/extensions/schema_dumper.rb +282 -76
  160. data/lib/sequel/extensions/select_remove.rb +39 -0
  161. data/lib/sequel/extensions/server_block.rb +140 -0
  162. data/lib/sequel/extensions/split_array_nil.rb +65 -0
  163. data/lib/sequel/extensions/sql_expr.rb +8 -110
  164. data/lib/sequel/extensions/string_date_time.rb +4 -0
  165. data/lib/sequel/extensions/thread_local_timezones.rb +10 -4
  166. data/lib/sequel/extensions/to_dot.rb +99 -83
  167. data/lib/sequel/model/associations.rb +1263 -409
  168. data/lib/sequel/model/base.rb +624 -171
  169. data/lib/sequel/model/errors.rb +1 -1
  170. data/lib/sequel/model/exceptions.rb +24 -2
  171. data/lib/sequel/model/inflections.rb +1 -1
  172. data/lib/sequel/model.rb +30 -11
  173. data/lib/sequel/no_core_ext.rb +2 -0
  174. data/lib/sequel/plugins/active_model.rb +13 -1
  175. data/lib/sequel/plugins/association_pks.rb +22 -4
  176. data/lib/sequel/plugins/caching.rb +25 -18
  177. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  178. data/lib/sequel/plugins/composition.rb +44 -12
  179. data/lib/sequel/plugins/constraint_validations.rb +198 -0
  180. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  181. data/lib/sequel/plugins/defaults_setter.rb +72 -0
  182. data/lib/sequel/plugins/dirty.rb +214 -0
  183. data/lib/sequel/plugins/eager_each.rb +59 -0
  184. data/lib/sequel/plugins/force_encoding.rb +6 -6
  185. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  186. data/lib/sequel/plugins/identity_map.rb +134 -15
  187. data/lib/sequel/plugins/instance_filters.rb +10 -0
  188. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  189. data/lib/sequel/plugins/json_serializer.rb +58 -6
  190. data/lib/sequel/plugins/list.rb +13 -2
  191. data/lib/sequel/plugins/many_through_many.rb +103 -51
  192. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  193. data/lib/sequel/plugins/nested_attributes.rb +150 -66
  194. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  195. data/lib/sequel/plugins/pg_row.rb +121 -0
  196. data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
  197. data/lib/sequel/plugins/prepared_statements.rb +167 -0
  198. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  199. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  200. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  201. data/lib/sequel/plugins/rcte_tree.rb +31 -17
  202. data/lib/sequel/plugins/schema.rb +8 -3
  203. data/lib/sequel/plugins/serialization.rb +98 -49
  204. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  205. data/lib/sequel/plugins/sharding.rb +21 -54
  206. data/lib/sequel/plugins/single_table_inheritance.rb +5 -3
  207. data/lib/sequel/plugins/static_cache.rb +99 -0
  208. data/lib/sequel/plugins/subclasses.rb +29 -3
  209. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  210. data/lib/sequel/plugins/timestamps.rb +1 -1
  211. data/lib/sequel/plugins/tree.rb +3 -3
  212. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  213. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  214. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  215. data/lib/sequel/plugins/validation_helpers.rb +55 -4
  216. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  217. data/lib/sequel/sql.rb +649 -122
  218. data/lib/sequel/timezones.rb +67 -40
  219. data/lib/sequel/version.rb +1 -1
  220. data/spec/adapters/db2_spec.rb +146 -0
  221. data/spec/adapters/firebird_spec.rb +1 -1
  222. data/spec/adapters/mssql_spec.rb +194 -66
  223. data/spec/adapters/mysql_spec.rb +475 -306
  224. data/spec/adapters/oracle_spec.rb +92 -117
  225. data/spec/adapters/postgres_spec.rb +1982 -325
  226. data/spec/adapters/spec_helper.rb +9 -6
  227. data/spec/adapters/sqlite_spec.rb +203 -75
  228. data/spec/core/connection_pool_spec.rb +218 -93
  229. data/spec/core/database_spec.rb +956 -522
  230. data/spec/core/dataset_spec.rb +1631 -1242
  231. data/spec/core/expression_filters_spec.rb +690 -334
  232. data/spec/core/mock_adapter_spec.rb +453 -0
  233. data/spec/core/object_graph_spec.rb +88 -129
  234. data/spec/core/schema_generator_spec.rb +4 -4
  235. data/spec/core/schema_spec.rb +556 -53
  236. data/spec/core/spec_helper.rb +7 -48
  237. data/spec/core_extensions_spec.rb +626 -0
  238. data/spec/extensions/active_model_spec.rb +13 -0
  239. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  240. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  241. data/spec/extensions/association_dependencies_spec.rb +15 -25
  242. data/spec/extensions/association_pks_spec.rb +66 -32
  243. data/spec/extensions/association_proxies_spec.rb +4 -4
  244. data/spec/extensions/boolean_readers_spec.rb +25 -25
  245. data/spec/extensions/caching_spec.rb +47 -51
  246. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  247. data/spec/extensions/columns_introspection_spec.rb +91 -0
  248. data/spec/extensions/composition_spec.rb +18 -13
  249. data/spec/extensions/constraint_validations_plugin_spec.rb +196 -0
  250. data/spec/extensions/constraint_validations_spec.rb +316 -0
  251. data/spec/extensions/dataset_associations_spec.rb +199 -0
  252. data/spec/extensions/defaults_setter_spec.rb +88 -0
  253. data/spec/extensions/dirty_spec.rb +155 -0
  254. data/spec/extensions/eager_each_spec.rb +34 -0
  255. data/spec/extensions/eval_inspect_spec.rb +67 -0
  256. data/spec/extensions/force_encoding_spec.rb +4 -2
  257. data/spec/extensions/hook_class_methods_spec.rb +97 -128
  258. data/spec/extensions/identity_map_spec.rb +142 -24
  259. data/spec/extensions/inflector_spec.rb +0 -4
  260. data/spec/extensions/instance_filters_spec.rb +11 -21
  261. data/spec/extensions/instance_hooks_spec.rb +72 -0
  262. data/spec/extensions/json_serializer_spec.rb +49 -12
  263. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  264. data/spec/extensions/list_spec.rb +49 -40
  265. data/spec/extensions/looser_typecasting_spec.rb +7 -7
  266. data/spec/extensions/many_through_many_spec.rb +368 -254
  267. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  268. data/spec/extensions/migration_spec.rb +140 -35
  269. data/spec/extensions/named_timezones_spec.rb +29 -11
  270. data/spec/extensions/nested_attributes_spec.rb +268 -89
  271. data/spec/extensions/null_dataset_spec.rb +85 -0
  272. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  273. data/spec/extensions/pg_array_ops_spec.rb +112 -0
  274. data/spec/extensions/pg_array_spec.rb +320 -0
  275. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  276. data/spec/extensions/pg_hstore_ops_spec.rb +140 -0
  277. data/spec/extensions/pg_hstore_spec.rb +194 -0
  278. data/spec/extensions/pg_inet_spec.rb +47 -0
  279. data/spec/extensions/pg_interval_spec.rb +72 -0
  280. data/spec/extensions/pg_json_spec.rb +99 -0
  281. data/spec/extensions/pg_range_ops_spec.rb +56 -0
  282. data/spec/extensions/pg_range_spec.rb +395 -0
  283. data/spec/extensions/pg_row_ops_spec.rb +58 -0
  284. data/spec/extensions/pg_row_plugin_spec.rb +49 -0
  285. data/spec/extensions/pg_row_spec.rb +323 -0
  286. data/spec/extensions/pg_statement_cache_spec.rb +208 -0
  287. data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
  288. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  289. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  290. data/spec/extensions/prepared_statements_spec.rb +87 -0
  291. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  292. data/spec/extensions/pretty_table_spec.rb +7 -6
  293. data/spec/extensions/query_literals_spec.rb +167 -0
  294. data/spec/extensions/query_spec.rb +9 -3
  295. data/spec/extensions/rcte_tree_spec.rb +50 -43
  296. data/spec/extensions/schema_caching_spec.rb +41 -0
  297. data/spec/extensions/schema_dumper_spec.rb +434 -49
  298. data/spec/extensions/schema_spec.rb +30 -49
  299. data/spec/extensions/select_remove_spec.rb +38 -0
  300. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  301. data/spec/extensions/serialization_spec.rb +86 -48
  302. data/spec/extensions/server_block_spec.rb +90 -0
  303. data/spec/extensions/sharding_spec.rb +69 -143
  304. data/spec/extensions/single_table_inheritance_spec.rb +23 -29
  305. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  306. data/spec/extensions/spec_helper.rb +34 -67
  307. data/spec/extensions/split_array_nil_spec.rb +24 -0
  308. data/spec/extensions/sql_expr_spec.rb +29 -60
  309. data/spec/extensions/static_cache_spec.rb +145 -0
  310. data/spec/extensions/subclasses_spec.rb +14 -0
  311. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  312. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  313. data/spec/extensions/timestamps_spec.rb +6 -6
  314. data/spec/extensions/to_dot_spec.rb +8 -11
  315. data/spec/extensions/touch_spec.rb +13 -14
  316. data/spec/extensions/tree_spec.rb +11 -26
  317. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  318. data/spec/extensions/update_primary_key_spec.rb +30 -24
  319. data/spec/extensions/validation_class_methods_spec.rb +58 -67
  320. data/spec/extensions/validation_helpers_spec.rb +31 -37
  321. data/spec/extensions/xml_serializer_spec.rb +29 -4
  322. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  323. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  324. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  325. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  326. data/spec/integration/associations_test.rb +659 -21
  327. data/spec/integration/database_test.rb +11 -5
  328. data/spec/integration/dataset_test.rb +717 -129
  329. data/spec/integration/eager_loader_test.rb +38 -54
  330. data/spec/integration/migrator_test.rb +2 -3
  331. data/spec/integration/model_test.rb +81 -2
  332. data/spec/integration/plugin_test.rb +604 -147
  333. data/spec/integration/prepared_statement_test.rb +272 -128
  334. data/spec/integration/schema_test.rb +367 -57
  335. data/spec/integration/spec_helper.rb +42 -32
  336. data/spec/integration/timezone_test.rb +38 -12
  337. data/spec/integration/transaction_test.rb +183 -8
  338. data/spec/integration/type_test.rb +31 -8
  339. data/spec/model/association_reflection_spec.rb +207 -8
  340. data/spec/model/associations_spec.rb +1206 -693
  341. data/spec/model/base_spec.rb +334 -100
  342. data/spec/model/dataset_methods_spec.rb +45 -27
  343. data/spec/model/eager_loading_spec.rb +828 -692
  344. data/spec/model/hooks_spec.rb +355 -67
  345. data/spec/model/model_spec.rb +251 -179
  346. data/spec/model/plugins_spec.rb +24 -13
  347. data/spec/model/record_spec.rb +506 -232
  348. data/spec/model/spec_helper.rb +16 -77
  349. data/spec/model/validations_spec.rb +38 -2
  350. data/spec/rcov.opts +2 -0
  351. metadata +209 -80
  352. data/spec/core/core_sql_spec.rb +0 -451
@@ -0,0 +1,100 @@
1
+ module Sequel
2
+ module Plugins
3
+ # DatasetAssociations allows you to easily use your model associations
4
+ # via datasets. For each association you define, it creates a dataset
5
+ # method for that association that returns a dataset of all objects
6
+ # that are associated to objects in the current dataset. Here's a simple
7
+ # example:
8
+ #
9
+ # class Artist < Sequel::Model
10
+ # plugin :dataset_associations
11
+ # one_to_many :albums
12
+ # end
13
+ # Artist.filter(id=>1..100).albums
14
+ # # SELECT * FROM albums
15
+ # # WHERE (albums.artist_id IN (
16
+ # # SELECT id FROM artists
17
+ # # WHERE ((id >= 1) AND (id <= 100))))
18
+ #
19
+ # This works for all of the association types that ship with Sequel,
20
+ # including the many_through_many type. Most association options that
21
+ # are supported when eager loading are supported when using a
22
+ # dataset association. However, associations that use :limit or
23
+ # one_to_one associations that are really one_to_many relationships
24
+ # in the database will not work correctly, returning all associated
25
+ # objects.
26
+ #
27
+ # As the dataset methods return datasets, you can easily chain the
28
+ # methods to get associated datasets of associated datasets:
29
+ #
30
+ # Artist.filter(id=>1..100).albums.filter{name < 'M'}.tags
31
+ # # SELECT tags.* FROM tags
32
+ # # WHERE (tags.id IN (
33
+ # # SELECT albums_tags.tag_id FROM albums
34
+ # # INNER JOIN albums_tags
35
+ # # ON (albums_tags.album_id = albums.id)
36
+ # # WHERE
37
+ # # ((albums.artist_id IN (
38
+ # # SELECT id FROM artists
39
+ # # WHERE ((id >= 1) AND (id <= 100)))
40
+ # # AND
41
+ # # (name < 'M')))))
42
+ #
43
+ # Usage:
44
+ #
45
+ # # Make all model subclasses create association methods for datasets
46
+ # Sequel::Model.plugin :dataset_associations
47
+ #
48
+ # # Make the Album class create association methods for datasets
49
+ # Album.plugin :dataset_associations
50
+ module DatasetAssociations
51
+ module ClassMethods
52
+ # Set up a dataset method for each association to return an associated dataset
53
+ def associate(type, name, *)
54
+ ret = super
55
+ r = association_reflection(name)
56
+ meth = r.returns_array? ? name : pluralize(name).to_sym
57
+ def_dataset_method(meth){associated(name)}
58
+ ret
59
+ end
60
+ end
61
+
62
+ module DatasetMethods
63
+ # For the association given by +name+, return a dataset of associated objects
64
+ # such that it would return the union of calling the association method on
65
+ # all objects returned by the current dataset.
66
+ #
67
+ # This supports most options that are supported when eager loading. It doesn't
68
+ # support limits on the associations, or one_to_one associations that are really
69
+ # one_to_many and use an order to select the first matching object. In both of
70
+ # those cases, this will return an array of all matching objects.
71
+ def associated(name)
72
+ raise Error, "unrecognized association name: #{name.inspect}" unless r = model.association_reflection(name)
73
+ ds = r.associated_class.dataset
74
+ sds = opts[:limit] ? self : unordered
75
+ ds = case r[:type]
76
+ when :many_to_one
77
+ ds.filter(r.qualified_primary_key=>sds.select(*Array(r[:qualified_key])))
78
+ when :one_to_one, :one_to_many
79
+ ds.filter(r.qualified_key=>sds.select(*Array(r.qualified_primary_key)))
80
+ when :many_to_many
81
+ ds.filter(r.qualified_right_primary_key=>sds.select(*Array(r.qualified_right_key)).
82
+ join(r[:join_table], r[:left_keys].zip(r[:left_primary_keys]), :implicit_qualifier=>model.table_name))
83
+ when :many_through_many
84
+ fre = r.reverse_edges.first
85
+ fe, *edges = r.edges
86
+ sds = sds.select(*Array(r.qualify(fre[:table], fre[:left]))).
87
+ join(fe[:table], Array(fe[:right]).zip(Array(fe[:left])), :implicit_qualifier=>model.table_name)
88
+ edges.each{|e| sds = sds.join(e[:table], Array(e[:right]).zip(Array(e[:left])))}
89
+ ds.filter(r.qualified_right_primary_key=>sds)
90
+ else
91
+ raise Error, "unrecognized association type for association #{name.inspect}: #{r[:type].inspect}"
92
+ end
93
+ ds = model.apply_association_dataset_opts(r, ds)
94
+ r[:extend].each{|m| ds.extend(m)}
95
+ ds
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,72 @@
1
+ module Sequel
2
+ module Plugins
3
+ # DefaultsSetter is a simple plugin that sets non-nil/NULL default values upon
4
+ # initialize:
5
+ #
6
+ # # column a default NULL
7
+ # # column b default 2
8
+ # album = Album.new.values # {:b => 2}
9
+ # album = Album.new(:a=>1, :b=>3).values # {:a => 1, :b => 3}
10
+ #
11
+ # Usage:
12
+ #
13
+ # # Make all model subclass instances set defaults (called before loading subclasses)
14
+ # Sequel::Model.plugin :defaults_setter
15
+ #
16
+ # # Make the Album class set defaults
17
+ # Album.plugin :defaults_setter
18
+ module DefaultsSetter
19
+ # Set the default values based on the model schema
20
+ def self.configure(model)
21
+ model.send(:set_default_values)
22
+ end
23
+
24
+ module ClassMethods
25
+ # The default values to set in initialize for this model. A hash with column symbol
26
+ # keys and default values. If the default values respond to +call+, it will be called
27
+ # to get the value, otherwise the value will be used directly. You can manually modify
28
+ # this hash to set specific default values, by default the ones will be parsed from the database.
29
+ attr_reader :default_values
30
+
31
+ # Set the default values when loading the dataset.
32
+ def set_dataset(*)
33
+ x = super
34
+ set_default_values
35
+ x
36
+ end
37
+
38
+ private
39
+
40
+ # Parse the cached database schema for this model and set the default values appropriately.
41
+ def set_default_values
42
+ h = {}
43
+ @db_schema.each{|k, v| h[k] = convert_default_value(v[:ruby_default]) unless v[:ruby_default].nil?} if @db_schema
44
+ @default_values = h
45
+ end
46
+
47
+ # Handle the CURRENT_DATE and CURRENT_TIMESTAMP values specially by returning an appropriate Date or
48
+ # Time/DateTime value.
49
+ def convert_default_value(v)
50
+ case v
51
+ when Sequel::CURRENT_DATE
52
+ lambda{Date.today}
53
+ when Sequel::CURRENT_TIMESTAMP
54
+ lambda{Sequel.datetime_class.now}
55
+ else
56
+ v
57
+ end
58
+ end
59
+ end
60
+
61
+ module InstanceMethods
62
+ private
63
+
64
+ # Set default values if they are not already set by the hash provided to initialize.
65
+ def initialize_set(h)
66
+ super
67
+ model.default_values.each{|k,v| self[k] = (v.respond_to?(:call) ? v.call : v) unless values.has_key?(k)}
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,214 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The dirty plugin makes Sequel save the initial value of
4
+ # a column when setting a new value for the column. This
5
+ # makes it easier to see what changes were made to the object:
6
+ #
7
+ # artist.name # => 'Foo'
8
+ # artist.name = 'Bar'
9
+ # artist.initial_value(:name) # 'Foo'
10
+ # artist.column_change(:name) # ['Foo', 'Bar']
11
+ # artist.column_changes # {:name => ['Foo', 'Bar']}
12
+ # artist.column_changed?(:name) # true
13
+ # artist.reset_column(:name)
14
+ # artist.name # => 'Foo'
15
+ # artist.column_changed?(:name) # false
16
+ #
17
+ # It also makes changed_columns more accurate in that it
18
+ # can detect when a the column value is changed and then
19
+ # changed back:
20
+ #
21
+ # artist.name # => 'Foo'
22
+ # artist.name = 'Bar'
23
+ # artist.changed_columns # => [:name]
24
+ # artist.name = 'Foo'
25
+ # artist.changed_columns # => []
26
+ #
27
+ # It can handle situations where a column value is
28
+ # modified in place:
29
+ #
30
+ # artist.will_change_column(:name)
31
+ # artist.name.gsub!(/o/, 'u')
32
+ # artist.changed_columns # => [:name]
33
+ # artist.initial_value(:name) # => 'Foo'
34
+ # artist.column_change(:name) # => ['Foo', 'Fuu']
35
+ #
36
+ # It also saves the previously changed values after an update:
37
+ #
38
+ # artist.update(:name=>'Bar')
39
+ # artist.column_changes # => {}
40
+ # artist.previous_changes # => {:name=>['Foo', 'Bar']}
41
+ #
42
+ # Usage:
43
+ #
44
+ # # Make all model subclass instances record previous values (called before loading subclasses)
45
+ # Sequel::Model.plugin :dirty
46
+ #
47
+ # # Make the Album class record previous values
48
+ # Album.plugin :dirty
49
+ module Dirty
50
+ module InstanceMethods
51
+ # A hash of previous changes before the object was
52
+ # saved, in the same format as #column_changes.
53
+ # Note that this is not necessarily the same as the columns
54
+ # that were used in the update statement.
55
+ attr_reader :previous_changes
56
+
57
+ # An array with the initial value and the current value
58
+ # of the column, if the column has been changed. If the
59
+ # column has not been changed, returns nil.
60
+ #
61
+ # column_change(:name) # => ['Initial', 'Current']
62
+ def column_change(column)
63
+ [initial_value(column), send(column)] if column_changed?(column)
64
+ end
65
+
66
+ # A hash with column symbol keys and pairs of initial and
67
+ # current values for all changed columns.
68
+ #
69
+ # column_changes # => {:name => ['Initial', 'Current']}
70
+ def column_changes
71
+ h = {}
72
+ initial_values.each do |column, value|
73
+ h[column] = [value, send(column)]
74
+ end
75
+ h
76
+ end
77
+
78
+ # Either true or false depending on whether the column has
79
+ # changed. Note that this is not exactly the same as checking if
80
+ # the column is in changed_columns, if the column was not set
81
+ # initially.
82
+ #
83
+ # column_changed?(:name) # => true
84
+ def column_changed?(column)
85
+ initial_values.has_key?(column)
86
+ end
87
+
88
+ # The initial value of the given column. If the column value has
89
+ # not changed, this will be the same as the current value of the
90
+ # column.
91
+ #
92
+ # initial_value(:name) # => 'Initial'
93
+ def initial_value(column)
94
+ initial_values.fetch(column){send(column)}
95
+ end
96
+
97
+ # A hash with column symbol keys and initial values.
98
+ #
99
+ # initial_values # {:name => 'Initial'}
100
+ def initial_values
101
+ @initial_values ||= {}
102
+ end
103
+
104
+ # Reset the column to its initial value. If the column was not set
105
+ # initial, removes it from the values.
106
+ #
107
+ # reset_column(:name)
108
+ # name # => 'Initial'
109
+ def reset_column(column)
110
+ if initial_values.has_key?(column)
111
+ send(:"#{column}=", initial_values[column])
112
+ end
113
+ if missing_initial_values.include?(column)
114
+ values.delete(column)
115
+ end
116
+ end
117
+
118
+ # Manually specify that a column will change. This should only be used
119
+ # if you plan to modify a column value in place, which is not recommended.
120
+ #
121
+ # will_change_column(:name)
122
+ # name.gsub(/i/i, 'o')
123
+ # column_change(:name) # => ['Initial', 'onotoal']
124
+ def will_change_column(column)
125
+ changed_columns << column unless changed_columns.include?(column)
126
+ check_missing_initial_value(column)
127
+
128
+ value = if initial_values.has_key?(column)
129
+ initial_values[column]
130
+ else
131
+ send(column)
132
+ end
133
+
134
+ initial_values[column] = if value && value != true && value.respond_to?(:clone)
135
+ begin
136
+ value.clone
137
+ rescue TypeError
138
+ value
139
+ end
140
+ else
141
+ value
142
+ end
143
+ end
144
+
145
+ private
146
+
147
+ # Reset the initial values after saving.
148
+ def after_save
149
+ super
150
+ reset_initial_values
151
+ end
152
+
153
+ # Save the current changes so they are available after updating. This happens
154
+ # before after_save resets them.
155
+ def after_update
156
+ super
157
+ @previous_changes = column_changes
158
+ end
159
+
160
+ # Reset the initial values when refreshing.
161
+ def _refresh(dataset)
162
+ super
163
+ reset_initial_values
164
+ end
165
+
166
+ # When changing the column value, save the initial column value. If the column
167
+ # value is changed back to the initial value, update changed columns to remove
168
+ # the column.
169
+ def change_column_value(column, value)
170
+ if (iv = initial_values).has_key?(column)
171
+ initial = iv[column]
172
+ super
173
+ if value == initial
174
+ changed_columns.delete(column) unless missing_initial_values.include?(column)
175
+ iv.delete(column)
176
+ end
177
+ else
178
+ check_missing_initial_value(column)
179
+ iv[column] = send(column)
180
+ super
181
+ end
182
+ end
183
+
184
+ # If the values hash does not contain the column, make sure missing_initial_values
185
+ # does so that it doesn't get deleted from changed_columns if changed back,
186
+ # and so that resetting the column value can be handled correctly.
187
+ def check_missing_initial_value(column)
188
+ unless values.has_key?(column) || (miv = missing_initial_values).include?(column)
189
+ miv << column
190
+ end
191
+ end
192
+
193
+ # Reset the initial values when initializing.
194
+ def initialize_set(h)
195
+ super
196
+ reset_initial_values
197
+ end
198
+
199
+ # Array holding column symbols that were not present initially. This is necessary
200
+ # to differentiate between values that were not present and values that were
201
+ # present but equal to nil.
202
+ def missing_initial_values
203
+ @missing_initial_values ||= []
204
+ end
205
+
206
+ # Clear the data structures that store the initial values.
207
+ def reset_initial_values
208
+ @initial_values.clear if @initial_values
209
+ @missing_initial_values.clear if @missing_initial_values
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,59 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The eager_each plugin makes calling each on an eager loaded dataset do eager loading.
4
+ # By default, each does not work on an eager loaded dataset, because each iterates
5
+ # over rows of the dataset as they come in, and to eagerly load you need to have all
6
+ # values up front. With the default associations code, you must call #all on an eagerly
7
+ # loaded dataset, as calling #each on an #eager dataset skips the eager loading, and calling
8
+ # #each on an #eager_graph dataset makes it yield plain hashes with columns from all
9
+ # tables, instead of yielding the instances of the main model.
10
+ #
11
+ # This plugin makes #each call #all for eagerly loaded datasets. As #all usually calls
12
+ # #each, this is a bit of issue, but this plugin resolves the issue by cloning the dataset
13
+ # and setting a new flag in the cloned dataset, so that each can check with the flag to
14
+ # determine whether it should call all.
15
+ #
16
+ # Usage:
17
+ #
18
+ # # Make all model subclass instances eagerly load for each (called before loading subclasses)
19
+ # Sequel::Model.plugin :eager_each
20
+ #
21
+ # # Make the Album class eagerly load for each
22
+ # Album.plugin :eager_each
23
+ module EagerEach
24
+ # Methods added to eagerly loaded datasets when the eager_each plugin is in use.
25
+ module EagerDatasetMethods
26
+ # Call #all instead of #each unless #each is being called by #all.
27
+ def each(&block)
28
+ if opts[:all_called]
29
+ super
30
+ else
31
+ all(&block)
32
+ end
33
+ end
34
+
35
+ # Clone the dataset and set a flag to let #each know not to call #all,
36
+ # to avoid the infinite loop.
37
+ def all(&block)
38
+ if opts[:all_called]
39
+ super
40
+ else
41
+ clone(:all_called=>true).all(&block)
42
+ end
43
+ end
44
+ end
45
+
46
+ module DatasetMethods
47
+ # Make sure calling each on this dataset will eagerly load the dataset.
48
+ def eager(*)
49
+ super.extend(EagerDatasetMethods)
50
+ end
51
+
52
+ # Make sure calling each on this dataset will eagerly load the dataset.
53
+ def eager_graph(*)
54
+ super.extend(EagerDatasetMethods)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -8,7 +8,7 @@ module Sequel
8
8
  # attribute, the resulting value is forced to a given encoding if the
9
9
  # value is a string. There are two ways to specify the encoding. You
10
10
  # can either do so in the plugin call itself, or via the
11
- # forced_encoding class accessor:
11
+ # forced_encoding class accessor.
12
12
  #
13
13
  # Usage:
14
14
  #
@@ -46,6 +46,11 @@ module Sequel
46
46
  super(force_hash_encoding(row))
47
47
  end
48
48
 
49
+ # Force the encoding of all string values when setting the instance's values.
50
+ def set_values(row)
51
+ super(force_hash_encoding(row))
52
+ end
53
+
49
54
  private
50
55
 
51
56
  # Force the encoding for all string values in the given row hash.
@@ -55,11 +60,6 @@ module Sequel
55
60
  row
56
61
  end
57
62
 
58
- # Force the encoding of all string values when setting the instance's values.
59
- def set_values(row)
60
- super(force_hash_encoding(row))
61
- end
62
-
63
63
  # Force the encoding of all returned strings to the model's forced_encoding.
64
64
  def typecast_value(column, value)
65
65
  s = super
@@ -90,9 +90,9 @@ module Sequel
90
90
 
91
91
  # Make a copy of the current class's hooks for the subclass.
92
92
  def inherited(subclass)
93
- super
94
93
  hooks = subclass.instance_variable_set(:@hooks, {})
95
94
  instance_variable_get(:@hooks).each{|k,v| hooks[k] = v.dup}
95
+ super
96
96
  end
97
97
 
98
98
  private
@@ -22,10 +22,7 @@ module Sequel
22
22
  # Identity maps are thread-local and only persist for the duration of the block,
23
23
  # so they should only be considered as a possible performance enhancer.
24
24
  #
25
- # The identity_map plugin is not compatible with the standard eager loading of
26
- # many_to_many and many_through_many associations. If you want to use the identity_map plugin,
27
- # you should use +eager_graph+ instead of +eager+ for those associations. It is also
28
- # not compatible with the eager loading in the +rcte_tree+ plugin.
25
+ # The identity_map plugin is not compatible with the eager loading in the +rcte_tree+ plugin.
29
26
  #
30
27
  # Usage:
31
28
  #
@@ -37,6 +34,116 @@ module Sequel
37
34
  # # would need to do Album.with_identity_map{} to use the identity map
38
35
  module IdentityMap
39
36
  module ClassMethods
37
+ # Override the default :eager_loader option for many_*_many associations to work
38
+ # with an identity_map. If the :eager_graph association option is used, you'll probably have to use
39
+ # :uniq=>true on the current association amd :cartesian_product_number=>2 on the association
40
+ # mentioned by :eager_graph, otherwise you'll end up with duplicates because the row proc will be
41
+ # getting called multiple times for the same object. If you do have duplicates and you use :eager_graph,
42
+ # they'll probably be lost. Making that work correctly would require changing a lot of the core
43
+ # architecture, such as how graphing and eager graphing work.
44
+ def associate(type, name, opts = {}, &block)
45
+ if opts[:eager_loader]
46
+ super
47
+ elsif type == :many_to_many
48
+ opts = super
49
+ el = opts[:eager_loader]
50
+ model = self
51
+ left_pk = opts[:left_primary_key]
52
+ uses_lcks = opts[:uses_left_composite_keys]
53
+ uses_rcks = opts[:uses_right_composite_keys]
54
+ right = opts[:right_key]
55
+ join_table = opts[:join_table]
56
+ left = opts[:left_key]
57
+ lcks = opts[:left_keys]
58
+ left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
59
+ opts[:eager_loader] = lambda do |eo|
60
+ return el.call(eo) unless model.identity_map
61
+ h = eo[:id_map]
62
+ eo[:rows].each{|object| object.associations[name] = []}
63
+ r = uses_rcks ? rcks.zip(opts.right_primary_keys) : [[right, opts.right_primary_key]]
64
+ l = uses_lcks ? [[lcks.map{|k| SQL::QualifiedIdentifier.new(join_table, k)}, h.keys]] : [[left, h.keys]]
65
+
66
+ # Replace the row proc to remove the left key alias before calling the previous row proc.
67
+ # Associate the value of the left key alias with the associated object (through its object_id).
68
+ # When loading the associated objects, lookup the left key alias value and associate the
69
+ # associated objects to the main objects if the left key alias value matches the left primary key
70
+ # value of the main object.
71
+ #
72
+ # The deleting of the left key alias from the hash before calling the previous row proc
73
+ # is necessary when an identity map is used, otherwise if the same associated object is returned more than
74
+ # once for the association, it won't know which of current objects to associate it to.
75
+ ds = opts.associated_class.inner_join(join_table, r + l)
76
+ pr = ds.row_proc
77
+ h2 = {}
78
+ ds.row_proc = proc do |hash|
79
+ hash_key = if uses_lcks
80
+ left_key_alias.map{|k| hash.delete(k)}
81
+ else
82
+ hash.delete(left_key_alias)
83
+ end
84
+ obj = pr.call(hash)
85
+ (h2[obj.object_id] ||= []) << hash_key
86
+ obj
87
+ end
88
+ model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo) .all do |assoc_record|
89
+ if hash_keys = h2.delete(assoc_record.object_id)
90
+ hash_keys.each do |hash_key|
91
+ if objects = h[hash_key]
92
+ objects.each{|object| object.associations[name].push(assoc_record)}
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ opts
99
+ elsif type == :many_through_many
100
+ opts = super
101
+ el = opts[:eager_loader]
102
+ model = self
103
+ left_pk = opts[:left_primary_key]
104
+ left_key = opts[:left_key]
105
+ uses_lcks = opts[:uses_left_composite_keys]
106
+ left_keys = Array(left_key)
107
+ left_key_alias = opts[:left_key_alias]
108
+ opts[:eager_loader] = lambda do |eo|
109
+ return el.call(eo) unless model.identity_map
110
+ h = eo[:id_map]
111
+ eo[:rows].each{|object| object.associations[name] = []}
112
+ ds = opts.associated_class
113
+ opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
114
+ ft = opts.final_reverse_edge
115
+ conds = uses_lcks ? [[left_keys.map{|k| SQL::QualifiedIdentifier.new(ft[:table], k)}, h.keys]] : [[left_key, h.keys]]
116
+
117
+ # See above comment in many_to_many eager_loader
118
+ ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + conds, :table_alias=>ft[:alias])
119
+ pr = ds.row_proc
120
+ h2 = {}
121
+ ds.row_proc = proc do |hash|
122
+ hash_key = if uses_lcks
123
+ left_key_alias.map{|k| hash.delete(k)}
124
+ else
125
+ hash.delete(left_key_alias)
126
+ end
127
+ obj = pr.call(hash)
128
+ (h2[obj.object_id] ||= []) << hash_key
129
+ obj
130
+ end
131
+ model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo).all do |assoc_record|
132
+ if hash_keys = h2.delete(assoc_record.object_id)
133
+ hash_keys.each do |hash_key|
134
+ if objects = h[hash_key]
135
+ objects.each{|object| object.associations[name].push(assoc_record)}
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ opts
142
+ else
143
+ super
144
+ end
145
+ end
146
+
40
147
  # Returns the current thread-local identity map. Should be a hash if
41
148
  # there is an active identity map, and nil otherwise.
42
149
  def identity_map
@@ -46,7 +153,8 @@ module Sequel
46
153
  # The identity map key for an object of the current class with the given pk.
47
154
  # May not always be correct for a class which uses STI.
48
155
  def identity_map_key(pk)
49
- "#{self}:#{pk ? Array(pk).join(',') : "nil:#{rand}"}"
156
+ pk = Array(pk)
157
+ "#{self}:#{pk.join(',')}" unless pk.compact.empty?
50
158
  end
51
159
 
52
160
  # If the identity map is in use, check it for a current copy of the object.
@@ -56,13 +164,15 @@ module Sequel
56
164
  # certain fields in an initial query, make modifications to some of those
57
165
  # fields and request other, potentially overlapping fields in a new query,
58
166
  # and not have the second query override fields you modified.
59
- def load(row)
60
- return super unless idm = identity_map
61
- if o = idm[identity_map_key(Array(primary_key).map{|x| row[x]})]
167
+ def call(row)
168
+ return super unless (idm = identity_map) && (pk = primary_key)
169
+ if (k = identity_map_key(Array(pk).map{|x| row[x]})) && (o = idm[k])
62
170
  o.merge_db_update(row)
63
171
  else
64
172
  o = super
65
- idm[identity_map_key(o.pk)] = o
173
+ if (k = identity_map_key(o.pk))
174
+ idm[k] = o
175
+ end
66
176
  end
67
177
  o
68
178
  end
@@ -89,7 +199,7 @@ module Sequel
89
199
  # Check the current identity map if it exists for the object with
90
200
  # the matching pk. If one is found, return it, otherwise call super.
91
201
  def primary_key_lookup(pk)
92
- (idm = identity_map and o = idm[identity_map_key(pk)]) ? o : super
202
+ ((idm = identity_map) && (k = identity_map_key(pk)) && (o = idm[k])) ? o : super
93
203
  end
94
204
  end
95
205
 
@@ -97,8 +207,8 @@ module Sequel
97
207
  # Remove instances from the identity map cache if they are deleted.
98
208
  def delete
99
209
  super
100
- if idm = model.identity_map
101
- idm.delete(model.identity_map_key(pk))
210
+ if (idm = model.identity_map) && (k = model.identity_map_key(pk))
211
+ idm.delete(k)
102
212
  end
103
213
  self
104
214
  end
@@ -121,10 +231,19 @@ module Sequel
121
231
  # key option has a value and the association uses the primary key of
122
232
  # the associated class as the :primary_key option, check the identity
123
233
  # map for the associated object and return it if present.
124
- def _load_associated_objects(opts)
234
+ def _load_associated_object(opts, dynamic_opts)
125
235
  klass = opts.associated_class
126
- if klass.respond_to?(:identity_map) && idm = klass.identity_map and opts[:type] == :many_to_one and opts[:primary_key] == klass.primary_key and
127
- opts[:key] and pk = _associated_object_pk(opts[:key]) and o = idm[klass.identity_map_key(pk)]
236
+ cache_lookup = opts.fetch(:idm_cache_lookup) do
237
+ opts[:idm_cache_lookup] = klass.respond_to?(:identity_map) &&
238
+ opts[:type] == :many_to_one &&
239
+ opts[:key] &&
240
+ opts.primary_key == klass.primary_key
241
+ end
242
+ if cache_lookup &&
243
+ !dynamic_opts[:callback] &&
244
+ (idm = klass.identity_map) &&
245
+ (k = klass.identity_map_key(_associated_object_pk(opts[:key]))) &&
246
+ (o = idm[k])
128
247
  o
129
248
  else
130
249
  super