sequel 3.21.0 → 3.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. data/CHANGELOG +1131 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +106 -65
  4. data/Rakefile +59 -27
  5. data/bin/sequel +50 -26
  6. data/doc/active_record.rdoc +68 -52
  7. data/doc/advanced_associations.rdoc +201 -75
  8. data/doc/association_basics.rdoc +327 -79
  9. data/doc/bin_sequel.rdoc +144 -0
  10. data/doc/cheat_sheet.rdoc +21 -21
  11. data/doc/core_extensions.rdoc +374 -0
  12. data/doc/dataset_basics.rdoc +10 -10
  13. data/doc/dataset_filtering.rdoc +54 -42
  14. data/doc/mass_assignment.rdoc +56 -0
  15. data/doc/migration.rdoc +89 -557
  16. data/doc/model_hooks.rdoc +80 -29
  17. data/doc/object_model.rdoc +576 -0
  18. data/doc/opening_databases.rdoc +121 -53
  19. data/doc/prepared_statements.rdoc +41 -15
  20. data/doc/querying.rdoc +137 -56
  21. data/doc/reflection.rdoc +36 -10
  22. data/doc/release_notes/3.22.0.txt +39 -0
  23. data/doc/release_notes/3.23.0.txt +172 -0
  24. data/doc/release_notes/3.24.0.txt +420 -0
  25. data/doc/release_notes/3.25.0.txt +88 -0
  26. data/doc/release_notes/3.26.0.txt +88 -0
  27. data/doc/release_notes/3.27.0.txt +82 -0
  28. data/doc/release_notes/3.28.0.txt +304 -0
  29. data/doc/release_notes/3.29.0.txt +459 -0
  30. data/doc/release_notes/3.30.0.txt +135 -0
  31. data/doc/release_notes/3.31.0.txt +146 -0
  32. data/doc/release_notes/3.32.0.txt +202 -0
  33. data/doc/release_notes/3.33.0.txt +157 -0
  34. data/doc/release_notes/3.34.0.txt +671 -0
  35. data/doc/release_notes/3.35.0.txt +144 -0
  36. data/doc/release_notes/3.36.0.txt +245 -0
  37. data/doc/release_notes/3.37.0.txt +338 -0
  38. data/doc/release_notes/3.38.0.txt +234 -0
  39. data/doc/release_notes/3.39.0.txt +237 -0
  40. data/doc/release_notes/3.40.0.txt +73 -0
  41. data/doc/release_notes/3.41.0.txt +155 -0
  42. data/doc/schema_modification.rdoc +585 -0
  43. data/doc/sharding.rdoc +99 -8
  44. data/doc/sql.rdoc +154 -112
  45. data/doc/testing.rdoc +169 -0
  46. data/doc/thread_safety.rdoc +17 -0
  47. data/doc/transactions.rdoc +137 -0
  48. data/doc/validations.rdoc +1 -1
  49. data/doc/virtual_rows.rdoc +78 -43
  50. data/lib/sequel/adapters/ado/access.rb +334 -0
  51. data/lib/sequel/adapters/ado/mssql.rb +18 -12
  52. data/lib/sequel/adapters/ado.rb +69 -21
  53. data/lib/sequel/adapters/amalgalite.rb +17 -14
  54. data/lib/sequel/adapters/cubrid.rb +143 -0
  55. data/lib/sequel/adapters/db2.rb +178 -85
  56. data/lib/sequel/adapters/dbi.rb +19 -21
  57. data/lib/sequel/adapters/do/mysql.rb +17 -11
  58. data/lib/sequel/adapters/do/postgres.rb +2 -61
  59. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  60. data/lib/sequel/adapters/do.rb +20 -39
  61. data/lib/sequel/adapters/firebird.rb +33 -210
  62. data/lib/sequel/adapters/ibmdb.rb +452 -0
  63. data/lib/sequel/adapters/informix.rb +6 -29
  64. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  65. data/lib/sequel/adapters/jdbc/cubrid.rb +52 -0
  66. data/lib/sequel/adapters/jdbc/db2.rb +78 -0
  67. data/lib/sequel/adapters/jdbc/derby.rb +325 -0
  68. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  69. data/lib/sequel/adapters/jdbc/h2.rb +68 -31
  70. data/lib/sequel/adapters/jdbc/hsqldb.rb +189 -0
  71. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  72. data/lib/sequel/adapters/jdbc/jtds.rb +40 -0
  73. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  74. data/lib/sequel/adapters/jdbc/mysql.rb +16 -28
  75. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  76. data/lib/sequel/adapters/jdbc/postgresql.rb +77 -52
  77. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  78. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  79. data/lib/sequel/adapters/jdbc/sqlserver.rb +72 -0
  80. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  81. data/lib/sequel/adapters/jdbc.rb +308 -114
  82. data/lib/sequel/adapters/mock.rb +372 -0
  83. data/lib/sequel/adapters/mysql.rb +130 -230
  84. data/lib/sequel/adapters/mysql2.rb +64 -63
  85. data/lib/sequel/adapters/odbc/mssql.rb +17 -9
  86. data/lib/sequel/adapters/odbc.rb +29 -10
  87. data/lib/sequel/adapters/openbase.rb +7 -11
  88. data/lib/sequel/adapters/oracle.rb +364 -57
  89. data/lib/sequel/adapters/postgres.rb +379 -145
  90. data/lib/sequel/adapters/shared/access.rb +236 -11
  91. data/lib/sequel/adapters/shared/cubrid.rb +216 -0
  92. data/lib/sequel/adapters/shared/db2.rb +353 -0
  93. data/lib/sequel/adapters/shared/firebird.rb +231 -0
  94. data/lib/sequel/adapters/shared/informix.rb +53 -0
  95. data/lib/sequel/adapters/shared/mssql.rb +412 -116
  96. data/lib/sequel/adapters/shared/mysql.rb +396 -89
  97. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +166 -0
  98. data/lib/sequel/adapters/shared/oracle.rb +262 -45
  99. data/lib/sequel/adapters/shared/postgres.rb +688 -279
  100. data/lib/sequel/adapters/shared/progress.rb +5 -8
  101. data/lib/sequel/adapters/shared/sqlite.rb +317 -96
  102. data/lib/sequel/adapters/sqlite.rb +109 -46
  103. data/lib/sequel/adapters/swift/mysql.rb +10 -12
  104. data/lib/sequel/adapters/swift/postgres.rb +13 -79
  105. data/lib/sequel/adapters/swift/sqlite.rb +9 -6
  106. data/lib/sequel/adapters/swift.rb +31 -27
  107. data/lib/sequel/adapters/tinytds.rb +177 -25
  108. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +77 -0
  109. data/lib/sequel/adapters/utils/pg_types.rb +81 -0
  110. data/lib/sequel/adapters/utils/split_alter_table.rb +36 -0
  111. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  112. data/lib/sequel/ast_transformer.rb +194 -0
  113. data/lib/sequel/connection_pool/sharded_single.rb +23 -13
  114. data/lib/sequel/connection_pool/sharded_threaded.rb +64 -13
  115. data/lib/sequel/connection_pool/single.rb +11 -3
  116. data/lib/sequel/connection_pool/threaded.rb +69 -20
  117. data/lib/sequel/connection_pool.rb +20 -18
  118. data/lib/sequel/core.rb +143 -11
  119. data/lib/sequel/database/connecting.rb +66 -29
  120. data/lib/sequel/database/dataset.rb +4 -4
  121. data/lib/sequel/database/dataset_defaults.rb +63 -2
  122. data/lib/sequel/database/logging.rb +7 -2
  123. data/lib/sequel/database/misc.rb +192 -51
  124. data/lib/sequel/database/query.rb +299 -114
  125. data/lib/sequel/database/schema_generator.rb +103 -36
  126. data/lib/sequel/database/schema_methods.rb +310 -69
  127. data/lib/sequel/dataset/actions.rb +344 -90
  128. data/lib/sequel/dataset/features.rb +101 -8
  129. data/lib/sequel/dataset/graph.rb +42 -15
  130. data/lib/sequel/dataset/misc.rb +52 -25
  131. data/lib/sequel/dataset/mutation.rb +12 -7
  132. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  133. data/lib/sequel/dataset/query.rb +322 -68
  134. data/lib/sequel/dataset/sql.rb +716 -293
  135. data/lib/sequel/dataset.rb +8 -0
  136. data/lib/sequel/exceptions.rb +4 -0
  137. data/lib/sequel/extensions/_pretty_table.rb +83 -0
  138. data/lib/sequel/extensions/arbitrary_servers.rb +109 -0
  139. data/lib/sequel/extensions/blank.rb +4 -0
  140. data/lib/sequel/extensions/columns_introspection.rb +72 -0
  141. data/lib/sequel/extensions/connection_validator.rb +109 -0
  142. data/lib/sequel/extensions/constraint_validations.rb +451 -0
  143. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +30 -46
  144. data/lib/sequel/extensions/eval_inspect.rb +173 -0
  145. data/lib/sequel/extensions/inflector.rb +4 -0
  146. data/lib/sequel/extensions/looser_typecasting.rb +5 -4
  147. data/lib/sequel/extensions/migration.rb +100 -11
  148. data/lib/sequel/extensions/named_timezones.rb +9 -0
  149. data/lib/sequel/extensions/null_dataset.rb +94 -0
  150. data/lib/sequel/extensions/pagination.rb +4 -0
  151. data/lib/sequel/extensions/pg_array.rb +539 -0
  152. data/lib/sequel/extensions/pg_array_ops.rb +263 -0
  153. data/lib/sequel/extensions/pg_auto_parameterize.rb +175 -0
  154. data/lib/sequel/extensions/pg_hstore.rb +334 -0
  155. data/lib/sequel/extensions/pg_hstore_ops.rb +293 -0
  156. data/lib/sequel/extensions/pg_inet.rb +113 -0
  157. data/lib/sequel/extensions/pg_interval.rb +191 -0
  158. data/lib/sequel/extensions/pg_json.rb +212 -0
  159. data/lib/sequel/extensions/pg_range.rb +513 -0
  160. data/lib/sequel/extensions/pg_range_ops.rb +150 -0
  161. data/lib/sequel/extensions/pg_row.rb +576 -0
  162. data/lib/sequel/extensions/pg_row_ops.rb +182 -0
  163. data/lib/sequel/extensions/pg_statement_cache.rb +317 -0
  164. data/lib/sequel/extensions/pretty_table.rb +17 -72
  165. data/lib/sequel/extensions/query.rb +8 -4
  166. data/lib/sequel/extensions/query_literals.rb +79 -0
  167. data/lib/sequel/extensions/schema_caching.rb +76 -0
  168. data/lib/sequel/extensions/schema_dumper.rb +282 -76
  169. data/lib/sequel/extensions/select_remove.rb +39 -0
  170. data/lib/sequel/extensions/server_block.rb +140 -0
  171. data/lib/sequel/extensions/split_array_nil.rb +65 -0
  172. data/lib/sequel/extensions/sql_expr.rb +8 -110
  173. data/lib/sequel/extensions/string_date_time.rb +4 -0
  174. data/lib/sequel/extensions/thread_local_timezones.rb +10 -4
  175. data/lib/sequel/extensions/to_dot.rb +99 -83
  176. data/lib/sequel/model/associations.rb +1263 -409
  177. data/lib/sequel/model/base.rb +624 -171
  178. data/lib/sequel/model/errors.rb +1 -1
  179. data/lib/sequel/model/exceptions.rb +24 -2
  180. data/lib/sequel/model/inflections.rb +1 -1
  181. data/lib/sequel/model.rb +30 -11
  182. data/lib/sequel/no_core_ext.rb +2 -0
  183. data/lib/sequel/plugins/active_model.rb +13 -1
  184. data/lib/sequel/plugins/association_pks.rb +137 -16
  185. data/lib/sequel/plugins/caching.rb +25 -18
  186. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  187. data/lib/sequel/plugins/composition.rb +44 -12
  188. data/lib/sequel/plugins/constraint_validations.rb +198 -0
  189. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  190. data/lib/sequel/plugins/defaults_setter.rb +72 -0
  191. data/lib/sequel/plugins/dirty.rb +214 -0
  192. data/lib/sequel/plugins/eager_each.rb +59 -0
  193. data/lib/sequel/plugins/force_encoding.rb +6 -6
  194. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  195. data/lib/sequel/plugins/identity_map.rb +134 -15
  196. data/lib/sequel/plugins/instance_filters.rb +10 -0
  197. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  198. data/lib/sequel/plugins/json_serializer.rb +77 -6
  199. data/lib/sequel/plugins/list.rb +13 -2
  200. data/lib/sequel/plugins/many_through_many.rb +103 -51
  201. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  202. data/lib/sequel/plugins/nested_attributes.rb +150 -66
  203. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  204. data/lib/sequel/plugins/pg_row.rb +121 -0
  205. data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
  206. data/lib/sequel/plugins/prepared_statements.rb +168 -0
  207. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  208. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  209. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  210. data/lib/sequel/plugins/rcte_tree.rb +31 -17
  211. data/lib/sequel/plugins/schema.rb +8 -3
  212. data/lib/sequel/plugins/serialization.rb +98 -49
  213. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  214. data/lib/sequel/plugins/sharding.rb +21 -54
  215. data/lib/sequel/plugins/single_table_inheritance.rb +57 -12
  216. data/lib/sequel/plugins/static_cache.rb +99 -0
  217. data/lib/sequel/plugins/subclasses.rb +29 -3
  218. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  219. data/lib/sequel/plugins/timestamps.rb +1 -1
  220. data/lib/sequel/plugins/touch.rb +18 -6
  221. data/lib/sequel/plugins/tree.rb +3 -3
  222. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  223. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  224. data/lib/sequel/plugins/validation_class_methods.rb +2 -1
  225. data/lib/sequel/plugins/validation_helpers.rb +57 -4
  226. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  227. data/lib/sequel/sql.rb +721 -141
  228. data/lib/sequel/timezones.rb +67 -40
  229. data/lib/sequel/version.rb +1 -1
  230. data/spec/adapters/db2_spec.rb +146 -0
  231. data/spec/adapters/firebird_spec.rb +53 -39
  232. data/spec/adapters/mssql_spec.rb +261 -66
  233. data/spec/adapters/mysql_spec.rb +597 -352
  234. data/spec/adapters/oracle_spec.rb +92 -117
  235. data/spec/adapters/postgres_spec.rb +2103 -341
  236. data/spec/adapters/spec_helper.rb +16 -6
  237. data/spec/adapters/sqlite_spec.rb +237 -76
  238. data/spec/core/connection_pool_spec.rb +270 -141
  239. data/spec/core/database_spec.rb +980 -524
  240. data/spec/core/dataset_spec.rb +1658 -1243
  241. data/spec/core/expression_filters_spec.rb +741 -334
  242. data/spec/core/mock_adapter_spec.rb +453 -0
  243. data/spec/core/object_graph_spec.rb +88 -129
  244. data/spec/core/schema_generator_spec.rb +4 -4
  245. data/spec/core/schema_spec.rb +556 -53
  246. data/spec/core/spec_helper.rb +7 -48
  247. data/spec/core_extensions_spec.rb +626 -0
  248. data/spec/extensions/active_model_spec.rb +13 -0
  249. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  250. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  251. data/spec/extensions/association_dependencies_spec.rb +15 -25
  252. data/spec/extensions/association_pks_spec.rb +227 -33
  253. data/spec/extensions/association_proxies_spec.rb +4 -4
  254. data/spec/extensions/boolean_readers_spec.rb +25 -25
  255. data/spec/extensions/caching_spec.rb +47 -51
  256. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  257. data/spec/extensions/columns_introspection_spec.rb +91 -0
  258. data/spec/extensions/composition_spec.rb +18 -13
  259. data/spec/extensions/connection_validator_spec.rb +118 -0
  260. data/spec/extensions/constraint_validations_plugin_spec.rb +196 -0
  261. data/spec/extensions/constraint_validations_spec.rb +316 -0
  262. data/spec/extensions/dataset_associations_spec.rb +199 -0
  263. data/spec/extensions/defaults_setter_spec.rb +88 -0
  264. data/spec/extensions/dirty_spec.rb +155 -0
  265. data/spec/extensions/eager_each_spec.rb +34 -0
  266. data/spec/extensions/eval_inspect_spec.rb +67 -0
  267. data/spec/extensions/force_encoding_spec.rb +4 -2
  268. data/spec/extensions/hook_class_methods_spec.rb +97 -128
  269. data/spec/extensions/identity_map_spec.rb +142 -24
  270. data/spec/extensions/inflector_spec.rb +0 -4
  271. data/spec/extensions/instance_filters_spec.rb +11 -21
  272. data/spec/extensions/instance_hooks_spec.rb +72 -0
  273. data/spec/extensions/json_serializer_spec.rb +49 -12
  274. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  275. data/spec/extensions/list_spec.rb +49 -40
  276. data/spec/extensions/looser_typecasting_spec.rb +7 -7
  277. data/spec/extensions/many_through_many_spec.rb +368 -254
  278. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  279. data/spec/extensions/migration_spec.rb +140 -35
  280. data/spec/extensions/named_timezones_spec.rb +29 -11
  281. data/spec/extensions/nested_attributes_spec.rb +268 -89
  282. data/spec/extensions/null_dataset_spec.rb +85 -0
  283. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  284. data/spec/extensions/pg_array_ops_spec.rb +112 -0
  285. data/spec/extensions/pg_array_spec.rb +324 -0
  286. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  287. data/spec/extensions/pg_hstore_ops_spec.rb +140 -0
  288. data/spec/extensions/pg_hstore_spec.rb +200 -0
  289. data/spec/extensions/pg_inet_spec.rb +47 -0
  290. data/spec/extensions/pg_interval_spec.rb +72 -0
  291. data/spec/extensions/pg_json_spec.rb +99 -0
  292. data/spec/extensions/pg_range_ops_spec.rb +56 -0
  293. data/spec/extensions/pg_range_spec.rb +395 -0
  294. data/spec/extensions/pg_row_ops_spec.rb +58 -0
  295. data/spec/extensions/pg_row_plugin_spec.rb +49 -0
  296. data/spec/extensions/pg_row_spec.rb +340 -0
  297. data/spec/extensions/pg_statement_cache_spec.rb +208 -0
  298. data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
  299. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  300. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  301. data/spec/extensions/prepared_statements_spec.rb +87 -0
  302. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  303. data/spec/extensions/pretty_table_spec.rb +7 -6
  304. data/spec/extensions/query_literals_spec.rb +167 -0
  305. data/spec/extensions/query_spec.rb +9 -3
  306. data/spec/extensions/rcte_tree_spec.rb +50 -43
  307. data/spec/extensions/schema_caching_spec.rb +41 -0
  308. data/spec/extensions/schema_dumper_spec.rb +434 -49
  309. data/spec/extensions/schema_spec.rb +30 -49
  310. data/spec/extensions/select_remove_spec.rb +38 -0
  311. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  312. data/spec/extensions/serialization_spec.rb +86 -48
  313. data/spec/extensions/server_block_spec.rb +90 -0
  314. data/spec/extensions/sharding_spec.rb +69 -143
  315. data/spec/extensions/single_table_inheritance_spec.rb +65 -29
  316. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  317. data/spec/extensions/spec_helper.rb +34 -67
  318. data/spec/extensions/split_array_nil_spec.rb +24 -0
  319. data/spec/extensions/sql_expr_spec.rb +29 -60
  320. data/spec/extensions/static_cache_spec.rb +145 -0
  321. data/spec/extensions/subclasses_spec.rb +14 -0
  322. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  323. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  324. data/spec/extensions/timestamps_spec.rb +6 -6
  325. data/spec/extensions/to_dot_spec.rb +8 -11
  326. data/spec/extensions/touch_spec.rb +53 -14
  327. data/spec/extensions/tree_spec.rb +11 -26
  328. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  329. data/spec/extensions/update_primary_key_spec.rb +30 -24
  330. data/spec/extensions/validation_class_methods_spec.rb +77 -68
  331. data/spec/extensions/validation_helpers_spec.rb +48 -37
  332. data/spec/extensions/xml_serializer_spec.rb +29 -4
  333. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  334. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  335. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  336. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  337. data/spec/integration/associations_test.rb +659 -21
  338. data/spec/integration/database_test.rb +25 -5
  339. data/spec/integration/dataset_test.rb +729 -141
  340. data/spec/integration/eager_loader_test.rb +38 -54
  341. data/spec/integration/migrator_test.rb +2 -3
  342. data/spec/integration/model_test.rb +81 -2
  343. data/spec/integration/plugin_test.rb +871 -160
  344. data/spec/integration/prepared_statement_test.rb +272 -128
  345. data/spec/integration/schema_test.rb +377 -57
  346. data/spec/integration/spec_helper.rb +49 -32
  347. data/spec/integration/timezone_test.rb +38 -12
  348. data/spec/integration/transaction_test.rb +183 -8
  349. data/spec/integration/type_test.rb +40 -8
  350. data/spec/model/association_reflection_spec.rb +207 -8
  351. data/spec/model/associations_spec.rb +1206 -693
  352. data/spec/model/base_spec.rb +334 -100
  353. data/spec/model/dataset_methods_spec.rb +45 -27
  354. data/spec/model/eager_loading_spec.rb +828 -692
  355. data/spec/model/hooks_spec.rb +355 -67
  356. data/spec/model/model_spec.rb +251 -179
  357. data/spec/model/plugins_spec.rb +24 -13
  358. data/spec/model/record_spec.rb +506 -232
  359. data/spec/model/spec_helper.rb +16 -77
  360. data/spec/model/validations_spec.rb +38 -2
  361. data/spec/rcov.opts +2 -0
  362. metadata +223 -81
  363. data/spec/core/core_sql_spec.rb +0 -451
@@ -11,8 +11,17 @@ module Sequel
11
11
  # set the @deserialized_values entry. This plugin adds a before_save hook
12
12
  # that serializes all @deserialized_values to @values.
13
13
  #
14
- # You can use either marshal, yaml, or json as the serialization format.
15
- # If you use yaml or json, you should require them by yourself.
14
+ # You can specify the serialization format as a pair of serializer/deserializer
15
+ # callable objects. You can also specify the serialization format as a single
16
+ # symbol, if such a symbol has a registered serializer/deserializer pair in the
17
+ # plugin. By default, the plugin registers the :marshal, :yaml, and :json
18
+ # serialization formats. To register your own serialization formats, use
19
+ # Sequel::Plugins::Serialization.register_format.
20
+ # If you use yaml or json format, you need to require the libraries, Sequel
21
+ # does not do the requiring for you.
22
+ #
23
+ # You can specify the columns to serialize when loading the plugin, or later
24
+ # using the serialize_attributes class method.
16
25
  #
17
26
  # Because of how this plugin works, it must be used inside each model class
18
27
  # that needs serialization, after any set_dataset method calls in that class.
@@ -22,56 +31,96 @@ module Sequel
22
31
  # == Example
23
32
  #
24
33
  # require 'sequel'
34
+ # # Require json, as the plugin doesn't require it for you.
25
35
  # require 'json'
36
+ #
37
+ # # Register custom serializer/deserializer pair
38
+ # Sequel::Plugins::Serialization.register_format(:reverse,
39
+ # lambda{|v| v.reverse},
40
+ # lambda{|v| v.reverse})
41
+ #
26
42
  # class User < Sequel::Model
43
+ # # Built-in format support when loading the plugin
27
44
  # plugin :serialization, :json, :permissions
28
- # # or
45
+ #
46
+ # # Built-in format support after loading the plugin using serialize_attributes
29
47
  # plugin :serialization
30
- # serialize_attributes :marshal, :permissions, :attributes
48
+ # serialize_attributes :marshal, :permissions
49
+ #
50
+ # # Use custom registered serialization format just like built-in format
51
+ # serialize_attributes :reverse, :password
52
+ #
53
+ # # Use a custom serializer/deserializer pair without registering
54
+ # serialize_attributes [lambda{|v| v.reverse}, lambda{|v| v.reverse}], :password
31
55
  # end
32
56
  # user = User.create
33
57
  # user.permissions = { :global => 'read-only' }
34
58
  # user.save
35
59
  module Serialization
60
+ # The default serializers supported by the serialization module.
61
+ # Use register_format to add serializers to this hash.
62
+ REGISTERED_FORMATS = {}
63
+
36
64
  # Set up the column readers to do deserialization and the column writers
37
65
  # to save the value in deserialized_values.
38
66
  def self.apply(model, *args)
39
- model.instance_eval{@serialization_map = {}}
67
+ model.instance_eval do
68
+ @deserialization_map = {}
69
+ @serialization_map = {}
70
+ end
40
71
  end
41
72
 
73
+ # Automatically call serialize_attributes with the format and columns unless
74
+ # no columns were provided.
42
75
  def self.configure(model, format=nil, *columns)
43
76
  model.serialize_attributes(format, *columns) unless columns.empty?
44
77
  end
45
78
 
79
+ # Register a serializer/deserializer pair with a format symbol, to allow
80
+ # models to pick this format by name. Both serializer and deserializer
81
+ # should be callable objects.
82
+ def self.register_format(format, serializer, deserializer)
83
+ REGISTERED_FORMATS[format] = [serializer, deserializer]
84
+ end
85
+ register_format(:marshal, lambda{|v| [Marshal.dump(v)].pack('m')}, lambda{|v| Marshal.load(v.unpack('m')[0]) rescue Marshal.load(v)})
86
+ register_format(:yaml, lambda{|v| v.to_yaml}, lambda{|v| YAML.load(v)})
87
+ register_format(:json, lambda{|v| v.to_json}, lambda{|v| JSON.parse(v)})
88
+
46
89
  module ClassMethods
47
- # A map of the serialized columns for this model. Keys are column
48
- # symbols, values are serialization formats (:marshal, :yaml, or :json).
90
+ # A hash with column name symbols and callable values, with the value
91
+ # called to deserialize the column.
92
+ attr_reader :deserialization_map
93
+
94
+ # A hash with column name symbols and callable values, with the value
95
+ # called to serialize the column.
49
96
  attr_reader :serialization_map
50
97
 
51
98
  # Module to store the serialized column accessor methods, so they can
52
99
  # call be overridden and call super to get the serialization behavior
53
100
  attr_accessor :serialization_module
54
101
 
55
- # Copy the serialization format and columns to serialize into the subclass.
102
+ # Copy the serialization_map and deserialization map into the subclass.
56
103
  def inherited(subclass)
57
104
  super
58
105
  sm = serialization_map.dup
59
- subclass.instance_eval{@serialization_map = sm}
60
- end
61
-
62
- # The first value in the serialization map. This is only for
63
- # backwards compatibility, use serialization_map in new code.
64
- def serialization_format
65
- serialization_map.values.first
106
+ dsm = deserialization_map.dup
107
+ subclass.instance_eval do
108
+ @deserialization_map = dsm
109
+ @serialization_map = sm
110
+ end
66
111
  end
67
112
 
68
113
  # Create instance level reader that deserializes column values on request,
69
- # and instance level writer that stores new deserialized value in deserialized
70
- # columns
114
+ # and instance level writer that stores new deserialized values.
71
115
  def serialize_attributes(format, *columns)
72
- raise(Error, "Unsupported serialization format (#{format}), should be :marshal, :yaml, or :json") unless [:marshal, :yaml, :json].include?(format)
116
+ if format.is_a?(Symbol)
117
+ unless format = REGISTERED_FORMATS[format]
118
+ raise(Error, "Unsupported serialization format: #{format} (valid formats: #{REGISTERED_FORMATS.keys.map{|k| k.inspect}.join})")
119
+ end
120
+ end
121
+ serializer, deserializer = format
73
122
  raise(Error, "No columns given. The serialization plugin requires you specify which columns to serialize") if columns.empty?
74
- define_serialized_attribute_accessor(format, *columns)
123
+ define_serialized_attribute_accessor(serializer, deserializer, *columns)
75
124
  end
76
125
 
77
126
  # The columns that will be serialized. This is only for
@@ -83,12 +132,13 @@ module Sequel
83
132
  private
84
133
 
85
134
  # Add serializated attribute acessor methods to the serialization_module
86
- def define_serialized_attribute_accessor(format, *columns)
135
+ def define_serialized_attribute_accessor(serializer, deserializer, *columns)
87
136
  m = self
88
137
  include(self.serialization_module ||= Module.new) unless serialization_module
89
138
  serialization_module.class_eval do
90
139
  columns.each do |column|
91
- m.serialization_map[column] = format
140
+ m.serialization_map[column] = serializer
141
+ m.deserialization_map[column] = deserializer
92
142
  define_method(column) do
93
143
  if deserialized_values.has_key?(column)
94
144
  deserialized_values[column]
@@ -110,52 +160,51 @@ module Sequel
110
160
  attr_reader :deserialized_values
111
161
 
112
162
  # Set @deserialized_values to the empty hash
113
- def initialize(*args, &block)
163
+ def initialize_set(values)
114
164
  @deserialized_values = {}
115
165
  super
116
166
  end
117
167
 
118
- # Serialize all deserialized values
168
+ # Serialize deserialized values before saving
119
169
  def before_save
120
- deserialized_values.each{|k,v| @values[k] = serialize_value(k, v)}
170
+ serialize_deserialized_values
121
171
  super
122
172
  end
123
173
 
124
- # Empty the deserialized values when refreshing.
125
- def refresh
126
- @deserialized_values = {}
174
+ # Initialization the deserialized values for objects retrieved from the database.
175
+ def set_values(*)
176
+ @deserialized_values ||= {}
127
177
  super
128
178
  end
129
179
 
130
180
  private
131
181
 
132
- # Deserialize the column from either marshal or yaml format
182
+ # Empty the deserialized values when refreshing.
183
+ def _refresh(*)
184
+ @deserialized_values = {}
185
+ super
186
+ end
187
+
188
+ # Deserialize the column value. Called when the model column accessor is called to
189
+ # return a deserialized value.
133
190
  def deserialize_value(column, v)
134
- return v if v.nil?
135
- case model.serialization_map[column]
136
- when :marshal
137
- Marshal.load(v.unpack('m')[0]) rescue Marshal.load(v)
138
- when :yaml
139
- YAML.load v if v
140
- when :json
141
- JSON.parse v if v
142
- else
143
- raise Error, "Bad serialization format (#{model.serialization_map[column].inspect}) for column #{column.inspect}"
191
+ unless v.nil?
192
+ raise Sequel::Error, "no entry in deserialization_map for #{column.inspect}" unless callable = model.deserialization_map[column]
193
+ callable.call(v)
144
194
  end
145
195
  end
146
196
 
147
- # Serialize the column to either marshal or yaml format
197
+ # Serialize all deserialized values
198
+ def serialize_deserialized_values
199
+ deserialized_values.each{|k,v| @values[k] = serialize_value(k, v)}
200
+ end
201
+
202
+ # Serialize the column value. Called before saving to ensure the serialized value
203
+ # is saved in the database.
148
204
  def serialize_value(column, v)
149
- return v if v.nil?
150
- case model.serialization_map[column]
151
- when :marshal
152
- [Marshal.dump(v)].pack('m')
153
- when :yaml
154
- v.to_yaml
155
- when :json
156
- v.to_json
157
- else
158
- raise Error, "Bad serialization format (#{model.serialization_map[column].inspect}) for column #{column.inspect}"
205
+ unless v.nil?
206
+ raise Sequel::Error, "no entry in serialization_map for #{column.inspect}" unless callable = model.serialization_map[column]
207
+ callable.call(v)
159
208
  end
160
209
  end
161
210
  end
@@ -0,0 +1,63 @@
1
+ module Sequel
2
+ module Plugins
3
+ # This plugin extends the serialization plugin and enables it to detect
4
+ # changes in serialized values by checking whether the current
5
+ # deserialized value is the same as the original deserialized value.
6
+ # The serialization plugin does not do such checks by default, as they
7
+ # often aren't needed and can hurt performance.
8
+ #
9
+ # Note that for this plugin to work correctly, the values you are
10
+ # serializing must roundtrip correctly (i.e. deserialize(serialize(value))
11
+ # should equal value). This is true in most cases, but not in all. For
12
+ # example, ruby symbols round trip through yaml, but not json (as they get
13
+ # turned into strings in json).
14
+ #
15
+ # == Example
16
+ #
17
+ # require 'sequel'
18
+ # require 'json'
19
+ # class User < Sequel::Model
20
+ # plugin :serialization, :json, :permissions
21
+ # plugin :serialization_modification_detection
22
+ # end
23
+ # user = User.create(:permissions => {})
24
+ # user.permissions[:global] = 'read-only'
25
+ # user.save_changes
26
+ module SerializationModificationDetection
27
+ # Load the serialization plugin automatically.
28
+ def self.apply(model)
29
+ model.plugin :serialization
30
+ end
31
+
32
+ module InstanceMethods
33
+ # Clear the cache of original deserialized values after saving so that it doesn't
34
+ # show the column is modified after saving.
35
+ def after_save
36
+ super
37
+ @original_deserialized_values = {}
38
+ end
39
+
40
+ # Detect which serialized columns have changed.
41
+ def changed_columns
42
+ cc = super
43
+ deserialized_values.each{|c, v| cc << c if !cc.include?(c) && original_deserialized_value(c) != v}
44
+ cc
45
+ end
46
+
47
+ private
48
+
49
+ # For new objects, serialize any existing deserialized values so that changes can
50
+ # be detected.
51
+ def initialize_set(values)
52
+ super
53
+ serialize_deserialized_values
54
+ end
55
+
56
+ # Return the original deserialized value of the column, caching it to improve performance.
57
+ def original_deserialized_value(column)
58
+ (@original_deserialized_values ||= {})[column] ||= deserialize_value(column, self[column])
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -1,13 +1,14 @@
1
1
  module Sequel
2
2
  module Plugins
3
- # The sharding plugin makes it easy to use Sequel's sharding features
4
- # with models. It lets you create model objects on specific shards,
5
- # and any models retrieved from specific shards are automatically
6
- # saved back to those shards. It also works with associations,
7
- # so that model objects retrieved from specific shards will only
8
- # return associated objects from that shard, and using the
9
- # add/remove/remove_all association methods will only affect
10
- # that shard.
3
+ # The sharding plugin augments Sequel's default model sharding support
4
+ # in the following ways:
5
+ #
6
+ # 1) It automatically sets model instances to be saved back to the
7
+ # shard they were retreived from.
8
+ # 2) It makes model associations use the same shard as the model
9
+ # object.
10
+ # 3) It adds a slightly nicer API for creating model instances on
11
+ # specific shards.
11
12
  #
12
13
  # Usage:
13
14
  #
@@ -40,15 +41,21 @@ module Sequel
40
41
  def new_using_server(s, values={}, &block)
41
42
  new(values, &block).set_server(s)
42
43
  end
43
- end
44
44
 
45
- module InstanceMethods
46
- # Set the shard that this object is tied to. Returns self.
47
- def set_server(s)
48
- @server = s
49
- self
45
+ private
46
+
47
+ # Set the server for each graphed dataset to the current server
48
+ # unless the graphed dataset already has a server set.
49
+ def eager_graph_dataset(opts, eager_options)
50
+ ds = super
51
+ if s = eager_options[:self].opts[:server]
52
+ ds = ds.server(s) unless ds.opts[:server]
53
+ end
54
+ ds
50
55
  end
56
+ end
51
57
 
58
+ module InstanceMethods
52
59
  # Set the server that this object is tied to, unless it has
53
60
  # already been set. Returns self.
54
61
  def set_server?(s)
@@ -56,11 +63,6 @@ module Sequel
56
63
  self
57
64
  end
58
65
 
59
- # Ensure that the instance dataset is tied to the correct shard.
60
- def this
61
- use_server(super)
62
- end
63
-
64
66
  private
65
67
 
66
68
  # Ensure that association datasets are tied to the correct shard.
@@ -68,21 +70,11 @@ module Sequel
68
70
  use_server(super)
69
71
  end
70
72
 
71
- # Ensure that the object is inserted into the correct shard.
72
- def _insert_dataset
73
- use_server(super)
74
- end
75
-
76
73
  # Ensure that the join table for many_to_many associations uses the correct shard.
77
74
  def _join_table_dataset(opts)
78
75
  use_server(super)
79
76
  end
80
77
 
81
- # Make sure to use the correct shard when using a transaction
82
- def checked_transaction(opts={}, &block)
83
- super(@server ? {:server=>@server}.merge(opts) : opts, &block)
84
- end
85
-
86
78
  # If creating the object by doing <tt>add_association</tt> for a
87
79
  # +many_to_many+ association, make sure the associated object is created on the
88
80
  # current object's shard, unless the passed object already has an assigned shard.
@@ -90,11 +82,6 @@ module Sequel
90
82
  o.set_server?(@server) if o.respond_to?(:set_server?)
91
83
  super
92
84
  end
93
-
94
- # Set the given dataset to use the current object's shard.
95
- def use_server(ds)
96
- @server ? ds.server(@server) : ds
97
- end
98
85
  end
99
86
 
100
87
  module DatasetMethods
@@ -109,26 +96,6 @@ module Sequel
109
96
  end
110
97
  ds
111
98
  end
112
-
113
- private
114
-
115
- # Set the shard of all retrieved objects to the shard of
116
- # the table's dataset, or the current shard if the table's
117
- # dataset does not have a shard.
118
- def graph_each
119
- ta = @opts[:graph][:table_aliases]
120
- s = @opts[:server]
121
- super do |r|
122
- r.each do |k, v|
123
- if ds = ta[k]
124
- dss = ds.opts[:server]
125
- end
126
- vs = dss || s
127
- v.set_server(vs) if vs && v.respond_to?(:set_server)
128
- end
129
- yield r
130
- end
131
- end
132
99
  end
133
100
  end
134
101
  end
@@ -35,6 +35,17 @@ module Sequel
35
35
  # Employee.plugin :single_table_inheritance, :type,
36
36
  # :model_map=>{'line staff'=>:Staff, 'supervisor'=>:Manager}
37
37
  #
38
+ # # By default the plugin sets the respective column value
39
+ # # when a new instance is created.
40
+ # Staff.create.type == 'line staff'
41
+ # Manager.create.type == 'supervisor'
42
+ #
43
+ # # You can customize this behavior with the :key_chooser option.
44
+ # # This is most useful when using a non-bijective mapping.
45
+ # Employee.plugin :single_table_inheritance, :type,
46
+ # :model_map=>{'line staff'=>:Staff, 'supervisor'=>:Manager},
47
+ # :key_chooser=>proc{|instance| instance.model.sti_key_map[instance.model.to_s].first || 'stranger' }
48
+ #
38
49
  # # Using custom procs, with :model_map taking column values
39
50
  # # and yielding either a class, string, symbol, or nil,
40
51
  # # and :key_map taking a class object and returning the column
@@ -43,6 +54,15 @@ module Sequel
43
54
  # :model_map=>proc{|v| v.reverse},
44
55
  # :key_map=>proc{|klass| klass.name.reverse}
45
56
  #
57
+ # # You can use the same class for multiple values.
58
+ # # This is mainly useful when the sti_key column contains multiple values
59
+ # # which are different but do not require different code.
60
+ # Employee.plugin :single_table_inheritance, :type,
61
+ # :model_map=>{'staff' => "Staff",
62
+ # 'manager => "Manager",
63
+ # 'overpayed staff => "Staff",
64
+ # 'underpayed staff' => "Staff"}
65
+ #
46
66
  # One minor issue to note is that if you specify the <tt>:key_map</tt>
47
67
  # option as a hash, instead of having it inferred from the <tt>:model_map</tt>,
48
68
  # you should only use class name strings as keys, you should not use symbols
@@ -57,20 +77,38 @@ module Sequel
57
77
  @sti_model_map = opts[:model_map] || lambda{|v| v if v && v != ''}
58
78
  @sti_key_map = if km = opts[:key_map]
59
79
  if km.is_a?(Hash)
60
- h = Hash.new{|h,k| h[k.to_s] unless k.is_a?(String)}
61
- h.merge!(km)
80
+ h = Hash.new do |h,k|
81
+ unless k.is_a?(String)
82
+ h[k.to_s]
83
+ else
84
+ []
85
+ end
86
+ end
87
+ km.each do |k,v|
88
+ h[k.to_s] = [ ] unless h.key?(k.to_s)
89
+ h[k.to_s].push( *Array(v) )
90
+ end
91
+ h
62
92
  else
63
93
  km
64
94
  end
65
95
  elsif sti_model_map.is_a?(Hash)
66
- h = Hash.new{|h,k| h[k.to_s] unless k.is_a?(String)}
96
+ h = Hash.new do |h,k|
97
+ unless k.is_a?(String)
98
+ h[k.to_s]
99
+ else
100
+ []
101
+ end
102
+ end
67
103
  sti_model_map.each do |k,v|
68
- h[v.to_s] = k
104
+ h[v.to_s] = [ ] unless h.key?(v.to_s)
105
+ h[v.to_s] << k
69
106
  end
70
107
  h
71
108
  else
72
109
  lambda{|klass| klass.name.to_s}
73
110
  end
111
+ @sti_key_chooser = opts[:key_chooser] || lambda{|inst| Array(inst.model.sti_key_map[inst.model]).last }
74
112
  dataset.row_proc = lambda{|r| model.sti_load(r)}
75
113
  end
76
114
  end
@@ -97,6 +135,10 @@ module Sequel
97
135
  # the value of the sti_key column to the appropriate class to use.
98
136
  attr_reader :sti_model_map
99
137
 
138
+ # A proc which returns the value to use for new instances.
139
+ # This defaults to a lookup in the key map.
140
+ attr_reader :sti_key_chooser
141
+
100
142
  # Copy the necessary attributes to the subclasses, and filter the
101
143
  # subclass's dataset based on the sti_kep_map entry for the class.
102
144
  def inherited(subclass)
@@ -105,31 +147,34 @@ module Sequel
105
147
  sd = sti_dataset
106
148
  skm = sti_key_map
107
149
  smm = sti_model_map
108
- key = skm[subclass]
150
+ skc = sti_key_chooser
151
+ key = Array(skm[subclass]).dup
109
152
  sti_subclass_added(key)
110
- ska = [key]
111
- subclass.set_dataset(sd.filter(SQL::QualifiedIdentifier.new(table_name, sk)=>ska), :inherited=>true)
153
+ rp = dataset.row_proc
154
+ subclass.set_dataset(sd.filter(SQL::QualifiedIdentifier.new(table_name, sk)=>key), :inherited=>true)
112
155
  subclass.instance_eval do
156
+ dataset.row_proc = rp
113
157
  @sti_key = sk
114
- @sti_key_array = ska
158
+ @sti_key_array = key
115
159
  @sti_dataset = sd
116
160
  @sti_key_map = skm
117
161
  @sti_model_map = smm
118
- @simple_table = nil
162
+ @sti_key_chooser = skc
163
+ self.simple_table = nil
119
164
  end
120
165
  end
121
166
 
122
167
  # Return an instance of the class specified by sti_key,
123
168
  # used by the row_proc.
124
169
  def sti_load(r)
125
- sti_class(sti_model_map[r[sti_key]]).load(r)
170
+ sti_class(sti_model_map[r[sti_key]]).call(r)
126
171
  end
127
172
 
128
173
  # Make sure that all subclasses of the parent class correctly include
129
174
  # keys for all of their descendant classes.
130
175
  def sti_subclass_added(key)
131
176
  if sti_key_array
132
- sti_key_array << key
177
+ Sequel.synchronize{sti_key_array.push(*Array(key))}
133
178
  superclass.sti_subclass_added(key)
134
179
  end
135
180
  end
@@ -157,7 +202,7 @@ module Sequel
157
202
  module InstanceMethods
158
203
  # Set the sti_key column based on the sti_key_map.
159
204
  def before_create
160
- send("#{model.sti_key}=", model.sti_key_map[model]) unless self[model.sti_key]
205
+ send("#{model.sti_key}=", model.sti_key_chooser.call(self)) unless self[model.sti_key]
161
206
  super
162
207
  end
163
208
  end
@@ -0,0 +1,99 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The static_cache plugin is designed for models that are not modified at all
4
+ # in production use cases, or at least where modifications to them would usually
5
+ # coincide with an application restart. When loaded into a model class, it
6
+ # retrieves all rows in the database and staticly caches a ruby array and hash
7
+ # keyed on primary key containing all of the model instances. All of these instances
8
+ # are frozen so they won't be modified unexpectedly.
9
+ #
10
+ # The caches this plugin creates are used for the following things:
11
+ #
12
+ # * Primary key lookups (e.g. Model[1])
13
+ # * Model.all calls
14
+ # * Model.each calls
15
+ # * Model.map calls without an argument
16
+ # * Model.to_hash calls without an argument
17
+ #
18
+ # Usage:
19
+ #
20
+ # # Cache the AlbumType class staticly
21
+ # AlbumType.plugin :static_cache
22
+ module StaticCache
23
+ # Populate the static caches when loading the plugin.
24
+ def self.configure(model)
25
+ model.send(:load_cache)
26
+ end
27
+
28
+ module ClassMethods
29
+ # A frozen ruby hash holding all of the model's frozen instances, keyed by frozen primary key.
30
+ attr_reader :cache
31
+
32
+ # An array of all of the model's frozen instances, without issuing a database
33
+ # query.
34
+ def all
35
+ @all.dup
36
+ end
37
+
38
+ # Return the frozen object with the given pk, or nil if no such object exists
39
+ # in the cache, without issuing a database query.
40
+ def cache_get_pk(pk)
41
+ cache[pk]
42
+ end
43
+
44
+ # Yield each of the model's frozen instances to the block, without issuing a database
45
+ # query.
46
+ def each(&block)
47
+ @all.each(&block)
48
+ end
49
+
50
+ # If no arguments are given, yield each of the model's frozen instances to the block.
51
+ # and return a new array, without issuing a database query. If any arguments are
52
+ # given, use the default Sequel behavior.
53
+ def map(*a)
54
+ if a.empty?
55
+ @all.map(&(Proc.new if block_given?))
56
+ else
57
+ super
58
+ end
59
+ end
60
+
61
+ # Reload the cache when the dataset changes.
62
+ def set_dataset(*)
63
+ s = super
64
+ load_cache
65
+ s
66
+ end
67
+
68
+ # If no arguments are given, yield an identity map for the model with frozen primary keys
69
+ # and instances, without issuing a database query. If any arguments are
70
+ # given, use the default Sequel behavior.
71
+ def to_hash(*a)
72
+ if a.empty?
73
+ cache.dup
74
+ else
75
+ super
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ # Return the frozen object with the given pk, or nil if no such object exists
82
+ # in the cache, without issuing a database query.
83
+ def primary_key_lookup(pk)
84
+ cache[pk]
85
+ end
86
+
87
+ # Reload the cache for this model by retrieving all of the instances in the dataset
88
+ # freezing them, and populating the cached array and hash.
89
+ def load_cache
90
+ a = dataset.all
91
+ h = {}
92
+ a.each{|o| h[o.pk.freeze] = o.freeze}
93
+ @all = a.freeze
94
+ @cache = h.freeze
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end