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,13 @@ module Sequel
11
11
  # the Model's dataset with the method of the same name with the given arguments.
12
12
  module ClassMethods
13
13
  # Which columns should be the only columns allowed in a call to a mass assignment method (e.g. set)
14
- # (default: not set, so all columns not otherwise restricted).
14
+ # (default: not set, so all columns not otherwise restricted are allowed).
15
15
  attr_reader :allowed_columns
16
+
17
+ # Whether to cache the anonymous models created by Sequel::Model(). This is
18
+ # required for reloading them correctly (avoiding the superclass mismatch). True
19
+ # by default for backwards compatibility.
20
+ attr_accessor :cache_anonymous_models
16
21
 
17
22
  # Array of modules that extend this model's dataset. Stored
18
23
  # so that if the model's dataset is changed, it will be extended
@@ -23,7 +28,15 @@ module Sequel
23
28
  # stored so when the dataset changes, methods defined with def_dataset_method
24
29
  # will be applied to the new dataset.
25
30
  attr_reader :dataset_methods
26
- #
31
+
32
+ # SQL string fragment used for faster DELETE statement creation when deleting/destroying
33
+ # model instances, or nil if the optimization should not be used. For internal use only.
34
+ attr_reader :fast_instance_delete_sql
35
+
36
+ # The dataset that instance datasets (#this) are based on. Generally a naked version of
37
+ # the model's dataset limited to one row. For internal use only.
38
+ attr_reader :instance_dataset
39
+
27
40
  # Array of plugin modules loaded by this class
28
41
  #
29
42
  # Sequel::Model.plugins
@@ -86,6 +99,11 @@ module Sequel
86
99
  # database to typecast the value correctly.
87
100
  attr_accessor :typecast_on_assignment
88
101
 
102
+ # Whether to enable the after_commit and after_rollback hooks when saving/destroying
103
+ # instances. On by default, can be turned off for performance reasons or when using
104
+ # prepared transactions (which aren't compatible with after commit/rollback).
105
+ attr_accessor :use_after_commit_rollback
106
+
89
107
  # Whether to use a transaction by default when saving/deleting records (default: true).
90
108
  # If you are sending database queries in before_* or after_* hooks, you shouldn't change
91
109
  # the default setting without a good reason.
@@ -102,8 +120,19 @@ module Sequel
102
120
  # Artist[:name=>'Bob'] # SELECT * FROM artists WHERE (name = 'Bob') LIMIT 1
103
121
  # # => #<Artist {:name=>'Bob', ...}>
104
122
  def [](*args)
105
- args = args.first if (args.size == 1)
106
- args.is_a?(Hash) ? dataset[args] : primary_key_lookup(args)
123
+ args = args.first if args.size <= 1
124
+ args.is_a?(Hash) ? dataset[args] : (primary_key_lookup(args) unless args.nil?)
125
+ end
126
+
127
+ # Initializes a model instance as an existing record. This constructor is
128
+ # used by Sequel to initialize model instances when fetching records.
129
+ # Requires that values be a hash where all keys are symbols. It
130
+ # probably should not be used by external code.
131
+ def call(values)
132
+ o = allocate
133
+ o.set_values(values)
134
+ o.after_initialize
135
+ o
107
136
  end
108
137
 
109
138
  # Clear the setter_methods cache
@@ -148,6 +177,36 @@ module Sequel
148
177
  def dataset=(ds)
149
178
  set_dataset(ds)
150
179
  end
180
+
181
+ # Extend the dataset with a module, similar to adding
182
+ # a plugin with the methods defined in DatasetMethods. If a block
183
+ # is given, an anonymous module is created and the module_evaled, otherwise
184
+ # the argument should be a module. Returns the module given or the anonymous
185
+ # module created.
186
+ #
187
+ # Artist.dataset_module Sequel::ColumnsIntrospection
188
+ #
189
+ # Artist.dataset_module do
190
+ # def foo
191
+ # :bar
192
+ # end
193
+ # end
194
+ # Artist.dataset.foo
195
+ # # => :bar
196
+ # Artist.foo
197
+ # # => :bar
198
+ def dataset_module(mod = nil)
199
+ if mod
200
+ raise Error, "can't provide both argument and block to Model.dataset_module" if block_given?
201
+ dataset_extend(mod)
202
+ mod
203
+ else
204
+ @dataset_module ||= Module.new
205
+ @dataset_module.module_eval(&Proc.new) if block_given?
206
+ dataset_extend(@dataset_module)
207
+ @dataset_module
208
+ end
209
+ end
151
210
 
152
211
  # Returns the database associated with the Model class.
153
212
  # If this model doesn't have a database associated with it,
@@ -161,8 +220,8 @@ module Sequel
161
220
  # end # COMMIT
162
221
  def db
163
222
  return @db if @db
164
- @db = self == Model ? DATABASES.first : superclass.db
165
- raise(Error, "No database associated with #{self}") unless @db
223
+ @db = self == Model ? Sequel.synchronize{DATABASES.first} : superclass.db
224
+ raise(Error, "No database associated with #{self}: have you called Sequel.connect or #{self}.db= ?") unless @db
166
225
  @db
167
226
  end
168
227
 
@@ -193,6 +252,16 @@ module Sequel
193
252
  @db_schema ||= get_db_schema
194
253
  end
195
254
 
255
+ # Create a column alias, where the column methods have one name, but the underlying storage uses a
256
+ # different name.
257
+ def def_column_alias(meth, column)
258
+ clear_setter_methods_cache
259
+ overridable_methods_module.module_eval do
260
+ define_method(meth){self[column]}
261
+ define_method("#{meth}="){|v| self[column] = v}
262
+ end
263
+ end
264
+
196
265
  # If a block is given, define a method on the dataset (if the model currently has an dataset) with the given argument name using
197
266
  # the given block. Also define a class method on the model that calls the
198
267
  # dataset method. Stores the method name and block so that it can be reapplied if the model's
@@ -217,9 +286,15 @@ module Sequel
217
286
  @dataset_methods[meth] = block
218
287
  dataset.meta_def(meth, &block) if @dataset
219
288
  end
220
- args.each{|arg| instance_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__) unless respond_to?(arg)}
289
+ args.each do |arg|
290
+ if arg.to_s =~ NORMAL_METHOD_NAME_REGEXP
291
+ instance_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__) unless respond_to?(arg, true)
292
+ else
293
+ def_model_dataset_method_block(arg)
294
+ end
295
+ end
221
296
  end
222
-
297
+
223
298
  # Finds a single record according to the supplied filter.
224
299
  # You are encouraged to use Model.[] or Model.first instead of this method.
225
300
  #
@@ -248,6 +323,13 @@ module Sequel
248
323
  find(cond) || create(cond, &block)
249
324
  end
250
325
 
326
+ # Clear the setter_methods cache when a module is included, as it
327
+ # may contain setter methods.
328
+ def include(mod)
329
+ clear_setter_methods_cache
330
+ super
331
+ end
332
+
251
333
  # If possible, set the dataset for the model subclass as soon as it
252
334
  # is created. Also, make sure the inherited class instance variables
253
335
  # are copied into the subclass.
@@ -268,15 +350,14 @@ module Sequel
268
350
  subclass.instance_variable_set(iv, sup_class_value)
269
351
  end
270
352
  unless ivs.include?("@dataset")
271
- db
272
- begin
273
- if self == Model || !@dataset
274
- subclass.set_dataset(subclass.implicit_table_name) unless subclass.name.empty?
275
- elsif @dataset
276
- subclass.set_dataset(@dataset.clone, :inherited=>true)
353
+ if self == Model || !@dataset
354
+ n = subclass.name
355
+ unless n.nil? || n.empty?
356
+ db
357
+ subclass.set_dataset(subclass.implicit_table_name) rescue nil
277
358
  end
278
- rescue
279
- nil
359
+ elsif @dataset
360
+ subclass.set_dataset(@dataset.clone, :inherited=>true) rescue nil
280
361
  end
281
362
  end
282
363
  end
@@ -290,12 +371,9 @@ module Sequel
290
371
  pluralize(underscore(demodulize(name))).to_sym
291
372
  end
292
373
 
293
- # Initializes a model instance as an existing record. This constructor is
294
- # used by Sequel to initialize model instances when fetching records.
295
- # +load+ requires that values be a hash where all keys are symbols. It
296
- # probably should not be used by external code.
374
+ # Calls #call with the values hash. Only for backwards compatibility.
297
375
  def load(values)
298
- new(values, true)
376
+ call(values)
299
377
  end
300
378
 
301
379
  # Clear the setter_methods cache when a setter method is added
@@ -312,7 +390,7 @@ module Sequel
312
390
  # Artist.primary_key # => nil
313
391
  def no_primary_key
314
392
  clear_setter_methods_cache
315
- @simple_pk = @primary_key = nil
393
+ self.simple_pk = @primary_key = nil
316
394
  end
317
395
 
318
396
  # Loads a plugin for use with the model class, passing optional arguments
@@ -320,23 +398,18 @@ module Sequel
320
398
  # require the plugin from either sequel/plugins/#{plugin} or
321
399
  # sequel_#{plugin}, and then attempt to load the module using a
322
400
  # the camelized plugin name under Sequel::Plugins.
323
- def plugin(plugin, *args, &blk)
401
+ def plugin(plugin, *args, &block)
324
402
  m = plugin.is_a?(Module) ? plugin : plugin_module(plugin)
325
403
  unless @plugins.include?(m)
326
404
  @plugins << m
327
- m.apply(self, *args, &blk) if m.respond_to?(:apply)
328
- include(m::InstanceMethods) if m.const_defined?("InstanceMethods")
329
- extend(m::ClassMethods)if m.const_defined?("ClassMethods")
330
- if m.const_defined?("DatasetMethods")
331
- dataset.extend(m::DatasetMethods) if @dataset
332
- dataset_method_modules << m::DatasetMethods
333
- meths = m::DatasetMethods.public_instance_methods.reject{|x| NORMAL_METHOD_NAME_REGEXP !~ x.to_s}
334
- def_dataset_method(*meths) unless meths.empty?
335
- end
405
+ m.apply(self, *args, &block) if m.respond_to?(:apply)
406
+ include(m::InstanceMethods) if plugin_module_defined?(m, :InstanceMethods)
407
+ extend(m::ClassMethods)if plugin_module_defined?(m, :ClassMethods)
408
+ dataset_extend(m::DatasetMethods) if plugin_module_defined?(m, :DatasetMethods)
336
409
  end
337
- m.configure(self, *args, &blk) if m.respond_to?(:configure)
410
+ m.configure(self, *args, &block) if m.respond_to?(:configure)
338
411
  end
339
-
412
+
340
413
  # Returns primary key attribute hash. If using a composite primary key
341
414
  # value such be an array with values for each primary key in the correct
342
415
  # order. For a standard primary key, value should be an object with a
@@ -389,8 +462,8 @@ module Sequel
389
462
  # setter methods (methods that end in =) that you want to be used during
390
463
  # mass assignment, they need to be listed here as well (without the =).
391
464
  #
392
- # It may be better to use a method such as +set_only+ instead of this in places where
393
- # only certain columns may be allowed.
465
+ # It may be better to use a method such as +set_only+ or +set_fields+ that lets you specify
466
+ # the allowed fields per call.
394
467
  #
395
468
  # Artist.set_allowed_columns(:name, :hometown)
396
469
  # Artist.set(:name=>'Bob', :hometown=>'Sactown') # No Error
@@ -400,10 +473,12 @@ module Sequel
400
473
  @allowed_columns = cols
401
474
  end
402
475
 
403
- # Sets the dataset associated with the Model class. +ds+ can be a +Symbol+
404
- # (specifying a table name in the current database), or a +Dataset+.
476
+ # Sets the dataset associated with the Model class. +ds+ can be a +Symbol+,
477
+ # +LiteralString+, <tt>SQL::Identifier</tt>, <tt>SQL::QualifiedIdentifier</tt>,
478
+ # <tt>SQL::AliasedExpression</tt>
479
+ # (all specifying a table name in the current database), or a +Dataset+.
405
480
  # If a dataset is used, the model's database is changed to the database of the given
406
- # dataset. If a symbol is used, a dataset is created from the current
481
+ # dataset. If a dataset is not used, a dataset is created from the current
407
482
  # database with the table name given. Other arguments raise an +Error+.
408
483
  # Returns self.
409
484
  #
@@ -418,20 +493,24 @@ module Sequel
418
493
  def set_dataset(ds, opts={})
419
494
  inherited = opts[:inherited]
420
495
  @dataset = case ds
421
- when Symbol
422
- @simple_table = db.literal(ds)
423
- db[ds]
496
+ when Symbol, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression, LiteralString
497
+ self.simple_table = db.literal(ds)
498
+ db.from(ds)
424
499
  when Dataset
425
- @simple_table = nil
500
+ self.simple_table = if ds.send(:simple_select_all?)
501
+ ds.literal(ds.first_source_table)
502
+ else
503
+ nil
504
+ end
426
505
  @db = ds.db
427
506
  ds
428
507
  else
429
- raise(Error, "Model.set_dataset takes a Symbol or a Sequel::Dataset")
508
+ raise(Error, "Model.set_dataset takes one of the following classes as an argument: Symbol, LiteralString, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression, Dataset")
430
509
  end
431
- @dataset.row_proc = Proc.new{|r| load(r)}
510
+ @dataset.row_proc = self
432
511
  @require_modification = Sequel::Model.require_modification.nil? ? @dataset.provides_accurate_rows_matched? : Sequel::Model.require_modification
433
512
  if inherited
434
- @simple_table = superclass.simple_table
513
+ self.simple_table = superclass.simple_table
435
514
  @columns = @dataset.columns rescue nil
436
515
  else
437
516
  @dataset_method_modules.each{|m| @dataset.extend(m)} if @dataset_method_modules
@@ -439,12 +518,14 @@ module Sequel
439
518
  end
440
519
  @dataset.model = self if @dataset.respond_to?(:model=)
441
520
  check_non_connection_error{@db_schema = (inherited ? superclass.db_schema : get_db_schema)}
521
+ @instance_dataset = @dataset.limit(1).naked
442
522
  self
443
523
  end
444
-
524
+
445
525
  # Sets the primary key for this model. You can use either a regular
446
526
  # or a composite primary key. To not use a primary key, set to nil
447
- # or use +no_primary_key+.
527
+ # or use +no_primary_key+. On most adapters, Sequel can automatically
528
+ # determine the primary key to use, so this method is not needed often.
448
529
  #
449
530
  # class Person < Sequel::Model
450
531
  # # regular key
@@ -458,22 +539,25 @@ module Sequel
458
539
  def set_primary_key(*key)
459
540
  clear_setter_methods_cache
460
541
  key = key.flatten
461
- @simple_pk = key.length == 1 ? db.literal(key.first) : nil
542
+ self.simple_pk = if key.length == 1
543
+ (@dataset || db).literal(key.first)
544
+ else
545
+ nil
546
+ end
462
547
  @primary_key = (key.length == 1) ? key[0] : key
463
548
  end
464
549
 
465
550
  # Set the columns to restrict when using mass assignment (e.g. +set+). Using this means that
466
551
  # attempts to call setter methods for the columns listed here will cause an
467
- # exception or be silently skipped (based on the +strict_param_setting+ setting.
552
+ # exception or be silently skipped (based on the +strict_param_setting+ setting).
468
553
  # If you have any virtual setter methods (methods that end in =) that you
469
554
  # want not to be used during mass assignment, they need to be listed here as well (without the =).
470
555
  #
471
- # It may be better to use a method such as +set_except+ instead of this in places where
472
- # certain columns are restricted. In general, it's better to have a whitelist approach
473
- # where you specify only what is allowed, as opposed to a blacklist approach that this
474
- # method uses, where everything is allowed other than what you restrict.
556
+ # It's generally a bad idea to rely on a blacklist approach for security. Using a whitelist
557
+ # approach such as set_allowed_columns or the instance level set_only or set_fields methods
558
+ # is usually a better choice. So use of this method is generally a bad idea.
475
559
  #
476
- # Artist.set_restricted_column(:records_sold)
560
+ # Artist.set_restricted_columns(:records_sold)
477
561
  # Artist.set(:name=>'Bob', :hometown=>'Sactown') # No Error
478
562
  # Artist.set(:name=>'Bob', :records_sold=>30000) # Error
479
563
  def set_restricted_columns(*cols)
@@ -511,6 +595,9 @@ module Sequel
511
595
  # 7 days ago.
512
596
  #
513
597
  # Both the args given and the block are passed to <tt>Dataset#filter</tt>.
598
+ #
599
+ # This method creates dataset methods that do not accept arguments. To create
600
+ # dataset methods that accept arguments, you have to use def_dataset_method.
514
601
  def subset(name, *args, &block)
515
602
  def_dataset_method(name){filter(*args, &block)}
516
603
  end
@@ -526,6 +613,7 @@ module Sequel
526
613
  end
527
614
 
528
615
  # Allow the setting of the primary key(s) when using the mass assignment methods.
616
+ # Using this method can open up security issues, be very careful before using it.
529
617
  #
530
618
  # Artist.set(:id=>1) # Error
531
619
  # Artist.unrestrict_primary_key
@@ -548,6 +636,16 @@ module Sequel
548
636
  end
549
637
  end
550
638
 
639
+ # Add the module to the class's dataset_method_modules. Extend the dataset with the
640
+ # module if the model has a dataset. Add dataset methods to the class for all
641
+ # public dataset methods.
642
+ def dataset_extend(mod)
643
+ dataset.extend(mod) if @dataset
644
+ dataset_method_modules << mod
645
+ meths = mod.public_instance_methods.reject{|x| NORMAL_METHOD_NAME_REGEXP !~ x.to_s}
646
+ def_dataset_method(*meths) unless meths.empty?
647
+ end
648
+
551
649
  # Create a column accessor for a column with a method name that is hard to use in ruby code.
552
650
  def def_bad_column_accessor(column)
553
651
  overridable_methods_module.module_eval do
@@ -570,6 +668,13 @@ module Sequel
570
668
  end
571
669
  end
572
670
 
671
+ # Define a model method that calls the dataset method with the same name,
672
+ # only used for methods with names that can't be presented directly in
673
+ # ruby code.
674
+ def def_model_dataset_method_block(arg)
675
+ meta_def(arg){|*args, &block| dataset.send(arg, *args, &block)}
676
+ end
677
+
573
678
  # Get the schema from the database, fall back on checking the columns
574
679
  # via the database if that will return inaccurate results or if
575
680
  # it raises an error.
@@ -578,10 +683,9 @@ module Sequel
578
683
  return nil unless @dataset
579
684
  schema_hash = {}
580
685
  ds_opts = dataset.opts
581
- single_table = ds_opts[:from] && (ds_opts[:from].length == 1) \
582
- && !ds_opts.include?(:join) && !ds_opts.include?(:sql)
583
686
  get_columns = proc{check_non_connection_error{columns} || []}
584
- if single_table && (schema_array = (db.schema(dataset.first_source_table, :reload=>reload) rescue nil))
687
+ schema_array = check_non_connection_error{db.schema(dataset, :reload=>reload)}
688
+ if schema_array
585
689
  schema_array.each{|k,v| schema_hash[k] = v}
586
690
  if ds_opts.include?(:select)
587
691
  # We don't remove the columns from the schema_hash,
@@ -589,6 +693,7 @@ module Sequel
589
693
  # even if they are not selected.
590
694
  cols = get_columns.call
591
695
  cols.each{|c| schema_hash[c] ||= {}}
696
+ def_column_accessor(*schema_hash.keys)
592
697
  else
593
698
  # Dataset is for a single table with all columns,
594
699
  # so set the columns based on the order they were
@@ -657,15 +762,43 @@ module Sequel
657
762
  Sequel::Plugins.const_get(module_name)
658
763
  end
659
764
 
765
+ # Check if the plugin module +plugin+ defines the constant named by +submod+.
766
+ def plugin_module_defined?(plugin, submod)
767
+ if RUBY_VERSION >= '1.9'
768
+ plugin.const_defined?(submod, false)
769
+ else
770
+ plugin.const_defined?(submod)
771
+ end
772
+ end
773
+
660
774
  # Find the row in the dataset that matches the primary key. Uses
661
775
  # a static SQL optimization if the table and primary key are simple.
776
+ #
777
+ # This method should not be called with a nil primary key, in case
778
+ # it is overridden by plugins which assume that the passed argument
779
+ # is valid.
662
780
  def primary_key_lookup(pk)
663
- if t = simple_table and p = simple_pk
664
- with_sql("SELECT * FROM #{t} WHERE #{p} = #{dataset.literal(pk)}").first
781
+ if sql = @fast_pk_lookup_sql
782
+ sql = sql.dup
783
+ ds = dataset
784
+ ds.literal_append(sql, pk)
785
+ ds.fetch_rows(sql){|r| return ds.row_proc.call(r)}
786
+ nil
665
787
  else
666
788
  dataset[primary_key_hash(pk)]
667
789
  end
668
790
  end
791
+
792
+ # Reset the cached fast primary lookup SQL if a simple table and primary key
793
+ # are used, or set it to nil if not used.
794
+ def reset_fast_pk_lookup_sql
795
+ @fast_pk_lookup_sql = if @simple_table && @simple_pk
796
+ "SELECT * FROM #@simple_table WHERE #@simple_pk = ".freeze
797
+ end
798
+ @fast_instance_delete_sql = if @simple_table && @simple_pk
799
+ "DELETE FROM #@simple_table WHERE #@simple_pk = ".freeze
800
+ end
801
+ end
669
802
 
670
803
  # Set the columns for this model and create accessor methods for each column.
671
804
  def set_columns(new_columns)
@@ -674,6 +807,18 @@ module Sequel
674
807
  @columns
675
808
  end
676
809
 
810
+ # Reset the fast primary key lookup SQL when the simple_pk value changes.
811
+ def simple_pk=(pk)
812
+ @simple_pk = pk
813
+ reset_fast_pk_lookup_sql
814
+ end
815
+
816
+ # Reset the fast primary key lookup SQL when the simple_table value changes.
817
+ def simple_table=(t)
818
+ @simple_table = t
819
+ reset_fast_pk_lookup_sql
820
+ end
821
+
677
822
  # Add model methods that call dataset methods
678
823
  DATASET_METHODS.each{|arg| class_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__)}
679
824
 
@@ -686,24 +831,25 @@ module Sequel
686
831
 
687
832
  # Sequel::Model instance methods that implement basic model functionality.
688
833
  #
689
- # * All of the methods in +HOOKS+ create instance methods that are called
834
+ # * All of the methods in +HOOKS+ and +AROUND_HOOKS+ create instance methods that are called
690
835
  # by Sequel when the appropriate action occurs. For example, when destroying
691
- # a model object, Sequel will call +before_destroy+, do the destroy,
692
- # and then call +after_destroy+.
836
+ # a model object, Sequel will call +around_destroy+, which will call +before_destroy+, do
837
+ # the destroy, and then call +after_destroy+.
693
838
  # * The following instance_methods all call the class method of the same
694
- # name: columns, dataset, db, primary_key, db_schema.
839
+ # name: columns, db, primary_key, db_schema.
695
840
  # * All of the methods in +BOOLEAN_SETTINGS+ create attr_writers allowing you
696
- # to set values for the attribute. It also creates instnace getters returning
841
+ # to set values for the attribute. It also creates instance getters returning
697
842
  # the value of the setting. If the value has not yet been set, it
698
843
  # gets the default value from the class by calling the class method of the same name.
699
844
  module InstanceMethods
700
845
  HOOKS.each{|h| class_eval("def #{h}; end", __FILE__, __LINE__)}
846
+ AROUND_HOOKS.each{|h| class_eval("def #{h}; yield end", __FILE__, __LINE__)}
701
847
 
702
848
  # Define instance method(s) that calls class method(s) of the
703
849
  # same name, caching the result in an instance variable. Define
704
850
  # standard attr_writer method for modifying that instance variable.
705
851
  def self.class_attr_overridable(*meths) # :nodoc:
706
- meths.each{|meth| class_eval("def #{meth}; !defined?(@#{meth}) ? (@#{meth} = self.class.#{meth}) : @#{meth} end", __FILE__, __LINE__)}
852
+ meths.each{|meth| class_eval("def #{meth}; !defined?(@#{meth}) ? (frozen? ? self.class.#{meth} : (@#{meth} = self.class.#{meth})) : @#{meth} end", __FILE__, __LINE__)}
707
853
  attr_writer(*meths)
708
854
  end
709
855
 
@@ -712,7 +858,7 @@ module Sequel
712
858
  #
713
859
  # define_method(meth){self.class.send(meth)}
714
860
  def self.class_attr_reader(*meths) # :nodoc:
715
- meths.each{|meth| class_eval("def #{meth}; model.#{meth} end", __FILE__, __LINE__)}
861
+ meths.each{|meth| class_eval("def #{meth}; self.class.#{meth} end", __FILE__, __LINE__)}
716
862
  end
717
863
 
718
864
  private_class_method :class_attr_overridable, :class_attr_reader
@@ -726,6 +872,7 @@ module Sequel
726
872
  # Artist.new(:name=>'Bob').values # => {:name=>'Bob'}
727
873
  # Artist[1].values # => {:id=>1, :name=>'Jim', ...}
728
874
  attr_reader :values
875
+ alias to_hash values
729
876
 
730
877
  # Creates new instance and passes the given values to set.
731
878
  # If a block is given, yield the instance to the block unless
@@ -735,7 +882,7 @@ module Sequel
735
882
  #
736
883
  # Arguments:
737
884
  # values :: should be a hash to pass to set.
738
- # from_db :: should only be set by <tt>Model.load</tt>, forget it exists.
885
+ # from_db :: only for backwards compatibility, forget it exists.
739
886
  #
740
887
  # Artist.new(:name=>'Bob')
741
888
  #
@@ -744,13 +891,12 @@ module Sequel
744
891
  # end
745
892
  def initialize(values = {}, from_db = false)
746
893
  if from_db
747
- @new = false
748
894
  set_values(values)
749
895
  else
750
896
  @values = {}
751
897
  @new = true
752
898
  @modified = true
753
- set(values)
899
+ initialize_set(values)
754
900
  changed_columns.clear
755
901
  yield self if block_given?
756
902
  end
@@ -766,7 +912,7 @@ module Sequel
766
912
 
767
913
  # Sets the value for the given column. If typecasting is enabled for
768
914
  # this object, typecast the value based on the column's type.
769
- # If this a a new record or the typecasted value isn't the same
915
+ # If this is a new record or the typecasted value isn't the same
770
916
  # as the current value for the column, mark the column as changed.
771
917
  #
772
918
  # a = Artist.new
@@ -778,9 +924,9 @@ module Sequel
778
924
  # If the column isn't in @values, we can't assume it is
779
925
  # NULL in the database, so assume it has changed.
780
926
  v = typecast_value(column, value)
781
- if new? || !@values.include?(column) || v != (c = @values[column]) || v.class != c.class
782
- changed_columns << column unless changed_columns.include?(column)
783
- @values[column] = v
927
+ vals = @values
928
+ if new? || !vals.include?(column) || v != (c = vals[column]) || v.class != c.class
929
+ change_column_value(column, v)
784
930
  end
785
931
  end
786
932
 
@@ -831,6 +977,7 @@ module Sequel
831
977
  # Artist[1].delete # DELETE FROM artists WHERE (id = 1)
832
978
  # # => #<Artist {:id=>1, ...}>
833
979
  def delete
980
+ raise Sequel::Error, "can't delete frozen object" if frozen?
834
981
  _delete
835
982
  self
836
983
  end
@@ -845,6 +992,7 @@ module Sequel
845
992
  # Artist[1].destroy # BEGIN; DELETE FROM artists WHERE (id = 1); COMMIT;
846
993
  # # => #<Artist {:id=>1, ...}>
847
994
  def destroy(opts = {})
995
+ raise Sequel::Error, "can't destroy frozen object" if frozen?
848
996
  checked_save_failure(opts){checked_transaction(opts){_destroy(opts)}}
849
997
  end
850
998
 
@@ -874,14 +1022,38 @@ module Sequel
874
1022
 
875
1023
  # Returns true when current instance exists, false otherwise.
876
1024
  # Generally an object that isn't new will exist unless it has
877
- # been deleted.
1025
+ # been deleted. Uses a database query to check for existence,
1026
+ # unless the model object is new, in which case this is always
1027
+ # false.
878
1028
  #
879
1029
  # Artist[1].exists? # SELECT 1 FROM artists WHERE (id = 1)
880
1030
  # # => true
1031
+ # Artist.new.exists?
1032
+ # # => false
881
1033
  def exists?
882
- !this.get(1).nil?
1034
+ new? ? false : !this.get(1).nil?
883
1035
  end
884
1036
 
1037
+ # Ignore the model's setter method cache when this instances extends a module, as the
1038
+ # module may contain setter methods.
1039
+ def extend(mod)
1040
+ @singleton_setter_added = true
1041
+ super
1042
+ end
1043
+
1044
+ # Freeze the object in such a way that it is still usable but not modifiable.
1045
+ # Once an object is frozen, you cannot modify it's values, changed_columns,
1046
+ # errors, or dataset.
1047
+ def freeze
1048
+ values.freeze
1049
+ changed_columns.freeze
1050
+ errors
1051
+ validate
1052
+ errors.freeze
1053
+ this.freeze unless new?
1054
+ super
1055
+ end
1056
+
885
1057
  # Value that should be unique for objects with the same class and pk (if pk is not nil), or
886
1058
  # the same class and values (if pk is nil).
887
1059
  #
@@ -890,7 +1062,14 @@ module Sequel
890
1062
  # Artist.new.hash == Artist.new.hash # true
891
1063
  # Artist.new(:name=>'Bob').hash == Artist.new.hash # false
892
1064
  def hash
893
- [model, pk.nil? ? @values.sort_by{|k,v| k.to_s} : pk].hash
1065
+ case primary_key
1066
+ when Array
1067
+ [model, !pk.all? ? @values.sort_by{|k,v| k.to_s} : pk].hash
1068
+ when Symbol
1069
+ [model, pk.nil? ? @values.sort_by{|k,v| k.to_s} : pk].hash
1070
+ else
1071
+ [model, @values.sort_by{|k,v| k.to_s}].hash
1072
+ end
894
1073
  end
895
1074
 
896
1075
  # Returns value for the :id attribute, even if the primary key is
@@ -926,7 +1105,8 @@ module Sequel
926
1105
  # a.update(...)
927
1106
  # end
928
1107
  def lock!
929
- new? ? self : _refresh(this.for_update)
1108
+ _refresh(this.for_update) unless new?
1109
+ self
930
1110
  end
931
1111
 
932
1112
  # Remove elements of the model object that make marshalling fail. Returns self.
@@ -957,7 +1137,7 @@ module Sequel
957
1137
  # a = Artist[1]
958
1138
  # a.modified? # => false
959
1139
  # a.set(:name=>'Jim')
960
- # a.modified # => true
1140
+ # a.modified? # => true
961
1141
  def modified?
962
1142
  @modified || !changed_columns.empty?
963
1143
  end
@@ -967,18 +1147,23 @@ module Sequel
967
1147
  # Artist.new.new? # => true
968
1148
  # Artist[1].new? # => false
969
1149
  def new?
970
- @new
1150
+ defined?(@new) ? @new : (@new = false)
971
1151
  end
972
1152
 
973
1153
  # Returns the primary key value identifying the model instance.
974
- # Raises an error if this model does not have a primary key.
1154
+ # Raises an +Error+ if this model does not have a primary key.
975
1155
  # If the model has a composite primary key, returns an array of values.
976
1156
  #
977
1157
  # Artist[1].pk # => 1
978
1158
  # Artist[[1, 2]].pk # => [1, 2]
979
1159
  def pk
980
1160
  raise(Error, "No primary key is associated with this model") unless key = primary_key
981
- key.is_a?(Array) ? key.map{|k| @values[k]} : @values[key]
1161
+ if key.is_a?(Array)
1162
+ vals = @values
1163
+ key.map{|k| vals[k]}
1164
+ else
1165
+ @values[key]
1166
+ end
982
1167
  end
983
1168
 
984
1169
  # Returns a hash identifying mapping the receivers primary key column(s) to their values.
@@ -998,7 +1183,9 @@ module Sequel
998
1183
  # a.refresh
999
1184
  # a.name # => 'Bob'
1000
1185
  def refresh
1186
+ raise Sequel::Error, "can't refresh frozen object" if frozen?
1001
1187
  _refresh(this)
1188
+ self
1002
1189
  end
1003
1190
 
1004
1191
  # Alias of refresh, but not aliased directly to make overriding in a plugin easier.
@@ -1016,26 +1203,32 @@ module Sequel
1016
1203
  #
1017
1204
  # If +save+ fails and either raise_on_save_failure or the
1018
1205
  # :raise_on_failure option is true, it raises ValidationFailed
1019
- # or BeforeHookFailed. Otherwise it returns nil.
1206
+ # or HookFailed. Otherwise it returns nil.
1020
1207
  #
1021
1208
  # If it succeeds, it returns self.
1022
1209
  #
1023
1210
  # You can provide an optional list of columns to update, in which
1024
- # case it only updates those columns.
1211
+ # case it only updates those columns, or a options hash.
1025
1212
  #
1026
1213
  # Takes the following options:
1027
1214
  #
1028
- # * :changed - save all changed columns, instead of all columns or the columns given
1029
- # * :transaction - set to true or false to override the current
1030
- # use_transactions setting
1031
- # * :validate - set to false to skip validation
1032
- # * :raise_on_failure - set to true or false to override the current
1033
- # raise_on_save_failure setting
1215
+ # :changed :: save all changed columns, instead of all columns or the columns given
1216
+ # :raise_on_failure :: set to true or false to override the current
1217
+ # +raise_on_save_failure+ setting
1218
+ # :server :: set the server/shard on the object before saving, and use that
1219
+ # server/shard in any transaction.
1220
+ # :transaction :: set to true or false to override the current
1221
+ # +use_transactions+ setting
1222
+ # :validate :: set to false to skip validation
1034
1223
  def save(*columns)
1224
+ raise Sequel::Error, "can't save frozen object" if frozen?
1035
1225
  opts = columns.last.is_a?(Hash) ? columns.pop : {}
1036
- if opts[:validate] != false and !valid?(opts)
1037
- raise(ValidationFailed.new(errors)) if raise_on_failure?(opts)
1038
- return
1226
+ set_server(opts[:server]) if opts[:server]
1227
+ if opts[:validate] != false
1228
+ unless checked_save_failure(opts){_valid?(true, opts)}
1229
+ raise(ValidationFailed.new(self)) if raise_on_failure?(opts)
1230
+ return
1231
+ end
1039
1232
  end
1040
1233
  checked_save_failure(opts){checked_transaction(opts){_save(columns, opts)}}
1041
1234
  end
@@ -1075,7 +1268,8 @@ module Sequel
1075
1268
  end
1076
1269
 
1077
1270
  # Set all values using the entries in the hash, except for the keys
1078
- # given in except.
1271
+ # given in except. You should probably use +set_fields+ or +set_only+
1272
+ # instead of this method, as blacklist approaches to security are a bad idea.
1079
1273
  #
1080
1274
  # artist.set_except({:name=>'Jim'}, :hometown)
1081
1275
  # artist.name # => 'Jim'
@@ -1086,28 +1280,85 @@ module Sequel
1086
1280
  # For each of the fields in the given array +fields+, call the setter
1087
1281
  # method with the value of that +hash+ entry for the field. Returns self.
1088
1282
  #
1089
- # artist.set_fields({:name=>'Jim'}, :name)
1283
+ # You can provide an options hash, with the following options currently respected:
1284
+ # :missing :: Can be set to :skip to skip missing entries or :raise to raise an
1285
+ # Error for missing entries. The default behavior is not to check for
1286
+ # missing entries, in which case the default value is used. To be
1287
+ # friendly with most web frameworks, the missing check will also check
1288
+ # for the string version of the argument in the hash if given a symbol.
1289
+ #
1290
+ # Examples:
1291
+ #
1292
+ # artist.set_fields({:name=>'Jim'}, [:name])
1090
1293
  # artist.name # => 'Jim'
1091
1294
  #
1092
- # artist.set_fields({:hometown=>'LA'}, :name)
1295
+ # artist.set_fields({:hometown=>'LA'}, [:name])
1093
1296
  # artist.name # => nil
1094
1297
  # artist.hometown # => 'Sac'
1095
- def set_fields(hash, fields)
1096
- fields.each{|f| send("#{f}=", hash[f])}
1298
+ #
1299
+ # artist.name # => 'Jim'
1300
+ # artist.set_fields({}, [:name], :missing=>:skip)
1301
+ # artist.name # => 'Jim'
1302
+ #
1303
+ # artist.name # => 'Jim'
1304
+ # artist.set_fields({}, [:name], :missing=>:raise)
1305
+ # # Sequel::Error raised
1306
+ def set_fields(hash, fields, opts=nil)
1307
+ if opts
1308
+ case opts[:missing]
1309
+ when :skip
1310
+ fields.each do |f|
1311
+ if hash.has_key?(f)
1312
+ send("#{f}=", hash[f])
1313
+ elsif f.is_a?(Symbol) && hash.has_key?(sf = f.to_s)
1314
+ send("#{sf}=", hash[sf])
1315
+ end
1316
+ end
1317
+ when :raise
1318
+ fields.each do |f|
1319
+ if hash.has_key?(f)
1320
+ send("#{f}=", hash[f])
1321
+ elsif f.is_a?(Symbol) && hash.has_key?(sf = f.to_s)
1322
+ send("#{sf}=", hash[sf])
1323
+ else
1324
+ raise(Sequel::Error, "missing field in hash: #{f.inspect} not in #{hash.inspect}")
1325
+ end
1326
+ end
1327
+ else
1328
+ fields.each{|f| send("#{f}=", hash[f])}
1329
+ end
1330
+ else
1331
+ fields.each{|f| send("#{f}=", hash[f])}
1332
+ end
1097
1333
  self
1098
1334
  end
1099
1335
 
1100
1336
  # Set the values using the entries in the hash, only if the key
1101
- # is included in only.
1337
+ # is included in only. It may be a better idea to use +set_fields+
1338
+ # instead of this method.
1102
1339
  #
1103
1340
  # artist.set_only({:name=>'Jim'}, :name)
1104
1341
  # artist.name # => 'Jim'
1105
1342
  #
1106
- # artist.set_only({:hometown=>'LA'}, :name) # Raise error
1343
+ # artist.set_only({:hometown=>'LA'}, :name) # Raise Error
1107
1344
  def set_only(hash, *only)
1108
1345
  set_restricted(hash, only.flatten, false)
1109
1346
  end
1110
1347
 
1348
+ # Set the shard that this object is tied to. Returns self.
1349
+ def set_server(s)
1350
+ @server = s
1351
+ @this.opts[:server] = s if @this
1352
+ self
1353
+ end
1354
+
1355
+ # Replace the current values with hash. Should definitely not be
1356
+ # used with untrusted input, and should probably not be called
1357
+ # directly by user code.
1358
+ def set_values(hash)
1359
+ @values = hash
1360
+ end
1361
+
1111
1362
  # Clear the setter_methods cache when a method is added
1112
1363
  def singleton_method_added(meth)
1113
1364
  @singleton_setter_added = true if meth.to_s =~ SETTER_METHOD_REGEXP
@@ -1119,10 +1370,10 @@ module Sequel
1119
1370
  # Artist[1].this
1120
1371
  # # SELECT * FROM artists WHERE (id = 1) LIMIT 1
1121
1372
  def this
1122
- @this ||= model.dataset.filter(pk_hash).limit(1).naked
1373
+ @this ||= use_server(model.instance_dataset.filter(pk_hash))
1123
1374
  end
1124
1375
 
1125
- # Runs set with the passed hash and then runs save_changes.
1376
+ # Runs #set with the passed hash and then runs save_changes.
1126
1377
  #
1127
1378
  # artist.update(:name=>'Jim') # UPDATE artists SET name = 'Jim' WHERE (id = 1)
1128
1379
  def update(hash)
@@ -1130,7 +1381,7 @@ module Sequel
1130
1381
  end
1131
1382
 
1132
1383
  # Update all values using the entries in the hash, ignoring any setting of
1133
- # allowed_columns or restricted columns in the model.
1384
+ # +allowed_columns+ or +restricted_columns+ in the model.
1134
1385
  #
1135
1386
  # Artist.set_restricted_columns(:name)
1136
1387
  # artist.update_all(:name=>'Jim') # UPDATE artists SET name = 'Jim' WHERE (id = 1)
@@ -1139,28 +1390,30 @@ module Sequel
1139
1390
  end
1140
1391
 
1141
1392
  # Update all values using the entries in the hash, except for the keys
1142
- # given in except.
1393
+ # given in except. You should probably use +update_fields+ or +update_only+
1394
+ # instead of this method, as blacklist approaches to security are a bad idea.
1143
1395
  #
1144
1396
  # artist.update_except({:name=>'Jim'}, :hometown) # UPDATE artists SET name = 'Jim' WHERE (id = 1)
1145
1397
  def update_except(hash, *except)
1146
1398
  update_restricted(hash, false, except.flatten)
1147
1399
  end
1148
1400
 
1149
- # Update the instances values by calling +set_fields+ with the +hash+
1150
- # and +fields+, then save any changes to the record. Returns self.
1401
+ # Update the instances values by calling +set_fields+ with the arguments, then
1402
+ # saves any changes to the record. Returns self.
1151
1403
  #
1152
- # artist.update_fields({:name=>'Jim'}, :name)
1404
+ # artist.update_fields({:name=>'Jim'}, [:name])
1153
1405
  # # UPDATE artists SET name = 'Jim' WHERE (id = 1)
1154
1406
  #
1155
- # artist.update_fields({:hometown=>'LA'}, :name)
1407
+ # artist.update_fields({:hometown=>'LA'}, [:name])
1156
1408
  # # UPDATE artists SET name = NULL WHERE (id = 1)
1157
- def update_fields(hash, fields)
1158
- set_fields(hash, fields)
1409
+ def update_fields(hash, fields, opts=nil)
1410
+ set_fields(hash, fields, opts)
1159
1411
  save_changes
1160
1412
  end
1161
1413
 
1162
1414
  # Update the values using the entries in the hash, only if the key
1163
- # is included in only.
1415
+ # is included in only. It may be a better idea to use +update_fields+
1416
+ # instead of this method.
1164
1417
  #
1165
1418
  # artist.update_only({:name=>'Jim'}, :name)
1166
1419
  # # UPDATE artists SET name = 'Jim' WHERE (id = 1)
@@ -1185,21 +1438,15 @@ module Sequel
1185
1438
  # artist(:name=>'Invalid').valid? # => false
1186
1439
  # artist.errors.full_messages # => ['name cannot be Invalid']
1187
1440
  def valid?(opts = {})
1188
- errors.clear
1189
- if before_validation == false
1190
- raise_hook_failure(:validation) if raise_on_failure?(opts)
1191
- return false
1192
- end
1193
- validate
1194
- after_validation
1195
- errors.empty?
1441
+ _valid?(false, opts)
1196
1442
  end
1197
1443
 
1198
1444
  private
1199
1445
 
1200
- # Actually do the deletion of the object's dataset.
1446
+ # Do the deletion of the object's dataset, and check that the row
1447
+ # was actually deleted.
1201
1448
  def _delete
1202
- n = _delete_dataset.delete
1449
+ n = _delete_without_checking
1203
1450
  raise(NoExistingObject, "Attempt to delete object did not result in a single row modification (Rows Deleted: #{n}, SQL: #{_delete_dataset.delete_sql})") if require_modification && n != 1
1204
1451
  n
1205
1452
  end
@@ -1210,12 +1457,33 @@ module Sequel
1210
1457
  this
1211
1458
  end
1212
1459
 
1460
+ # Actually do the deletion of the object's dataset. Return the
1461
+ # number of rows modified.
1462
+ def _delete_without_checking
1463
+ if sql = (m = model).fast_instance_delete_sql
1464
+ sql = sql.dup
1465
+ (ds = m.dataset).literal_append(sql, pk)
1466
+ ds.with_sql_delete(sql)
1467
+ else
1468
+ _delete_dataset.delete
1469
+ end
1470
+ end
1471
+
1213
1472
  # Internal destroy method, separted from destroy to
1214
1473
  # allow running inside a transaction
1215
1474
  def _destroy(opts)
1216
- raise_hook_failure(:destroy) if before_destroy == false
1217
- _destroy_delete
1218
- after_destroy
1475
+ sh = {:server=>this_server}
1476
+ db.after_rollback(sh){after_destroy_rollback} if uacr = use_after_commit_rollback
1477
+ called = false
1478
+ around_destroy do
1479
+ called = true
1480
+ raise_hook_failure(:before_destroy) if before_destroy == false
1481
+ _destroy_delete
1482
+ after_destroy
1483
+ true
1484
+ end
1485
+ raise_hook_failure(:around_destroy) unless called
1486
+ db.after_commit(sh){after_destroy_commit} if uacr
1219
1487
  self
1220
1488
  end
1221
1489
 
@@ -1230,68 +1498,107 @@ module Sequel
1230
1498
  # the record should be refreshed from the database.
1231
1499
  def _insert
1232
1500
  ds = _insert_dataset
1233
- if ds.respond_to?(:insert_select) and h = ds.insert_select(@values)
1234
- @values = h
1501
+ if !ds.opts[:select] and ds.supports_insert_select? and h = _insert_select_raw(ds)
1502
+ set_values(h)
1235
1503
  nil
1236
1504
  else
1237
- iid = ds.insert(@values)
1505
+ iid = _insert_raw(ds)
1238
1506
  # if we have a regular primary key and it's not set in @values,
1239
1507
  # we assume it's the last inserted id
1240
- if (pk = autoincrementing_primary_key) && pk.is_a?(Symbol) && !@values[pk]
1241
- @values[pk] = iid
1508
+ if (pk = autoincrementing_primary_key) && pk.is_a?(Symbol) && !(vals = @values)[pk]
1509
+ vals[pk] = iid
1242
1510
  end
1243
1511
  pk
1244
1512
  end
1245
1513
  end
1246
-
1514
+
1247
1515
  # The dataset to use when inserting a new object. The same as the model's
1248
1516
  # dataset by default.
1249
1517
  def _insert_dataset
1250
- model.dataset
1518
+ use_server(model.instance_dataset)
1251
1519
  end
1252
1520
 
1521
+ # Insert into the given dataset and return the primary key created (if any).
1522
+ def _insert_raw(ds)
1523
+ ds.insert(@values)
1524
+ end
1525
+
1526
+ # Insert into the given dataset and return the hash of column values.
1527
+ def _insert_select_raw(ds)
1528
+ ds.insert_select(@values)
1529
+ end
1530
+
1253
1531
  # Refresh using a particular dataset, used inside save to make sure the same server
1254
1532
  # is used for reading newly inserted values from the database
1255
1533
  def _refresh(dataset)
1256
- set_values(dataset.first || raise(Error, "Record not found"))
1534
+ set_values(_refresh_get(dataset) || raise(Error, "Record not found"))
1257
1535
  changed_columns.clear
1258
- self
1536
+ end
1537
+
1538
+ # Get the row of column data from the database.
1539
+ def _refresh_get(dataset)
1540
+ dataset.first
1259
1541
  end
1260
1542
 
1261
1543
  # Internal version of save, split from save to allow running inside
1262
1544
  # it's own transaction.
1263
1545
  def _save(columns, opts)
1264
- raise_hook_failure(:save) if before_save == false
1265
- if new?
1266
- raise_hook_failure(:create) if before_create == false
1267
- pk = _insert
1268
- @this = nil
1269
- @new = false
1270
- @was_new = true
1271
- after_create
1546
+ sh = {:server=>this_server}
1547
+ db.after_rollback(sh){after_rollback} if uacr = use_after_commit_rollback
1548
+ was_new = false
1549
+ pk = nil
1550
+ called_save = false
1551
+ called_cu = false
1552
+ around_save do
1553
+ called_save = true
1554
+ raise_hook_failure(:before_save) if before_save == false
1555
+ if new?
1556
+ was_new = true
1557
+ around_create do
1558
+ called_cu = true
1559
+ raise_hook_failure(:before_create) if before_create == false
1560
+ pk = _insert
1561
+ @this = nil
1562
+ @new = false
1563
+ @was_new = true
1564
+ after_create
1565
+ true
1566
+ end
1567
+ raise_hook_failure(:around_create) unless called_cu
1568
+ else
1569
+ around_update do
1570
+ called_cu = true
1571
+ raise_hook_failure(:before_update) if before_update == false
1572
+ if columns.empty?
1573
+ @columns_updated = if opts[:changed]
1574
+ @values.reject{|k,v| !changed_columns.include?(k)}
1575
+ else
1576
+ _save_update_all_columns_hash
1577
+ end
1578
+ changed_columns.clear
1579
+ else # update only the specified columns
1580
+ @columns_updated = @values.reject{|k, v| !columns.include?(k)}
1581
+ changed_columns.reject!{|c| columns.include?(c)}
1582
+ end
1583
+ _update_columns(@columns_updated)
1584
+ @this = nil
1585
+ after_update
1586
+ true
1587
+ end
1588
+ raise_hook_failure(:around_update) unless called_cu
1589
+ end
1272
1590
  after_save
1591
+ true
1592
+ end
1593
+ raise_hook_failure(:around_save) unless called_save
1594
+ if was_new
1273
1595
  @was_new = nil
1274
1596
  pk ? _save_refresh : changed_columns.clear
1275
1597
  else
1276
- raise_hook_failure(:update) if before_update == false
1277
- if columns.empty?
1278
- @columns_updated = if opts[:changed]
1279
- @values.reject{|k,v| !changed_columns.include?(k)}
1280
- else
1281
- _save_update_all_columns_hash
1282
- end
1283
- changed_columns.clear
1284
- else # update only the specified columns
1285
- @columns_updated = @values.reject{|k, v| !columns.include?(k)}
1286
- changed_columns.reject!{|c| columns.include?(c)}
1287
- end
1288
- _update_columns(@columns_updated)
1289
- @this = nil
1290
- after_update
1291
- after_save
1292
1598
  @columns_updated = nil
1293
1599
  end
1294
1600
  @modified = false
1601
+ db.after_commit(sh){after_commit} if uacr
1295
1602
  self
1296
1603
  end
1297
1604
 
@@ -1321,9 +1628,10 @@ module Sequel
1321
1628
  _update(columns) unless columns.empty?
1322
1629
  end
1323
1630
 
1324
- # Update this instance's dataset with the supplied column hash.
1631
+ # Update this instance's dataset with the supplied column hash,
1632
+ # checking that only a single row was modified.
1325
1633
  def _update(columns)
1326
- n = _update_dataset.update(columns)
1634
+ n = _update_without_checking(columns)
1327
1635
  raise(NoExistingObject, "Attempt to update object did not result in a single row modification (SQL: #{_update_dataset.update_sql(columns)})") if require_modification && n != 1
1328
1636
  n
1329
1637
  end
@@ -1334,7 +1642,47 @@ module Sequel
1334
1642
  this
1335
1643
  end
1336
1644
 
1337
- # If not raising on failure, check for BeforeHookFailed
1645
+ # Update this instances dataset with the supplied column hash.
1646
+ def _update_without_checking(columns)
1647
+ _update_dataset.update(columns)
1648
+ end
1649
+
1650
+ # Internal validation method. If +raise_errors+ is +true+, hook
1651
+ # failures will be raised as HookFailure exceptions. If it is
1652
+ # +false+, +false+ will be returned instead.
1653
+ def _valid?(raise_errors, opts)
1654
+ return errors.empty? if frozen?
1655
+ errors.clear
1656
+ called = false
1657
+ error = false
1658
+ around_validation do
1659
+ called = true
1660
+ if before_validation == false
1661
+ if raise_errors
1662
+ raise_hook_failure(:before_validation)
1663
+ else
1664
+ error = true
1665
+ end
1666
+ false
1667
+ else
1668
+ validate
1669
+ after_validation
1670
+ errors.empty?
1671
+ end
1672
+ end
1673
+ error = true unless called
1674
+ if error
1675
+ if raise_errors
1676
+ raise_hook_failure(:around_validation)
1677
+ else
1678
+ false
1679
+ end
1680
+ else
1681
+ errors.empty?
1682
+ end
1683
+ end
1684
+
1685
+ # If not raising on failure, check for HookFailed
1338
1686
  # being raised by yielding and swallow it.
1339
1687
  def checked_save_failure(opts)
1340
1688
  if raise_on_failure?(opts)
@@ -1342,7 +1690,7 @@ module Sequel
1342
1690
  else
1343
1691
  begin
1344
1692
  yield
1345
- rescue BeforeHookFailed
1693
+ rescue HookFailed
1346
1694
  nil
1347
1695
  end
1348
1696
  end
@@ -1350,7 +1698,21 @@ module Sequel
1350
1698
 
1351
1699
  # If transactions should be used, wrap the yield in a transaction block.
1352
1700
  def checked_transaction(opts={})
1353
- use_transaction?(opts) ? db.transaction(opts){yield} : yield
1701
+ use_transaction?(opts) ? db.transaction({:server=>this_server}.merge(opts)){yield} : yield
1702
+ end
1703
+
1704
+ # Change the value of the column to given value, recording the change.
1705
+ def change_column_value(column, value)
1706
+ cc = changed_columns
1707
+ cc << column unless cc.include?(column)
1708
+ @values[column] = value
1709
+ end
1710
+
1711
+ # Set the columns with the given hash. By default, the same as +set+, but
1712
+ # exists so it can be overridden. This is called only for new records, before
1713
+ # changed_columns is cleared.
1714
+ def initialize_set(h)
1715
+ set(h) unless h.empty?
1354
1716
  end
1355
1717
 
1356
1718
  # Default inspection output for the values hash, overwrite to change what #inspect displays.
@@ -1369,11 +1731,12 @@ module Sequel
1369
1731
  # Raise an error appropriate to the hook type. May be swallowed by
1370
1732
  # checked_save_failure depending on the raise_on_failure? setting.
1371
1733
  def raise_hook_failure(type)
1372
- raise BeforeHookFailed, "one of the before_#{type} hooks returned false"
1734
+ raise HookFailed.new("the #{type} hook failed", self)
1373
1735
  end
1374
1736
 
1375
1737
  # Set the columns, filtered by the only and except arrays.
1376
1738
  def set_restricted(hash, only, except)
1739
+ return self if hash.empty?
1377
1740
  meths = if only.nil? && except.nil? && !@singleton_setter_added
1378
1741
  model.setter_methods
1379
1742
  else
@@ -1385,17 +1748,21 @@ module Sequel
1385
1748
  if meths.include?(m)
1386
1749
  send(m, v)
1387
1750
  elsif strict
1388
- raise Error, "method #{m} doesn't exist or access is restricted to it"
1751
+ # Avoid using respond_to? or creating symbols from user input
1752
+ if public_methods.map{|s| s.to_s}.include?(m)
1753
+ if Array(model.primary_key).map{|s| s.to_s}.member?(k.to_s) && model.restrict_primary_key?
1754
+ raise Error, "#{k} is a restricted primary key"
1755
+ else
1756
+ raise Error, "#{k} is a restricted column"
1757
+ end
1758
+ else
1759
+ raise Error, "method #{m} doesn't exist"
1760
+ end
1389
1761
  end
1390
1762
  end
1391
1763
  self
1392
1764
  end
1393
1765
 
1394
- # Replace the current values with hash.
1395
- def set_values(hash)
1396
- @values = hash
1397
- end
1398
-
1399
1766
  # Returns all methods that can be used for attribute
1400
1767
  # assignment (those that end with =), modified by the only
1401
1768
  # and except arguments:
@@ -1424,13 +1791,25 @@ module Sequel
1424
1791
  meths
1425
1792
  end
1426
1793
  end
1794
+
1795
+ # The server/shard that the model object's dataset uses, or :default if the
1796
+ # model object's dataset does not have an associated shard.
1797
+ def this_server
1798
+ if (s = @server)
1799
+ s
1800
+ elsif (t = @this)
1801
+ t.opts[:server] || :default
1802
+ else
1803
+ model.dataset.opts[:server] || :default
1804
+ end
1805
+ end
1427
1806
 
1428
1807
  # Typecast the value to the column's type if typecasting. Calls the database's
1429
1808
  # typecast_value method, so database adapters can override/augment the handling
1430
1809
  # for database specific column types.
1431
1810
  def typecast_value(column, value)
1432
1811
  return value unless typecast_on_assignment && db_schema && (col_schema = db_schema[column])
1433
- value = nil if value == '' and typecast_empty_string_to_nil and col_schema[:type] and ![:string, :blob].include?(col_schema[:type])
1812
+ value = nil if '' == value and typecast_empty_string_to_nil and col_schema[:type] and ![:string, :blob].include?(col_schema[:type])
1434
1813
  raise(InvalidValue, "nil/NULL is not allowed for the #{column} column") if raise_on_typecast_failure && value.nil? && (col_schema[:allow_null] == false)
1435
1814
  begin
1436
1815
  model.db.typecast_value(col_schema[:type], value)
@@ -1444,6 +1823,11 @@ module Sequel
1444
1823
  set_restricted(hash, only, except)
1445
1824
  save_changes
1446
1825
  end
1826
+
1827
+ # Set the given dataset to use the current object's shard.
1828
+ def use_server(ds)
1829
+ @server ? ds.server(@server) : ds
1830
+ end
1447
1831
 
1448
1832
  # Whether to use a transaction for this action. If the :transaction
1449
1833
  # option is present in the hash, use that, otherwise, fallback to the
@@ -1461,6 +1845,18 @@ module Sequel
1461
1845
  # Artist.dataset.model # => Artist
1462
1846
  attr_accessor :model
1463
1847
 
1848
+ # Assume if a single integer is given that it is a lookup by primary
1849
+ # key, and call with_pk with the argument.
1850
+ #
1851
+ # Artist.dataset[1] # SELECT * FROM artists WHERE (id = 1) LIMIT 1
1852
+ def [](*args)
1853
+ if args.length == 1 && (i = args.at(0)) && i.is_a?(Integer)
1854
+ with_pk(i)
1855
+ else
1856
+ super
1857
+ end
1858
+ end
1859
+
1464
1860
  # Destroy each row in the dataset by instantiating it and then calling
1465
1861
  # destroy on the resulting model object. This isn't as fast as deleting
1466
1862
  # the dataset, which does a single SQL call, but this runs any destroy
@@ -1472,7 +1868,51 @@ module Sequel
1472
1868
  # # ...
1473
1869
  def destroy
1474
1870
  pr = proc{all{|r| r.destroy}.length}
1475
- model.use_transactions ? @db.transaction(&pr) : pr.call
1871
+ model.use_transactions ? @db.transaction(:server=>opts[:server], &pr) : pr.call
1872
+ end
1873
+
1874
+ # Allow Sequel::Model classes to be used as dataset arguments when graphing:
1875
+ #
1876
+ # Artist.graph(Album, :artist_id=>id)
1877
+ # # SELECT artists.id, artists.name, albums.id AS albums_id, albums.artist_id, albums.name AS albums_name
1878
+ # # FROM artists LEFT OUTER JOIN albums ON (albums.artist_id = artists.id)
1879
+ def graph(table, *args, &block)
1880
+ if table.is_a?(Class) && table < Sequel::Model
1881
+ super(table.dataset, *args, &block)
1882
+ else
1883
+ super
1884
+ end
1885
+ end
1886
+
1887
+ # Handle Sequel::Model instances when inserting, using the model instance's
1888
+ # values for the insert, unless the model instance can be used directly in
1889
+ # SQL.
1890
+ #
1891
+ # Album.insert(Album.load(:name=>'A'))
1892
+ # # INSERT INTO albums (name) VALUES ('A')
1893
+ def insert_sql(*values)
1894
+ if values.size == 1 && (v = values.at(0)).is_a?(Sequel::Model) && !v.respond_to?(:sql_literal_append)
1895
+ super(v.to_hash)
1896
+ else
1897
+ super
1898
+ end
1899
+ end
1900
+
1901
+ # Allow Sequel::Model classes to be used as table name arguments in dataset
1902
+ # join methods:
1903
+ #
1904
+ # Artist.join(Album, :artist_id=>id)
1905
+ # # SELECT * FROM artists INNER JOIN albums ON (albums.artist_id = artists.id)
1906
+ def join_table(type, table, *args, &block)
1907
+ if table.is_a?(Class) && table < Sequel::Model
1908
+ if table.dataset.simple_select_all?
1909
+ super(type, table.table_name, *args, &block)
1910
+ else
1911
+ super(type, table.dataset, *args, &block)
1912
+ end
1913
+ else
1914
+ super
1915
+ end
1476
1916
  end
1477
1917
 
1478
1918
  # This allows you to call +to_hash+ without any arguments, which will
@@ -1491,6 +1931,19 @@ module Sequel
1491
1931
  super(pk, value_column)
1492
1932
  end
1493
1933
  end
1934
+
1935
+ # Given a primary key value, return the first record in the dataset with that primary key
1936
+ # value.
1937
+ #
1938
+ # # Single primary key
1939
+ # Artist.dataset.with_pk(1) # SELECT * FROM artists WHERE (id = 1) LIMIT 1
1940
+ #
1941
+ # # Composite primary key
1942
+ # Artist.dataset.with_pk([1, 2]) # SELECT * FROM artists
1943
+ # # WHERE ((id1 = 1) AND (id2 = 2)) LIMIT 1
1944
+ def with_pk(pk)
1945
+ first(model.qualified_primary_key_hash(pk))
1946
+ end
1494
1947
  end
1495
1948
 
1496
1949
  extend ClassMethods