sequel 3.21.0 → 3.36.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 (311) hide show
  1. data/CHANGELOG +855 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +23 -7
  4. data/Rakefile +42 -24
  5. data/bin/sequel +26 -8
  6. data/doc/active_record.rdoc +1 -0
  7. data/doc/advanced_associations.rdoc +171 -11
  8. data/doc/association_basics.rdoc +293 -43
  9. data/doc/dataset_basics.rdoc +5 -5
  10. data/doc/dataset_filtering.rdoc +8 -0
  11. data/doc/mass_assignment.rdoc +56 -0
  12. data/doc/migration.rdoc +85 -552
  13. data/doc/model_hooks.rdoc +78 -27
  14. data/doc/object_model.rdoc +541 -0
  15. data/doc/opening_databases.rdoc +97 -50
  16. data/doc/prepared_statements.rdoc +40 -14
  17. data/doc/querying.rdoc +28 -4
  18. data/doc/reflection.rdoc +30 -4
  19. data/doc/release_notes/3.22.0.txt +39 -0
  20. data/doc/release_notes/3.23.0.txt +172 -0
  21. data/doc/release_notes/3.24.0.txt +420 -0
  22. data/doc/release_notes/3.25.0.txt +88 -0
  23. data/doc/release_notes/3.26.0.txt +88 -0
  24. data/doc/release_notes/3.27.0.txt +82 -0
  25. data/doc/release_notes/3.28.0.txt +304 -0
  26. data/doc/release_notes/3.29.0.txt +459 -0
  27. data/doc/release_notes/3.30.0.txt +135 -0
  28. data/doc/release_notes/3.31.0.txt +146 -0
  29. data/doc/release_notes/3.32.0.txt +202 -0
  30. data/doc/release_notes/3.33.0.txt +157 -0
  31. data/doc/release_notes/3.34.0.txt +671 -0
  32. data/doc/release_notes/3.35.0.txt +144 -0
  33. data/doc/release_notes/3.36.0.txt +245 -0
  34. data/doc/schema_modification.rdoc +572 -0
  35. data/doc/sharding.rdoc +95 -3
  36. data/doc/testing.rdoc +129 -0
  37. data/doc/thread_safety.rdoc +17 -0
  38. data/doc/transactions.rdoc +137 -0
  39. data/doc/virtual_rows.rdoc +51 -2
  40. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  41. data/lib/sequel/adapters/ado.rb +40 -18
  42. data/lib/sequel/adapters/amalgalite.rb +17 -9
  43. data/lib/sequel/adapters/db2.rb +175 -86
  44. data/lib/sequel/adapters/dbi.rb +15 -15
  45. data/lib/sequel/adapters/do/mysql.rb +17 -11
  46. data/lib/sequel/adapters/do/postgres.rb +1 -57
  47. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  48. data/lib/sequel/adapters/do.rb +18 -36
  49. data/lib/sequel/adapters/firebird.rb +27 -208
  50. data/lib/sequel/adapters/ibmdb.rb +453 -0
  51. data/lib/sequel/adapters/informix.rb +6 -23
  52. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  53. data/lib/sequel/adapters/jdbc/db2.rb +47 -0
  54. data/lib/sequel/adapters/jdbc/derby.rb +287 -0
  55. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  56. data/lib/sequel/adapters/jdbc/h2.rb +73 -31
  57. data/lib/sequel/adapters/jdbc/hsqldb.rb +184 -0
  58. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  59. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  61. data/lib/sequel/adapters/jdbc/mysql.rb +7 -24
  62. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +58 -48
  64. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  65. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  66. data/lib/sequel/adapters/jdbc/sqlserver.rb +66 -0
  67. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  68. data/lib/sequel/adapters/jdbc.rb +265 -94
  69. data/lib/sequel/adapters/mock.rb +361 -0
  70. data/lib/sequel/adapters/mysql.rb +118 -222
  71. data/lib/sequel/adapters/mysql2.rb +58 -57
  72. data/lib/sequel/adapters/odbc/mssql.rb +15 -7
  73. data/lib/sequel/adapters/odbc.rb +28 -9
  74. data/lib/sequel/adapters/openbase.rb +3 -5
  75. data/lib/sequel/adapters/oracle.rb +349 -53
  76. data/lib/sequel/adapters/postgres.rb +356 -107
  77. data/lib/sequel/adapters/shared/access.rb +32 -9
  78. data/lib/sequel/adapters/shared/db2.rb +342 -0
  79. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  80. data/lib/sequel/adapters/shared/informix.rb +53 -0
  81. data/lib/sequel/adapters/shared/mssql.rb +335 -118
  82. data/lib/sequel/adapters/shared/mysql.rb +340 -76
  83. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +155 -0
  84. data/lib/sequel/adapters/shared/oracle.rb +243 -45
  85. data/lib/sequel/adapters/shared/postgres.rb +524 -279
  86. data/lib/sequel/adapters/shared/progress.rb +5 -8
  87. data/lib/sequel/adapters/shared/sqlite.rb +265 -89
  88. data/lib/sequel/adapters/sqlite.rb +101 -39
  89. data/lib/sequel/adapters/swift/mysql.rb +9 -12
  90. data/lib/sequel/adapters/swift/postgres.rb +9 -74
  91. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  92. data/lib/sequel/adapters/swift.rb +8 -7
  93. data/lib/sequel/adapters/tinytds.rb +161 -21
  94. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  95. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  96. data/lib/sequel/ast_transformer.rb +194 -0
  97. data/lib/sequel/connection_pool/sharded_single.rb +11 -1
  98. data/lib/sequel/connection_pool/sharded_threaded.rb +28 -2
  99. data/lib/sequel/connection_pool/single.rb +5 -0
  100. data/lib/sequel/connection_pool/threaded.rb +38 -14
  101. data/lib/sequel/connection_pool.rb +10 -2
  102. data/lib/sequel/core.rb +118 -11
  103. data/lib/sequel/database/connecting.rb +38 -28
  104. data/lib/sequel/database/dataset.rb +4 -4
  105. data/lib/sequel/database/dataset_defaults.rb +63 -2
  106. data/lib/sequel/database/logging.rb +7 -2
  107. data/lib/sequel/database/misc.rb +150 -46
  108. data/lib/sequel/database/query.rb +215 -65
  109. data/lib/sequel/database/schema_generator.rb +64 -20
  110. data/lib/sequel/database/schema_methods.rb +212 -54
  111. data/lib/sequel/dataset/actions.rb +322 -85
  112. data/lib/sequel/dataset/features.rb +96 -8
  113. data/lib/sequel/dataset/graph.rb +38 -11
  114. data/lib/sequel/dataset/misc.rb +50 -23
  115. data/lib/sequel/dataset/mutation.rb +12 -7
  116. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  117. data/lib/sequel/dataset/query.rb +237 -44
  118. data/lib/sequel/dataset/sql.rb +698 -278
  119. data/lib/sequel/dataset.rb +8 -0
  120. data/lib/sequel/exceptions.rb +4 -0
  121. data/lib/sequel/extensions/_pretty_table.rb +79 -0
  122. data/lib/sequel/extensions/arbitrary_servers.rb +108 -0
  123. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  124. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +16 -34
  125. data/lib/sequel/extensions/migration.rb +97 -11
  126. data/lib/sequel/extensions/named_timezones.rb +5 -0
  127. data/lib/sequel/extensions/null_dataset.rb +90 -0
  128. data/lib/sequel/extensions/pg_array.rb +460 -0
  129. data/lib/sequel/extensions/pg_array_ops.rb +220 -0
  130. data/lib/sequel/extensions/pg_auto_parameterize.rb +169 -0
  131. data/lib/sequel/extensions/pg_hstore.rb +296 -0
  132. data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
  133. data/lib/sequel/extensions/pg_inet.rb +89 -0
  134. data/lib/sequel/extensions/pg_json.rb +178 -0
  135. data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
  136. data/lib/sequel/extensions/pretty_table.rb +5 -71
  137. data/lib/sequel/extensions/query.rb +1 -1
  138. data/lib/sequel/extensions/query_literals.rb +79 -0
  139. data/lib/sequel/extensions/schema_caching.rb +76 -0
  140. data/lib/sequel/extensions/schema_dumper.rb +251 -37
  141. data/lib/sequel/extensions/select_remove.rb +35 -0
  142. data/lib/sequel/extensions/server_block.rb +139 -0
  143. data/lib/sequel/extensions/sql_expr.rb +4 -110
  144. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  145. data/lib/sequel/extensions/to_dot.rb +95 -83
  146. data/lib/sequel/model/associations.rb +1187 -393
  147. data/lib/sequel/model/base.rb +578 -170
  148. data/lib/sequel/model/errors.rb +1 -1
  149. data/lib/sequel/model/exceptions.rb +5 -1
  150. data/lib/sequel/model/inflections.rb +1 -1
  151. data/lib/sequel/model.rb +30 -11
  152. data/lib/sequel/no_core_ext.rb +2 -0
  153. data/lib/sequel/plugins/active_model.rb +13 -1
  154. data/lib/sequel/plugins/association_pks.rb +22 -4
  155. data/lib/sequel/plugins/caching.rb +25 -18
  156. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  157. data/lib/sequel/plugins/composition.rb +44 -12
  158. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  159. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  160. data/lib/sequel/plugins/dirty.rb +214 -0
  161. data/lib/sequel/plugins/eager_each.rb +59 -0
  162. data/lib/sequel/plugins/force_encoding.rb +6 -6
  163. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  164. data/lib/sequel/plugins/identity_map.rb +123 -8
  165. data/lib/sequel/plugins/instance_filters.rb +10 -0
  166. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  167. data/lib/sequel/plugins/json_serializer.rb +58 -6
  168. data/lib/sequel/plugins/list.rb +12 -2
  169. data/lib/sequel/plugins/many_through_many.rb +79 -28
  170. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  171. data/lib/sequel/plugins/nested_attributes.rb +139 -63
  172. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  173. data/lib/sequel/plugins/prepared_statements.rb +167 -0
  174. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  175. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  176. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  177. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  178. data/lib/sequel/plugins/schema.rb +7 -2
  179. data/lib/sequel/plugins/serialization.rb +98 -49
  180. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  181. data/lib/sequel/plugins/sharding.rb +21 -54
  182. data/lib/sequel/plugins/single_table_inheritance.rb +5 -3
  183. data/lib/sequel/plugins/static_cache.rb +99 -0
  184. data/lib/sequel/plugins/subclasses.rb +10 -2
  185. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  186. data/lib/sequel/plugins/timestamps.rb +1 -1
  187. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  188. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  189. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  190. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  191. data/lib/sequel/sql.rb +526 -59
  192. data/lib/sequel/timezones.rb +57 -38
  193. data/lib/sequel/version.rb +1 -1
  194. data/spec/adapters/db2_spec.rb +146 -0
  195. data/spec/adapters/firebird_spec.rb +1 -1
  196. data/spec/adapters/mssql_spec.rb +142 -40
  197. data/spec/adapters/mysql_spec.rb +380 -234
  198. data/spec/adapters/oracle_spec.rb +67 -77
  199. data/spec/adapters/postgres_spec.rb +1050 -212
  200. data/spec/adapters/spec_helper.rb +8 -5
  201. data/spec/adapters/sqlite_spec.rb +139 -27
  202. data/spec/core/connection_pool_spec.rb +196 -93
  203. data/spec/core/database_spec.rb +879 -510
  204. data/spec/core/dataset_spec.rb +1415 -1086
  205. data/spec/core/expression_filters_spec.rb +554 -109
  206. data/spec/core/mock_adapter_spec.rb +449 -0
  207. data/spec/core/object_graph_spec.rb +86 -114
  208. data/spec/core/schema_generator_spec.rb +4 -4
  209. data/spec/core/schema_spec.rb +482 -38
  210. data/spec/core/spec_helper.rb +6 -48
  211. data/spec/extensions/active_model_spec.rb +13 -0
  212. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  213. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  214. data/spec/extensions/association_dependencies_spec.rb +15 -25
  215. data/spec/extensions/association_pks_spec.rb +66 -32
  216. data/spec/extensions/association_proxies_spec.rb +4 -4
  217. data/spec/extensions/caching_spec.rb +47 -51
  218. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  219. data/spec/extensions/columns_introspection_spec.rb +91 -0
  220. data/spec/extensions/composition_spec.rb +18 -13
  221. data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +119 -139
  222. data/spec/extensions/dataset_associations_spec.rb +199 -0
  223. data/spec/extensions/defaults_setter_spec.rb +64 -0
  224. data/spec/extensions/dirty_spec.rb +155 -0
  225. data/spec/extensions/eager_each_spec.rb +34 -0
  226. data/spec/extensions/force_encoding_spec.rb +4 -2
  227. data/spec/extensions/hook_class_methods_spec.rb +69 -92
  228. data/spec/extensions/identity_map_spec.rb +131 -22
  229. data/spec/extensions/inflector_spec.rb +0 -4
  230. data/spec/extensions/instance_filters_spec.rb +11 -21
  231. data/spec/extensions/instance_hooks_spec.rb +72 -0
  232. data/spec/extensions/json_serializer_spec.rb +49 -6
  233. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  234. data/spec/extensions/list_spec.rb +48 -39
  235. data/spec/extensions/many_through_many_spec.rb +283 -250
  236. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  237. data/spec/extensions/migration_spec.rb +140 -35
  238. data/spec/extensions/named_timezones_spec.rb +29 -11
  239. data/spec/extensions/nested_attributes_spec.rb +251 -89
  240. data/spec/extensions/null_dataset_spec.rb +85 -0
  241. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  242. data/spec/extensions/pg_array_ops_spec.rb +105 -0
  243. data/spec/extensions/pg_array_spec.rb +196 -0
  244. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  245. data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
  246. data/spec/extensions/pg_hstore_spec.rb +195 -0
  247. data/spec/extensions/pg_inet_spec.rb +44 -0
  248. data/spec/extensions/pg_json_spec.rb +101 -0
  249. data/spec/extensions/pg_statement_cache_spec.rb +209 -0
  250. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  251. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  252. data/spec/extensions/prepared_statements_spec.rb +87 -0
  253. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  254. data/spec/extensions/pretty_table_spec.rb +7 -6
  255. data/spec/extensions/query_literals_spec.rb +168 -0
  256. data/spec/extensions/query_spec.rb +6 -0
  257. data/spec/extensions/rcte_tree_spec.rb +50 -43
  258. data/spec/extensions/schema_caching_spec.rb +41 -0
  259. data/spec/extensions/schema_dumper_spec.rb +429 -22
  260. data/spec/extensions/schema_spec.rb +25 -21
  261. data/spec/extensions/select_remove_spec.rb +38 -0
  262. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  263. data/spec/extensions/serialization_spec.rb +85 -43
  264. data/spec/extensions/server_block_spec.rb +90 -0
  265. data/spec/extensions/sharding_spec.rb +69 -143
  266. data/spec/extensions/single_table_inheritance_spec.rb +23 -29
  267. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  268. data/spec/extensions/spec_helper.rb +22 -61
  269. data/spec/extensions/sql_expr_spec.rb +29 -60
  270. data/spec/extensions/static_cache_spec.rb +145 -0
  271. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  272. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  273. data/spec/extensions/timestamps_spec.rb +6 -6
  274. data/spec/extensions/to_dot_spec.rb +4 -7
  275. data/spec/extensions/touch_spec.rb +13 -14
  276. data/spec/extensions/tree_spec.rb +11 -26
  277. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  278. data/spec/extensions/update_primary_key_spec.rb +30 -24
  279. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  280. data/spec/extensions/validation_helpers_spec.rb +16 -35
  281. data/spec/extensions/xml_serializer_spec.rb +29 -4
  282. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  283. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  284. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  285. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  286. data/spec/integration/associations_test.rb +659 -21
  287. data/spec/integration/database_test.rb +9 -3
  288. data/spec/integration/dataset_test.rb +605 -91
  289. data/spec/integration/eager_loader_test.rb +19 -21
  290. data/spec/integration/migrator_test.rb +2 -3
  291. data/spec/integration/model_test.rb +81 -2
  292. data/spec/integration/plugin_test.rb +454 -143
  293. data/spec/integration/prepared_statement_test.rb +263 -128
  294. data/spec/integration/schema_test.rb +309 -43
  295. data/spec/integration/spec_helper.rb +40 -30
  296. data/spec/integration/timezone_test.rb +38 -12
  297. data/spec/integration/transaction_test.rb +183 -8
  298. data/spec/integration/type_test.rb +18 -4
  299. data/spec/model/association_reflection_spec.rb +201 -7
  300. data/spec/model/associations_spec.rb +1067 -661
  301. data/spec/model/base_spec.rb +329 -95
  302. data/spec/model/dataset_methods_spec.rb +7 -27
  303. data/spec/model/eager_loading_spec.rb +677 -676
  304. data/spec/model/hooks_spec.rb +349 -67
  305. data/spec/model/model_spec.rb +240 -168
  306. data/spec/model/plugins_spec.rb +24 -13
  307. data/spec/model/record_spec.rb +490 -232
  308. data/spec/model/spec_helper.rb +14 -71
  309. data/spec/model/validations_spec.rb +11 -0
  310. data/spec/rcov.opts +2 -0
  311. metadata +178 -80
@@ -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,20 +539,23 @@ 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
560
  # Artist.set_restricted_column(:records_sold)
477
561
  # Artist.set(:name=>'Bob', :hometown=>'Sactown') # No Error
@@ -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
@@ -735,7 +881,7 @@ module Sequel
735
881
  #
736
882
  # Arguments:
737
883
  # values :: should be a hash to pass to set.
738
- # from_db :: should only be set by <tt>Model.load</tt>, forget it exists.
884
+ # from_db :: only for backwards compatibility, forget it exists.
739
885
  #
740
886
  # Artist.new(:name=>'Bob')
741
887
  #
@@ -744,13 +890,12 @@ module Sequel
744
890
  # end
745
891
  def initialize(values = {}, from_db = false)
746
892
  if from_db
747
- @new = false
748
893
  set_values(values)
749
894
  else
750
895
  @values = {}
751
896
  @new = true
752
897
  @modified = true
753
- set(values)
898
+ initialize_set(values)
754
899
  changed_columns.clear
755
900
  yield self if block_given?
756
901
  end
@@ -766,7 +911,7 @@ module Sequel
766
911
 
767
912
  # Sets the value for the given column. If typecasting is enabled for
768
913
  # 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
914
+ # If this is a new record or the typecasted value isn't the same
770
915
  # as the current value for the column, mark the column as changed.
771
916
  #
772
917
  # a = Artist.new
@@ -778,9 +923,9 @@ module Sequel
778
923
  # If the column isn't in @values, we can't assume it is
779
924
  # NULL in the database, so assume it has changed.
780
925
  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
926
+ vals = @values
927
+ if new? || !vals.include?(column) || v != (c = vals[column]) || v.class != c.class
928
+ change_column_value(column, v)
784
929
  end
785
930
  end
786
931
 
@@ -831,6 +976,7 @@ module Sequel
831
976
  # Artist[1].delete # DELETE FROM artists WHERE (id = 1)
832
977
  # # => #<Artist {:id=>1, ...}>
833
978
  def delete
979
+ raise Sequel::Error, "can't delete frozen object" if frozen?
834
980
  _delete
835
981
  self
836
982
  end
@@ -845,6 +991,7 @@ module Sequel
845
991
  # Artist[1].destroy # BEGIN; DELETE FROM artists WHERE (id = 1); COMMIT;
846
992
  # # => #<Artist {:id=>1, ...}>
847
993
  def destroy(opts = {})
994
+ raise Sequel::Error, "can't destroy frozen object" if frozen?
848
995
  checked_save_failure(opts){checked_transaction(opts){_destroy(opts)}}
849
996
  end
850
997
 
@@ -874,14 +1021,38 @@ module Sequel
874
1021
 
875
1022
  # Returns true when current instance exists, false otherwise.
876
1023
  # Generally an object that isn't new will exist unless it has
877
- # been deleted.
1024
+ # been deleted. Uses a database query to check for existence,
1025
+ # unless the model object is new, in which case this is always
1026
+ # false.
878
1027
  #
879
1028
  # Artist[1].exists? # SELECT 1 FROM artists WHERE (id = 1)
880
1029
  # # => true
1030
+ # Artist.new.exists?
1031
+ # # => false
881
1032
  def exists?
882
- !this.get(1).nil?
1033
+ new? ? false : !this.get(1).nil?
883
1034
  end
884
1035
 
1036
+ # Ignore the model's setter method cache when this instances extends a module, as the
1037
+ # module may contain setter methods.
1038
+ def extend(mod)
1039
+ @singleton_setter_added = true
1040
+ super
1041
+ end
1042
+
1043
+ # Freeze the object in such a way that it is still usable but not modifiable.
1044
+ # Once an object is frozen, you cannot modify it's values, changed_columns,
1045
+ # errors, or dataset.
1046
+ def freeze
1047
+ values.freeze
1048
+ changed_columns.freeze
1049
+ errors
1050
+ validate
1051
+ errors.freeze
1052
+ this.freeze unless new?
1053
+ super
1054
+ end
1055
+
885
1056
  # Value that should be unique for objects with the same class and pk (if pk is not nil), or
886
1057
  # the same class and values (if pk is nil).
887
1058
  #
@@ -890,7 +1061,14 @@ module Sequel
890
1061
  # Artist.new.hash == Artist.new.hash # true
891
1062
  # Artist.new(:name=>'Bob').hash == Artist.new.hash # false
892
1063
  def hash
893
- [model, pk.nil? ? @values.sort_by{|k,v| k.to_s} : pk].hash
1064
+ case primary_key
1065
+ when Array
1066
+ [model, !pk.all? ? @values.sort_by{|k,v| k.to_s} : pk].hash
1067
+ when Symbol
1068
+ [model, pk.nil? ? @values.sort_by{|k,v| k.to_s} : pk].hash
1069
+ else
1070
+ [model, @values.sort_by{|k,v| k.to_s}].hash
1071
+ end
894
1072
  end
895
1073
 
896
1074
  # Returns value for the :id attribute, even if the primary key is
@@ -926,7 +1104,8 @@ module Sequel
926
1104
  # a.update(...)
927
1105
  # end
928
1106
  def lock!
929
- new? ? self : _refresh(this.for_update)
1107
+ _refresh(this.for_update) unless new?
1108
+ self
930
1109
  end
931
1110
 
932
1111
  # Remove elements of the model object that make marshalling fail. Returns self.
@@ -957,7 +1136,7 @@ module Sequel
957
1136
  # a = Artist[1]
958
1137
  # a.modified? # => false
959
1138
  # a.set(:name=>'Jim')
960
- # a.modified # => true
1139
+ # a.modified? # => true
961
1140
  def modified?
962
1141
  @modified || !changed_columns.empty?
963
1142
  end
@@ -967,18 +1146,23 @@ module Sequel
967
1146
  # Artist.new.new? # => true
968
1147
  # Artist[1].new? # => false
969
1148
  def new?
970
- @new
1149
+ defined?(@new) ? @new : (@new = false)
971
1150
  end
972
1151
 
973
1152
  # Returns the primary key value identifying the model instance.
974
- # Raises an error if this model does not have a primary key.
1153
+ # Raises an +Error+ if this model does not have a primary key.
975
1154
  # If the model has a composite primary key, returns an array of values.
976
1155
  #
977
1156
  # Artist[1].pk # => 1
978
1157
  # Artist[[1, 2]].pk # => [1, 2]
979
1158
  def pk
980
1159
  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]
1160
+ if key.is_a?(Array)
1161
+ vals = @values
1162
+ key.map{|k| vals[k]}
1163
+ else
1164
+ @values[key]
1165
+ end
982
1166
  end
983
1167
 
984
1168
  # Returns a hash identifying mapping the receivers primary key column(s) to their values.
@@ -998,7 +1182,9 @@ module Sequel
998
1182
  # a.refresh
999
1183
  # a.name # => 'Bob'
1000
1184
  def refresh
1185
+ raise Sequel::Error, "can't refresh frozen object" if frozen?
1001
1186
  _refresh(this)
1187
+ self
1002
1188
  end
1003
1189
 
1004
1190
  # Alias of refresh, but not aliased directly to make overriding in a plugin easier.
@@ -1016,26 +1202,32 @@ module Sequel
1016
1202
  #
1017
1203
  # If +save+ fails and either raise_on_save_failure or the
1018
1204
  # :raise_on_failure option is true, it raises ValidationFailed
1019
- # or BeforeHookFailed. Otherwise it returns nil.
1205
+ # or HookFailed. Otherwise it returns nil.
1020
1206
  #
1021
1207
  # If it succeeds, it returns self.
1022
1208
  #
1023
1209
  # You can provide an optional list of columns to update, in which
1024
- # case it only updates those columns.
1210
+ # case it only updates those columns, or a options hash.
1025
1211
  #
1026
1212
  # Takes the following options:
1027
1213
  #
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
1214
+ # :changed :: save all changed columns, instead of all columns or the columns given
1215
+ # :raise_on_failure :: set to true or false to override the current
1216
+ # +raise_on_save_failure+ setting
1217
+ # :server :: set the server/shard on the object before saving, and use that
1218
+ # server/shard in any transaction.
1219
+ # :transaction :: set to true or false to override the current
1220
+ # +use_transactions+ setting
1221
+ # :validate :: set to false to skip validation
1034
1222
  def save(*columns)
1223
+ raise Sequel::Error, "can't save frozen object" if frozen?
1035
1224
  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
1225
+ set_server(opts[:server]) if opts[:server]
1226
+ if opts[:validate] != false
1227
+ unless checked_save_failure(opts){_valid?(true, opts)}
1228
+ raise(ValidationFailed.new(errors)) if raise_on_failure?(opts)
1229
+ return
1230
+ end
1039
1231
  end
1040
1232
  checked_save_failure(opts){checked_transaction(opts){_save(columns, opts)}}
1041
1233
  end
@@ -1075,7 +1267,8 @@ module Sequel
1075
1267
  end
1076
1268
 
1077
1269
  # Set all values using the entries in the hash, except for the keys
1078
- # given in except.
1270
+ # given in except. You should probably use +set_fields+ or +set_only+
1271
+ # instead of this method, as blacklist approaches to security are a bad idea.
1079
1272
  #
1080
1273
  # artist.set_except({:name=>'Jim'}, :hometown)
1081
1274
  # artist.name # => 'Jim'
@@ -1086,28 +1279,85 @@ module Sequel
1086
1279
  # For each of the fields in the given array +fields+, call the setter
1087
1280
  # method with the value of that +hash+ entry for the field. Returns self.
1088
1281
  #
1089
- # artist.set_fields({:name=>'Jim'}, :name)
1282
+ # You can provide an options hash, with the following options currently respected:
1283
+ # :missing :: Can be set to :skip to skip missing entries or :raise to raise an
1284
+ # Error for missing entries. The default behavior is not to check for
1285
+ # missing entries, in which case the default value is used. To be
1286
+ # friendly with most web frameworks, the missing check will also check
1287
+ # for the string version of the argument in the hash if given a symbol.
1288
+ #
1289
+ # Examples:
1290
+ #
1291
+ # artist.set_fields({:name=>'Jim'}, [:name])
1090
1292
  # artist.name # => 'Jim'
1091
1293
  #
1092
- # artist.set_fields({:hometown=>'LA'}, :name)
1294
+ # artist.set_fields({:hometown=>'LA'}, [:name])
1093
1295
  # artist.name # => nil
1094
1296
  # artist.hometown # => 'Sac'
1095
- def set_fields(hash, fields)
1096
- fields.each{|f| send("#{f}=", hash[f])}
1297
+ #
1298
+ # artist.name # => 'Jim'
1299
+ # artist.set_fields({}, [:name], :missing=>:skip)
1300
+ # artist.name # => 'Jim'
1301
+ #
1302
+ # artist.name # => 'Jim'
1303
+ # artist.set_fields({}, [:name], :missing=>:raise)
1304
+ # # Sequel::Error raised
1305
+ def set_fields(hash, fields, opts=nil)
1306
+ if opts
1307
+ case opts[:missing]
1308
+ when :skip
1309
+ fields.each do |f|
1310
+ if hash.has_key?(f)
1311
+ send("#{f}=", hash[f])
1312
+ elsif f.is_a?(Symbol) && hash.has_key?(sf = f.to_s)
1313
+ send("#{sf}=", hash[sf])
1314
+ end
1315
+ end
1316
+ when :raise
1317
+ fields.each do |f|
1318
+ if hash.has_key?(f)
1319
+ send("#{f}=", hash[f])
1320
+ elsif f.is_a?(Symbol) && hash.has_key?(sf = f.to_s)
1321
+ send("#{sf}=", hash[sf])
1322
+ else
1323
+ raise(Sequel::Error, "missing field in hash: #{f.inspect} not in #{hash.inspect}")
1324
+ end
1325
+ end
1326
+ else
1327
+ fields.each{|f| send("#{f}=", hash[f])}
1328
+ end
1329
+ else
1330
+ fields.each{|f| send("#{f}=", hash[f])}
1331
+ end
1097
1332
  self
1098
1333
  end
1099
1334
 
1100
1335
  # Set the values using the entries in the hash, only if the key
1101
- # is included in only.
1336
+ # is included in only. It may be a better idea to use +set_fields+
1337
+ # instead of this method.
1102
1338
  #
1103
1339
  # artist.set_only({:name=>'Jim'}, :name)
1104
1340
  # artist.name # => 'Jim'
1105
1341
  #
1106
- # artist.set_only({:hometown=>'LA'}, :name) # Raise error
1342
+ # artist.set_only({:hometown=>'LA'}, :name) # Raise Error
1107
1343
  def set_only(hash, *only)
1108
1344
  set_restricted(hash, only.flatten, false)
1109
1345
  end
1110
1346
 
1347
+ # Set the shard that this object is tied to. Returns self.
1348
+ def set_server(s)
1349
+ @server = s
1350
+ @this.opts[:server] = s if @this
1351
+ self
1352
+ end
1353
+
1354
+ # Replace the current values with hash. Should definitely not be
1355
+ # used with untrusted input, and should probably not be called
1356
+ # directly by user code.
1357
+ def set_values(hash)
1358
+ @values = hash
1359
+ end
1360
+
1111
1361
  # Clear the setter_methods cache when a method is added
1112
1362
  def singleton_method_added(meth)
1113
1363
  @singleton_setter_added = true if meth.to_s =~ SETTER_METHOD_REGEXP
@@ -1119,10 +1369,10 @@ module Sequel
1119
1369
  # Artist[1].this
1120
1370
  # # SELECT * FROM artists WHERE (id = 1) LIMIT 1
1121
1371
  def this
1122
- @this ||= model.dataset.filter(pk_hash).limit(1).naked
1372
+ @this ||= use_server(model.instance_dataset.filter(pk_hash))
1123
1373
  end
1124
1374
 
1125
- # Runs set with the passed hash and then runs save_changes.
1375
+ # Runs #set with the passed hash and then runs save_changes.
1126
1376
  #
1127
1377
  # artist.update(:name=>'Jim') # UPDATE artists SET name = 'Jim' WHERE (id = 1)
1128
1378
  def update(hash)
@@ -1130,7 +1380,7 @@ module Sequel
1130
1380
  end
1131
1381
 
1132
1382
  # Update all values using the entries in the hash, ignoring any setting of
1133
- # allowed_columns or restricted columns in the model.
1383
+ # +allowed_columns+ or +restricted_columns+ in the model.
1134
1384
  #
1135
1385
  # Artist.set_restricted_columns(:name)
1136
1386
  # artist.update_all(:name=>'Jim') # UPDATE artists SET name = 'Jim' WHERE (id = 1)
@@ -1139,28 +1389,30 @@ module Sequel
1139
1389
  end
1140
1390
 
1141
1391
  # Update all values using the entries in the hash, except for the keys
1142
- # given in except.
1392
+ # given in except. You should probably use +update_fields+ or +update_only+
1393
+ # instead of this method, as blacklist approaches to security are a bad idea.
1143
1394
  #
1144
1395
  # artist.update_except({:name=>'Jim'}, :hometown) # UPDATE artists SET name = 'Jim' WHERE (id = 1)
1145
1396
  def update_except(hash, *except)
1146
1397
  update_restricted(hash, false, except.flatten)
1147
1398
  end
1148
1399
 
1149
- # Update the instances values by calling +set_fields+ with the +hash+
1150
- # and +fields+, then save any changes to the record. Returns self.
1400
+ # Update the instances values by calling +set_fields+ with the arguments, then
1401
+ # saves any changes to the record. Returns self.
1151
1402
  #
1152
- # artist.update_fields({:name=>'Jim'}, :name)
1403
+ # artist.update_fields({:name=>'Jim'}, [:name])
1153
1404
  # # UPDATE artists SET name = 'Jim' WHERE (id = 1)
1154
1405
  #
1155
- # artist.update_fields({:hometown=>'LA'}, :name)
1406
+ # artist.update_fields({:hometown=>'LA'}, [:name])
1156
1407
  # # UPDATE artists SET name = NULL WHERE (id = 1)
1157
- def update_fields(hash, fields)
1158
- set_fields(hash, fields)
1408
+ def update_fields(hash, fields, opts=nil)
1409
+ set_fields(hash, fields, opts)
1159
1410
  save_changes
1160
1411
  end
1161
1412
 
1162
1413
  # Update the values using the entries in the hash, only if the key
1163
- # is included in only.
1414
+ # is included in only. It may be a better idea to use +update_fields+
1415
+ # instead of this method.
1164
1416
  #
1165
1417
  # artist.update_only({:name=>'Jim'}, :name)
1166
1418
  # # UPDATE artists SET name = 'Jim' WHERE (id = 1)
@@ -1185,21 +1437,15 @@ module Sequel
1185
1437
  # artist(:name=>'Invalid').valid? # => false
1186
1438
  # artist.errors.full_messages # => ['name cannot be Invalid']
1187
1439
  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?
1440
+ _valid?(false, opts)
1196
1441
  end
1197
1442
 
1198
1443
  private
1199
1444
 
1200
- # Actually do the deletion of the object's dataset.
1445
+ # Do the deletion of the object's dataset, and check that the row
1446
+ # was actually deleted.
1201
1447
  def _delete
1202
- n = _delete_dataset.delete
1448
+ n = _delete_without_checking
1203
1449
  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
1450
  n
1205
1451
  end
@@ -1210,12 +1456,33 @@ module Sequel
1210
1456
  this
1211
1457
  end
1212
1458
 
1459
+ # Actually do the deletion of the object's dataset. Return the
1460
+ # number of rows modified.
1461
+ def _delete_without_checking
1462
+ if sql = (m = model).fast_instance_delete_sql
1463
+ sql = sql.dup
1464
+ (ds = m.dataset).literal_append(sql, pk)
1465
+ ds.with_sql_delete(sql)
1466
+ else
1467
+ _delete_dataset.delete
1468
+ end
1469
+ end
1470
+
1213
1471
  # Internal destroy method, separted from destroy to
1214
1472
  # allow running inside a transaction
1215
1473
  def _destroy(opts)
1216
- raise_hook_failure(:destroy) if before_destroy == false
1217
- _destroy_delete
1218
- after_destroy
1474
+ sh = {:server=>this_server}
1475
+ db.after_rollback(sh){after_destroy_rollback} if uacr = use_after_commit_rollback
1476
+ called = false
1477
+ around_destroy do
1478
+ called = true
1479
+ raise_hook_failure(:destroy) if before_destroy == false
1480
+ _destroy_delete
1481
+ after_destroy
1482
+ true
1483
+ end
1484
+ raise_hook_failure(:destroy) unless called
1485
+ db.after_commit(sh){after_destroy_commit} if uacr
1219
1486
  self
1220
1487
  end
1221
1488
 
@@ -1230,68 +1497,107 @@ module Sequel
1230
1497
  # the record should be refreshed from the database.
1231
1498
  def _insert
1232
1499
  ds = _insert_dataset
1233
- if ds.respond_to?(:insert_select) and h = ds.insert_select(@values)
1234
- @values = h
1500
+ if !ds.opts[:select] and ds.supports_insert_select? and h = _insert_select_raw(ds)
1501
+ set_values(h)
1235
1502
  nil
1236
1503
  else
1237
- iid = ds.insert(@values)
1504
+ iid = _insert_raw(ds)
1238
1505
  # if we have a regular primary key and it's not set in @values,
1239
1506
  # we assume it's the last inserted id
1240
- if (pk = autoincrementing_primary_key) && pk.is_a?(Symbol) && !@values[pk]
1241
- @values[pk] = iid
1507
+ if (pk = autoincrementing_primary_key) && pk.is_a?(Symbol) && !(vals = @values)[pk]
1508
+ vals[pk] = iid
1242
1509
  end
1243
1510
  pk
1244
1511
  end
1245
1512
  end
1246
-
1513
+
1247
1514
  # The dataset to use when inserting a new object. The same as the model's
1248
1515
  # dataset by default.
1249
1516
  def _insert_dataset
1250
- model.dataset
1517
+ use_server(model.instance_dataset)
1251
1518
  end
1252
1519
 
1520
+ # Insert into the given dataset and return the primary key created (if any).
1521
+ def _insert_raw(ds)
1522
+ ds.insert(@values)
1523
+ end
1524
+
1525
+ # Insert into the given dataset and return the hash of column values.
1526
+ def _insert_select_raw(ds)
1527
+ ds.insert_select(@values)
1528
+ end
1529
+
1253
1530
  # Refresh using a particular dataset, used inside save to make sure the same server
1254
1531
  # is used for reading newly inserted values from the database
1255
1532
  def _refresh(dataset)
1256
- set_values(dataset.first || raise(Error, "Record not found"))
1533
+ set_values(_refresh_get(dataset) || raise(Error, "Record not found"))
1257
1534
  changed_columns.clear
1258
- self
1535
+ end
1536
+
1537
+ # Get the row of column data from the database.
1538
+ def _refresh_get(dataset)
1539
+ dataset.first
1259
1540
  end
1260
1541
 
1261
1542
  # Internal version of save, split from save to allow running inside
1262
1543
  # it's own transaction.
1263
1544
  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
1545
+ sh = {:server=>this_server}
1546
+ db.after_rollback(sh){after_rollback} if uacr = use_after_commit_rollback
1547
+ was_new = false
1548
+ pk = nil
1549
+ called_save = false
1550
+ called_cu = false
1551
+ around_save do
1552
+ called_save = true
1553
+ raise_hook_failure(:save) if before_save == false
1554
+ if new?
1555
+ was_new = true
1556
+ around_create do
1557
+ called_cu = true
1558
+ raise_hook_failure(:create) if before_create == false
1559
+ pk = _insert
1560
+ @this = nil
1561
+ @new = false
1562
+ @was_new = true
1563
+ after_create
1564
+ true
1565
+ end
1566
+ raise_hook_failure(:create) unless called_cu
1567
+ else
1568
+ around_update do
1569
+ called_cu = true
1570
+ raise_hook_failure(:update) if before_update == false
1571
+ if columns.empty?
1572
+ @columns_updated = if opts[:changed]
1573
+ @values.reject{|k,v| !changed_columns.include?(k)}
1574
+ else
1575
+ _save_update_all_columns_hash
1576
+ end
1577
+ changed_columns.clear
1578
+ else # update only the specified columns
1579
+ @columns_updated = @values.reject{|k, v| !columns.include?(k)}
1580
+ changed_columns.reject!{|c| columns.include?(c)}
1581
+ end
1582
+ _update_columns(@columns_updated)
1583
+ @this = nil
1584
+ after_update
1585
+ true
1586
+ end
1587
+ raise_hook_failure(:update) unless called_cu
1588
+ end
1272
1589
  after_save
1590
+ true
1591
+ end
1592
+ raise_hook_failure(:save) unless called_save
1593
+ if was_new
1273
1594
  @was_new = nil
1274
1595
  pk ? _save_refresh : changed_columns.clear
1275
1596
  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
1597
  @columns_updated = nil
1293
1598
  end
1294
1599
  @modified = false
1600
+ db.after_commit(sh){after_commit} if uacr
1295
1601
  self
1296
1602
  end
1297
1603
 
@@ -1321,9 +1627,10 @@ module Sequel
1321
1627
  _update(columns) unless columns.empty?
1322
1628
  end
1323
1629
 
1324
- # Update this instance's dataset with the supplied column hash.
1630
+ # Update this instance's dataset with the supplied column hash,
1631
+ # checking that only a single row was modified.
1325
1632
  def _update(columns)
1326
- n = _update_dataset.update(columns)
1633
+ n = _update_without_checking(columns)
1327
1634
  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
1635
  n
1329
1636
  end
@@ -1334,7 +1641,47 @@ module Sequel
1334
1641
  this
1335
1642
  end
1336
1643
 
1337
- # If not raising on failure, check for BeforeHookFailed
1644
+ # Update this instances dataset with the supplied column hash.
1645
+ def _update_without_checking(columns)
1646
+ _update_dataset.update(columns)
1647
+ end
1648
+
1649
+ # Internal validation method. If +raise_errors+ is +true+, hook
1650
+ # failures will be raised as HookFailure exceptions. If it is
1651
+ # +false+, +false+ will be returned instead.
1652
+ def _valid?(raise_errors, opts)
1653
+ return errors.empty? if frozen?
1654
+ errors.clear
1655
+ called = false
1656
+ error = false
1657
+ around_validation do
1658
+ called = true
1659
+ if before_validation == false
1660
+ if raise_errors
1661
+ raise_hook_failure(:validation)
1662
+ else
1663
+ error = true
1664
+ end
1665
+ false
1666
+ else
1667
+ validate
1668
+ after_validation
1669
+ errors.empty?
1670
+ end
1671
+ end
1672
+ error = true unless called
1673
+ if error
1674
+ if raise_errors
1675
+ raise_hook_failure(:validation)
1676
+ else
1677
+ false
1678
+ end
1679
+ else
1680
+ errors.empty?
1681
+ end
1682
+ end
1683
+
1684
+ # If not raising on failure, check for HookFailed
1338
1685
  # being raised by yielding and swallow it.
1339
1686
  def checked_save_failure(opts)
1340
1687
  if raise_on_failure?(opts)
@@ -1342,7 +1689,7 @@ module Sequel
1342
1689
  else
1343
1690
  begin
1344
1691
  yield
1345
- rescue BeforeHookFailed
1692
+ rescue HookFailed
1346
1693
  nil
1347
1694
  end
1348
1695
  end
@@ -1350,7 +1697,21 @@ module Sequel
1350
1697
 
1351
1698
  # If transactions should be used, wrap the yield in a transaction block.
1352
1699
  def checked_transaction(opts={})
1353
- use_transaction?(opts) ? db.transaction(opts){yield} : yield
1700
+ use_transaction?(opts) ? db.transaction({:server=>this_server}.merge(opts)){yield} : yield
1701
+ end
1702
+
1703
+ # Change the value of the column to given value, recording the change.
1704
+ def change_column_value(column, value)
1705
+ cc = changed_columns
1706
+ cc << column unless cc.include?(column)
1707
+ @values[column] = value
1708
+ end
1709
+
1710
+ # Set the columns with the given hash. By default, the same as +set+, but
1711
+ # exists so it can be overridden. This is called only for new records, before
1712
+ # changed_columns is cleared.
1713
+ def initialize_set(h)
1714
+ set(h) unless h.empty?
1354
1715
  end
1355
1716
 
1356
1717
  # Default inspection output for the values hash, overwrite to change what #inspect displays.
@@ -1369,11 +1730,12 @@ module Sequel
1369
1730
  # Raise an error appropriate to the hook type. May be swallowed by
1370
1731
  # checked_save_failure depending on the raise_on_failure? setting.
1371
1732
  def raise_hook_failure(type)
1372
- raise BeforeHookFailed, "one of the before_#{type} hooks returned false"
1733
+ raise HookFailed, "one of the before_#{type} hooks returned false"
1373
1734
  end
1374
1735
 
1375
1736
  # Set the columns, filtered by the only and except arrays.
1376
1737
  def set_restricted(hash, only, except)
1738
+ return self if hash.empty?
1377
1739
  meths = if only.nil? && except.nil? && !@singleton_setter_added
1378
1740
  model.setter_methods
1379
1741
  else
@@ -1385,17 +1747,21 @@ module Sequel
1385
1747
  if meths.include?(m)
1386
1748
  send(m, v)
1387
1749
  elsif strict
1388
- raise Error, "method #{m} doesn't exist or access is restricted to it"
1750
+ # Avoid using respond_to? or creating symbols from user input
1751
+ if public_methods.map{|s| s.to_s}.include?(m)
1752
+ if Array(model.primary_key).map{|s| s.to_s}.member?(k.to_s) && model.restrict_primary_key?
1753
+ raise Error, "#{k} is a restricted primary key"
1754
+ else
1755
+ raise Error, "#{k} is a restricted column"
1756
+ end
1757
+ else
1758
+ raise Error, "method #{m} doesn't exist"
1759
+ end
1389
1760
  end
1390
1761
  end
1391
1762
  self
1392
1763
  end
1393
1764
 
1394
- # Replace the current values with hash.
1395
- def set_values(hash)
1396
- @values = hash
1397
- end
1398
-
1399
1765
  # Returns all methods that can be used for attribute
1400
1766
  # assignment (those that end with =), modified by the only
1401
1767
  # and except arguments:
@@ -1424,13 +1790,25 @@ module Sequel
1424
1790
  meths
1425
1791
  end
1426
1792
  end
1793
+
1794
+ # The server/shard that the model object's dataset uses, or :default if the
1795
+ # model object's dataset does not have an associated shard.
1796
+ def this_server
1797
+ if (s = @server)
1798
+ s
1799
+ elsif (t = @this)
1800
+ t.opts[:server] || :default
1801
+ else
1802
+ model.dataset.opts[:server] || :default
1803
+ end
1804
+ end
1427
1805
 
1428
1806
  # Typecast the value to the column's type if typecasting. Calls the database's
1429
1807
  # typecast_value method, so database adapters can override/augment the handling
1430
1808
  # for database specific column types.
1431
1809
  def typecast_value(column, value)
1432
1810
  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])
1811
+ value = nil if '' == value and typecast_empty_string_to_nil and col_schema[:type] and ![:string, :blob].include?(col_schema[:type])
1434
1812
  raise(InvalidValue, "nil/NULL is not allowed for the #{column} column") if raise_on_typecast_failure && value.nil? && (col_schema[:allow_null] == false)
1435
1813
  begin
1436
1814
  model.db.typecast_value(col_schema[:type], value)
@@ -1444,6 +1822,11 @@ module Sequel
1444
1822
  set_restricted(hash, only, except)
1445
1823
  save_changes
1446
1824
  end
1825
+
1826
+ # Set the given dataset to use the current object's shard.
1827
+ def use_server(ds)
1828
+ @server ? ds.server(@server) : ds
1829
+ end
1447
1830
 
1448
1831
  # Whether to use a transaction for this action. If the :transaction
1449
1832
  # option is present in the hash, use that, otherwise, fallback to the
@@ -1461,6 +1844,18 @@ module Sequel
1461
1844
  # Artist.dataset.model # => Artist
1462
1845
  attr_accessor :model
1463
1846
 
1847
+ # Assume if a single integer is given that it is a lookup by primary
1848
+ # key, and call with_pk with the argument.
1849
+ #
1850
+ # Artist.dataset[1] # SELECT * FROM artists WHERE (id = 1) LIMIT 1
1851
+ def [](*args)
1852
+ if args.length == 1 && (i = args.at(0)) && i.is_a?(Integer)
1853
+ with_pk(i)
1854
+ else
1855
+ super
1856
+ end
1857
+ end
1858
+
1464
1859
  # Destroy each row in the dataset by instantiating it and then calling
1465
1860
  # destroy on the resulting model object. This isn't as fast as deleting
1466
1861
  # the dataset, which does a single SQL call, but this runs any destroy
@@ -1472,7 +1867,7 @@ module Sequel
1472
1867
  # # ...
1473
1868
  def destroy
1474
1869
  pr = proc{all{|r| r.destroy}.length}
1475
- model.use_transactions ? @db.transaction(&pr) : pr.call
1870
+ model.use_transactions ? @db.transaction(:server=>opts[:server], &pr) : pr.call
1476
1871
  end
1477
1872
 
1478
1873
  # This allows you to call +to_hash+ without any arguments, which will
@@ -1491,6 +1886,19 @@ module Sequel
1491
1886
  super(pk, value_column)
1492
1887
  end
1493
1888
  end
1889
+
1890
+ # Given a primary key value, return the first record in the dataset with that primary key
1891
+ # value.
1892
+ #
1893
+ # # Single primary key
1894
+ # Artist.dataset.with_pk(1) # SELECT * FROM artists WHERE (id = 1) LIMIT 1
1895
+ #
1896
+ # # Composite primary key
1897
+ # Artist.dataset.with_pk([1, 2]) # SELECT * FROM artists
1898
+ # # WHERE ((id1 = 1) AND (id2 = 2)) LIMIT 1
1899
+ def with_pk(pk)
1900
+ first(model.qualified_primary_key_hash(pk))
1901
+ end
1494
1902
  end
1495
1903
 
1496
1904
  extend ClassMethods