sequel 3.21.0 → 3.31.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 (225) hide show
  1. data/CHANGELOG +485 -3
  2. data/README.rdoc +20 -6
  3. data/Rakefile +37 -19
  4. data/bin/sequel +1 -5
  5. data/doc/association_basics.rdoc +249 -43
  6. data/doc/dataset_basics.rdoc +4 -4
  7. data/doc/dataset_filtering.rdoc +8 -0
  8. data/doc/mass_assignment.rdoc +54 -0
  9. data/doc/migration.rdoc +15 -538
  10. data/doc/model_hooks.rdoc +72 -27
  11. data/doc/opening_databases.rdoc +89 -50
  12. data/doc/prepared_statements.rdoc +40 -13
  13. data/doc/reflection.rdoc +8 -2
  14. data/doc/release_notes/3.22.0.txt +39 -0
  15. data/doc/release_notes/3.23.0.txt +172 -0
  16. data/doc/release_notes/3.24.0.txt +420 -0
  17. data/doc/release_notes/3.25.0.txt +88 -0
  18. data/doc/release_notes/3.26.0.txt +88 -0
  19. data/doc/release_notes/3.27.0.txt +82 -0
  20. data/doc/release_notes/3.28.0.txt +304 -0
  21. data/doc/release_notes/3.29.0.txt +459 -0
  22. data/doc/release_notes/3.30.0.txt +135 -0
  23. data/doc/release_notes/3.31.0.txt +146 -0
  24. data/doc/schema_modification.rdoc +547 -0
  25. data/doc/sharding.rdoc +7 -1
  26. data/doc/testing.rdoc +129 -0
  27. data/doc/transactions.rdoc +137 -0
  28. data/doc/virtual_rows.rdoc +2 -2
  29. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  30. data/lib/sequel/adapters/ado.rb +40 -18
  31. data/lib/sequel/adapters/amalgalite.rb +17 -9
  32. data/lib/sequel/adapters/db2.rb +175 -86
  33. data/lib/sequel/adapters/dbi.rb +15 -15
  34. data/lib/sequel/adapters/do/mysql.rb +5 -7
  35. data/lib/sequel/adapters/do/postgres.rb +0 -5
  36. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  37. data/lib/sequel/adapters/do.rb +17 -36
  38. data/lib/sequel/adapters/firebird.rb +27 -208
  39. data/lib/sequel/adapters/ibmdb.rb +448 -0
  40. data/lib/sequel/adapters/informix.rb +6 -23
  41. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  42. data/lib/sequel/adapters/jdbc/db2.rb +45 -0
  43. data/lib/sequel/adapters/jdbc/derby.rb +247 -0
  44. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  45. data/lib/sequel/adapters/jdbc/h2.rb +56 -29
  46. data/lib/sequel/adapters/jdbc/hsqldb.rb +170 -0
  47. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  48. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  49. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  50. data/lib/sequel/adapters/jdbc/mysql.rb +11 -11
  51. data/lib/sequel/adapters/jdbc/oracle.rb +81 -25
  52. data/lib/sequel/adapters/jdbc/postgresql.rb +9 -13
  53. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -6
  54. data/lib/sequel/adapters/jdbc/sqlserver.rb +71 -0
  55. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  56. data/lib/sequel/adapters/jdbc.rb +231 -78
  57. data/lib/sequel/adapters/mock.rb +335 -0
  58. data/lib/sequel/adapters/mysql.rb +82 -184
  59. data/lib/sequel/adapters/mysql2.rb +26 -25
  60. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  61. data/lib/sequel/adapters/odbc/mssql.rb +1 -6
  62. data/lib/sequel/adapters/odbc.rb +28 -9
  63. data/lib/sequel/adapters/openbase.rb +3 -5
  64. data/lib/sequel/adapters/oracle.rb +351 -53
  65. data/lib/sequel/adapters/postgres.rb +194 -33
  66. data/lib/sequel/adapters/shared/access.rb +32 -9
  67. data/lib/sequel/adapters/shared/db2.rb +321 -0
  68. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  69. data/lib/sequel/adapters/shared/informix.rb +53 -0
  70. data/lib/sequel/adapters/shared/mssql.rb +231 -106
  71. data/lib/sequel/adapters/shared/mysql.rb +181 -63
  72. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +231 -41
  74. data/lib/sequel/adapters/shared/postgres.rb +214 -82
  75. data/lib/sequel/adapters/shared/progress.rb +4 -7
  76. data/lib/sequel/adapters/shared/sqlite.rb +201 -57
  77. data/lib/sequel/adapters/sqlite.rb +29 -21
  78. data/lib/sequel/adapters/swift/mysql.rb +12 -7
  79. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  80. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  81. data/lib/sequel/adapters/swift.rb +5 -5
  82. data/lib/sequel/adapters/tinytds.rb +160 -19
  83. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  84. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  85. data/lib/sequel/ast_transformer.rb +190 -0
  86. data/lib/sequel/connection_pool/threaded.rb +3 -2
  87. data/lib/sequel/connection_pool.rb +1 -1
  88. data/lib/sequel/core.rb +46 -5
  89. data/lib/sequel/database/connecting.rb +5 -6
  90. data/lib/sequel/database/dataset.rb +4 -4
  91. data/lib/sequel/database/dataset_defaults.rb +59 -1
  92. data/lib/sequel/database/logging.rb +1 -1
  93. data/lib/sequel/database/misc.rb +104 -19
  94. data/lib/sequel/database/query.rb +153 -58
  95. data/lib/sequel/database/schema_generator.rb +17 -7
  96. data/lib/sequel/database/schema_methods.rb +59 -29
  97. data/lib/sequel/dataset/actions.rb +231 -76
  98. data/lib/sequel/dataset/features.rb +91 -8
  99. data/lib/sequel/dataset/graph.rb +38 -11
  100. data/lib/sequel/dataset/misc.rb +50 -23
  101. data/lib/sequel/dataset/mutation.rb +5 -6
  102. data/lib/sequel/dataset/prepared_statements.rb +48 -27
  103. data/lib/sequel/dataset/query.rb +236 -43
  104. data/lib/sequel/dataset/sql.rb +665 -253
  105. data/lib/sequel/dataset.rb +8 -0
  106. data/lib/sequel/exceptions.rb +4 -0
  107. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  108. data/lib/sequel/extensions/migration.rb +6 -4
  109. data/lib/sequel/extensions/named_timezones.rb +5 -0
  110. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  111. data/lib/sequel/extensions/to_dot.rb +95 -83
  112. data/lib/sequel/model/associations.rb +1020 -337
  113. data/lib/sequel/model/base.rb +414 -132
  114. data/lib/sequel/model/errors.rb +1 -1
  115. data/lib/sequel/model/exceptions.rb +5 -1
  116. data/lib/sequel/model.rb +15 -8
  117. data/lib/sequel/plugins/association_pks.rb +22 -4
  118. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  119. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  120. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  121. data/lib/sequel/plugins/force_encoding.rb +6 -6
  122. data/lib/sequel/plugins/identity_map.rb +114 -7
  123. data/lib/sequel/plugins/many_through_many.rb +65 -7
  124. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  125. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  126. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  127. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  128. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  129. data/lib/sequel/plugins/serialization.rb +88 -44
  130. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  131. data/lib/sequel/plugins/sharding.rb +21 -54
  132. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  133. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  134. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  135. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  136. data/lib/sequel/sql.rb +109 -52
  137. data/lib/sequel/timezones.rb +57 -38
  138. data/lib/sequel/version.rb +1 -1
  139. data/spec/adapters/db2_spec.rb +146 -0
  140. data/spec/adapters/mssql_spec.rb +111 -29
  141. data/spec/adapters/mysql_spec.rb +135 -123
  142. data/spec/adapters/oracle_spec.rb +49 -77
  143. data/spec/adapters/postgres_spec.rb +267 -78
  144. data/spec/adapters/spec_helper.rb +6 -5
  145. data/spec/adapters/sqlite_spec.rb +35 -21
  146. data/spec/core/connection_pool_spec.rb +71 -92
  147. data/spec/core/core_sql_spec.rb +21 -30
  148. data/spec/core/database_spec.rb +788 -512
  149. data/spec/core/dataset_spec.rb +1073 -1053
  150. data/spec/core/expression_filters_spec.rb +159 -42
  151. data/spec/core/mock_adapter_spec.rb +419 -0
  152. data/spec/core/object_graph_spec.rb +86 -114
  153. data/spec/core/schema_generator_spec.rb +3 -3
  154. data/spec/core/schema_spec.rb +298 -38
  155. data/spec/core/spec_helper.rb +6 -48
  156. data/spec/extensions/association_autoreloading_spec.rb +1 -10
  157. data/spec/extensions/association_dependencies_spec.rb +2 -12
  158. data/spec/extensions/association_pks_spec.rb +66 -32
  159. data/spec/extensions/caching_spec.rb +23 -50
  160. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  161. data/spec/extensions/columns_introspection_spec.rb +91 -0
  162. data/spec/extensions/composition_spec.rb +18 -13
  163. data/spec/extensions/dataset_associations_spec.rb +199 -0
  164. data/spec/extensions/defaults_setter_spec.rb +64 -0
  165. data/spec/extensions/hook_class_methods_spec.rb +65 -91
  166. data/spec/extensions/identity_map_spec.rb +114 -22
  167. data/spec/extensions/instance_filters_spec.rb +11 -21
  168. data/spec/extensions/instance_hooks_spec.rb +53 -0
  169. data/spec/extensions/json_serializer_spec.rb +4 -5
  170. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  171. data/spec/extensions/list_spec.rb +17 -39
  172. data/spec/extensions/many_through_many_spec.rb +283 -250
  173. data/spec/extensions/migration_spec.rb +35 -32
  174. data/spec/extensions/named_timezones_spec.rb +23 -3
  175. data/spec/extensions/nested_attributes_spec.rb +97 -88
  176. data/spec/extensions/optimistic_locking_spec.rb +9 -20
  177. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  178. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  179. data/spec/extensions/prepared_statements_spec.rb +53 -0
  180. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  181. data/spec/extensions/pretty_table_spec.rb +1 -6
  182. data/spec/extensions/rcte_tree_spec.rb +41 -43
  183. data/spec/extensions/schema_dumper_spec.rb +5 -8
  184. data/spec/extensions/schema_spec.rb +13 -21
  185. data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
  186. data/spec/extensions/serialization_spec.rb +72 -43
  187. data/spec/extensions/sharding_spec.rb +66 -140
  188. data/spec/extensions/single_table_inheritance_spec.rb +18 -29
  189. data/spec/extensions/spec_helper.rb +13 -61
  190. data/spec/extensions/sql_expr_spec.rb +20 -60
  191. data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
  192. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  193. data/spec/extensions/timestamps_spec.rb +6 -6
  194. data/spec/extensions/to_dot_spec.rb +4 -7
  195. data/spec/extensions/touch_spec.rb +13 -14
  196. data/spec/extensions/tree_spec.rb +11 -26
  197. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  198. data/spec/extensions/update_primary_key_spec.rb +30 -24
  199. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  200. data/spec/extensions/validation_helpers_spec.rb +16 -35
  201. data/spec/extensions/xml_serializer_spec.rb +16 -4
  202. data/spec/integration/associations_test.rb +537 -21
  203. data/spec/integration/database_test.rb +4 -2
  204. data/spec/integration/dataset_test.rb +461 -65
  205. data/spec/integration/eager_loader_test.rb +19 -21
  206. data/spec/integration/model_test.rb +80 -1
  207. data/spec/integration/plugin_test.rb +304 -116
  208. data/spec/integration/prepared_statement_test.rb +212 -122
  209. data/spec/integration/schema_test.rb +161 -30
  210. data/spec/integration/spec_helper.rb +39 -30
  211. data/spec/integration/timezone_test.rb +38 -12
  212. data/spec/integration/transaction_test.rb +172 -5
  213. data/spec/integration/type_test.rb +17 -3
  214. data/spec/model/association_reflection_spec.rb +201 -7
  215. data/spec/model/associations_spec.rb +865 -662
  216. data/spec/model/base_spec.rb +275 -95
  217. data/spec/model/dataset_methods_spec.rb +7 -27
  218. data/spec/model/eager_loading_spec.rb +677 -676
  219. data/spec/model/hooks_spec.rb +309 -67
  220. data/spec/model/model_spec.rb +222 -168
  221. data/spec/model/plugins_spec.rb +24 -13
  222. data/spec/model/record_spec.rb +381 -218
  223. data/spec/model/spec_helper.rb +13 -71
  224. data/spec/model/validations_spec.rb +11 -0
  225. metadata +115 -73
@@ -11,7 +11,7 @@ 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
16
 
17
17
  # Array of modules that extend this model's dataset. Stored
@@ -23,7 +23,7 @@ module Sequel
23
23
  # stored so when the dataset changes, methods defined with def_dataset_method
24
24
  # will be applied to the new dataset.
25
25
  attr_reader :dataset_methods
26
- #
26
+
27
27
  # Array of plugin modules loaded by this class
28
28
  #
29
29
  # Sequel::Model.plugins
@@ -105,6 +105,17 @@ module Sequel
105
105
  args = args.first if (args.size == 1)
106
106
  args.is_a?(Hash) ? dataset[args] : primary_key_lookup(args)
107
107
  end
108
+
109
+ # Initializes a model instance as an existing record. This constructor is
110
+ # used by Sequel to initialize model instances when fetching records.
111
+ # Requires that values be a hash where all keys are symbols. It
112
+ # probably should not be used by external code.
113
+ def call(values)
114
+ o = allocate
115
+ o.set_values(values)
116
+ o.after_initialize
117
+ o
118
+ end
108
119
 
109
120
  # Clear the setter_methods cache
110
121
  def clear_setter_methods_cache
@@ -148,6 +159,36 @@ module Sequel
148
159
  def dataset=(ds)
149
160
  set_dataset(ds)
150
161
  end
162
+
163
+ # Extend the dataset with a module, similar to adding
164
+ # a plugin with the methods defined in DatasetMethods. If a block
165
+ # is given, an anonymous module is created and the module_evaled, otherwise
166
+ # the argument should be a module. Returns the module given or the anonymous
167
+ # module created.
168
+ #
169
+ # Artist.dataset_module Sequel::ColumnsIntrospection
170
+ #
171
+ # Artist.dataset_module do
172
+ # def foo
173
+ # :bar
174
+ # end
175
+ # end
176
+ # Artist.dataset.foo
177
+ # # => :bar
178
+ # Artist.foo
179
+ # # => :bar
180
+ def dataset_module(mod = nil)
181
+ if mod
182
+ raise Error, "can't provide both argument and block to Model.dataset_module" if block_given?
183
+ dataset_extend(mod)
184
+ mod
185
+ else
186
+ @dataset_module ||= Module.new
187
+ @dataset_module.module_eval(&Proc.new) if block_given?
188
+ dataset_extend(@dataset_module)
189
+ @dataset_module
190
+ end
191
+ end
151
192
 
152
193
  # Returns the database associated with the Model class.
153
194
  # If this model doesn't have a database associated with it,
@@ -162,7 +203,7 @@ module Sequel
162
203
  def db
163
204
  return @db if @db
164
205
  @db = self == Model ? DATABASES.first : superclass.db
165
- raise(Error, "No database associated with #{self}") unless @db
206
+ raise(Error, "No database associated with #{self}: have you called Sequel.connect or #{self}.db= ?") unless @db
166
207
  @db
167
208
  end
168
209
 
@@ -193,6 +234,16 @@ module Sequel
193
234
  @db_schema ||= get_db_schema
194
235
  end
195
236
 
237
+ # Create a column alias, where the column methods have one name, but the underlying storage uses a
238
+ # different name.
239
+ def def_column_alias(meth, column)
240
+ clear_setter_methods_cache
241
+ overridable_methods_module.module_eval do
242
+ define_method(meth){self[column]}
243
+ define_method("#{meth}="){|v| self[column] = v}
244
+ end
245
+ end
246
+
196
247
  # 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
248
  # the given block. Also define a class method on the model that calls the
198
249
  # dataset method. Stores the method name and block so that it can be reapplied if the model's
@@ -217,9 +268,30 @@ module Sequel
217
268
  @dataset_methods[meth] = block
218
269
  dataset.meta_def(meth, &block) if @dataset
219
270
  end
220
- args.each{|arg| instance_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__) unless respond_to?(arg)}
271
+ args.each do |arg|
272
+ if arg.to_s =~ NORMAL_METHOD_NAME_REGEXP
273
+ instance_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__) unless respond_to?(arg, true)
274
+ else
275
+ def_model_dataset_method_block(arg)
276
+ end
277
+ end
221
278
  end
222
-
279
+
280
+ module_eval(if RUBY_VERSION < '1.8.7'
281
+ <<-END
282
+ def def_model_dataset_method_block(arg)
283
+ meta_def(arg){|*args| dataset.send(arg, *args)}
284
+ end
285
+ END
286
+ else
287
+ <<-END
288
+ def def_model_dataset_method_block(arg)
289
+ meta_def(arg){|*args, &block| dataset.send(arg, *args, &block)}
290
+ end
291
+ END
292
+ end, __FILE__, __LINE__ - 4)
293
+ private :def_model_dataset_method_block
294
+
223
295
  # Finds a single record according to the supplied filter.
224
296
  # You are encouraged to use Model.[] or Model.first instead of this method.
225
297
  #
@@ -248,6 +320,13 @@ module Sequel
248
320
  find(cond) || create(cond, &block)
249
321
  end
250
322
 
323
+ # Clear the setter_methods cache when a module is included, as it
324
+ # may contain setter methods.
325
+ def include(mod)
326
+ clear_setter_methods_cache
327
+ super
328
+ end
329
+
251
330
  # If possible, set the dataset for the model subclass as soon as it
252
331
  # is created. Also, make sure the inherited class instance variables
253
332
  # are copied into the subclass.
@@ -271,7 +350,8 @@ module Sequel
271
350
  db
272
351
  begin
273
352
  if self == Model || !@dataset
274
- subclass.set_dataset(subclass.implicit_table_name) unless subclass.name.empty?
353
+ n = subclass.name
354
+ subclass.set_dataset(subclass.implicit_table_name) unless n.nil? || n.empty?
275
355
  elsif @dataset
276
356
  subclass.set_dataset(@dataset.clone, :inherited=>true)
277
357
  end
@@ -290,12 +370,9 @@ module Sequel
290
370
  pluralize(underscore(demodulize(name))).to_sym
291
371
  end
292
372
 
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.
373
+ # Calls #call with the values hash. Only for backwards compatibility.
297
374
  def load(values)
298
- new(values, true)
375
+ call(values)
299
376
  end
300
377
 
301
378
  # Clear the setter_methods cache when a setter method is added
@@ -325,18 +402,13 @@ module Sequel
325
402
  unless @plugins.include?(m)
326
403
  @plugins << m
327
404
  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
+ include(m::InstanceMethods) if plugin_module_defined?(m, :InstanceMethods)
406
+ extend(m::ClassMethods)if plugin_module_defined?(m, :ClassMethods)
407
+ dataset_extend(m::DatasetMethods) if plugin_module_defined?(m, :DatasetMethods)
336
408
  end
337
409
  m.configure(self, *args, &blk) if m.respond_to?(:configure)
338
410
  end
339
-
411
+
340
412
  # Returns primary key attribute hash. If using a composite primary key
341
413
  # value such be an array with values for each primary key in the correct
342
414
  # order. For a standard primary key, value should be an object with a
@@ -389,8 +461,8 @@ module Sequel
389
461
  # setter methods (methods that end in =) that you want to be used during
390
462
  # mass assignment, they need to be listed here as well (without the =).
391
463
  #
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.
464
+ # It may be better to use a method such as +set_only+ or +set_fields+ that lets you specify
465
+ # the allowed fields per call.
394
466
  #
395
467
  # Artist.set_allowed_columns(:name, :hometown)
396
468
  # Artist.set(:name=>'Bob', :hometown=>'Sactown') # No Error
@@ -400,10 +472,12 @@ module Sequel
400
472
  @allowed_columns = cols
401
473
  end
402
474
 
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+.
475
+ # Sets the dataset associated with the Model class. +ds+ can be a +Symbol+,
476
+ # +LiteralString+, <tt>SQL::Identifier</tt>, <tt>SQL::QualifiedIdentifier</tt>,
477
+ # <tt>SQL::AliasedExpression</tt>
478
+ # (all specifying a table name in the current database), or a +Dataset+.
405
479
  # 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
480
+ # dataset. If a dataset is not used, a dataset is created from the current
407
481
  # database with the table name given. Other arguments raise an +Error+.
408
482
  # Returns self.
409
483
  #
@@ -418,17 +492,21 @@ module Sequel
418
492
  def set_dataset(ds, opts={})
419
493
  inherited = opts[:inherited]
420
494
  @dataset = case ds
421
- when Symbol
495
+ when Symbol, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression, LiteralString
422
496
  @simple_table = db.literal(ds)
423
- db[ds]
497
+ db.from(ds)
424
498
  when Dataset
425
- @simple_table = nil
499
+ @simple_table = if ds.send(:simple_select_all?)
500
+ ds.literal(ds.first_source_table)
501
+ else
502
+ nil
503
+ end
426
504
  @db = ds.db
427
505
  ds
428
506
  else
429
- raise(Error, "Model.set_dataset takes a Symbol or a Sequel::Dataset")
507
+ raise(Error, "Model.set_dataset takes one of the following classes as an argument: Symbol, LiteralString, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression, Dataset")
430
508
  end
431
- @dataset.row_proc = Proc.new{|r| load(r)}
509
+ @dataset.row_proc = self
432
510
  @require_modification = Sequel::Model.require_modification.nil? ? @dataset.provides_accurate_rows_matched? : Sequel::Model.require_modification
433
511
  if inherited
434
512
  @simple_table = superclass.simple_table
@@ -441,10 +519,11 @@ module Sequel
441
519
  check_non_connection_error{@db_schema = (inherited ? superclass.db_schema : get_db_schema)}
442
520
  self
443
521
  end
444
-
522
+
445
523
  # Sets the primary key for this model. You can use either a regular
446
524
  # or a composite primary key. To not use a primary key, set to nil
447
- # or use +no_primary_key+.
525
+ # or use +no_primary_key+. On most adapters, Sequel can automatically
526
+ # determine the primary key to use, so this method is not needed often.
448
527
  #
449
528
  # class Person < Sequel::Model
450
529
  # # regular key
@@ -458,7 +537,11 @@ module Sequel
458
537
  def set_primary_key(*key)
459
538
  clear_setter_methods_cache
460
539
  key = key.flatten
461
- @simple_pk = key.length == 1 ? db.literal(key.first) : nil
540
+ @simple_pk = if key.length == 1
541
+ (@dataset || db).literal(key.first)
542
+ else
543
+ nil
544
+ end
462
545
  @primary_key = (key.length == 1) ? key[0] : key
463
546
  end
464
547
 
@@ -468,10 +551,9 @@ module Sequel
468
551
  # If you have any virtual setter methods (methods that end in =) that you
469
552
  # want not to be used during mass assignment, they need to be listed here as well (without the =).
470
553
  #
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.
554
+ # It's generally a bad idea to rely on a blacklist approach for security. Using a whitelist
555
+ # approach such as set_allowed_columns or the instance level set_only or set_fields methods
556
+ # is usually a better choice. So use of this method is generally a bad idea.
475
557
  #
476
558
  # Artist.set_restricted_column(:records_sold)
477
559
  # Artist.set(:name=>'Bob', :hometown=>'Sactown') # No Error
@@ -511,6 +593,9 @@ module Sequel
511
593
  # 7 days ago.
512
594
  #
513
595
  # Both the args given and the block are passed to <tt>Dataset#filter</tt>.
596
+ #
597
+ # This method creates dataset methods that do not accept arguments. To create
598
+ # dataset methods that accept arguments, you have to use def_dataset_method.
514
599
  def subset(name, *args, &block)
515
600
  def_dataset_method(name){filter(*args, &block)}
516
601
  end
@@ -526,6 +611,7 @@ module Sequel
526
611
  end
527
612
 
528
613
  # Allow the setting of the primary key(s) when using the mass assignment methods.
614
+ # Using this method can open up security issues, be very careful before using it.
529
615
  #
530
616
  # Artist.set(:id=>1) # Error
531
617
  # Artist.unrestrict_primary_key
@@ -548,6 +634,16 @@ module Sequel
548
634
  end
549
635
  end
550
636
 
637
+ # Add the module to the class's dataset_method_modules. Extend the dataset with the
638
+ # module if the model has a dataset. Add dataset methods to the class for all
639
+ # public dataset methods.
640
+ def dataset_extend(mod)
641
+ dataset.extend(mod) if @dataset
642
+ dataset_method_modules << mod
643
+ meths = mod.public_instance_methods.reject{|x| NORMAL_METHOD_NAME_REGEXP !~ x.to_s}
644
+ def_dataset_method(*meths) unless meths.empty?
645
+ end
646
+
551
647
  # Create a column accessor for a column with a method name that is hard to use in ruby code.
552
648
  def def_bad_column_accessor(column)
553
649
  overridable_methods_module.module_eval do
@@ -578,10 +674,9 @@ module Sequel
578
674
  return nil unless @dataset
579
675
  schema_hash = {}
580
676
  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
677
  get_columns = proc{check_non_connection_error{columns} || []}
584
- if single_table && (schema_array = (db.schema(dataset.first_source_table, :reload=>reload) rescue nil))
678
+ schema_array = check_non_connection_error{db.schema(dataset, :reload=>reload)}
679
+ if schema_array
585
680
  schema_array.each{|k,v| schema_hash[k] = v}
586
681
  if ds_opts.include?(:select)
587
682
  # We don't remove the columns from the schema_hash,
@@ -589,6 +684,7 @@ module Sequel
589
684
  # even if they are not selected.
590
685
  cols = get_columns.call
591
686
  cols.each{|c| schema_hash[c] ||= {}}
687
+ def_column_accessor(*schema_hash.keys)
592
688
  else
593
689
  # Dataset is for a single table with all columns,
594
690
  # so set the columns based on the order they were
@@ -657,6 +753,15 @@ module Sequel
657
753
  Sequel::Plugins.const_get(module_name)
658
754
  end
659
755
 
756
+ # Check if the plugin module +plugin+ defines the constant named by +submod+.
757
+ def plugin_module_defined?(plugin, submod)
758
+ if RUBY_VERSION >= '1.9'
759
+ plugin.const_defined?(submod, false)
760
+ else
761
+ plugin.const_defined?(submod)
762
+ end
763
+ end
764
+
660
765
  # Find the row in the dataset that matches the primary key. Uses
661
766
  # a static SQL optimization if the table and primary key are simple.
662
767
  def primary_key_lookup(pk)
@@ -686,18 +791,19 @@ module Sequel
686
791
 
687
792
  # Sequel::Model instance methods that implement basic model functionality.
688
793
  #
689
- # * All of the methods in +HOOKS+ create instance methods that are called
794
+ # * All of the methods in +HOOKS+ and +AROUND_HOOKS+ create instance methods that are called
690
795
  # 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+.
796
+ # a model object, Sequel will call +around_destory+, which will call +before_destroy+, do
797
+ # the destroy, and then call +after_destroy+.
693
798
  # * The following instance_methods all call the class method of the same
694
- # name: columns, dataset, db, primary_key, db_schema.
799
+ # name: columns, db, primary_key, db_schema.
695
800
  # * 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
801
+ # to set values for the attribute. It also creates instance getters returning
697
802
  # the value of the setting. If the value has not yet been set, it
698
803
  # gets the default value from the class by calling the class method of the same name.
699
804
  module InstanceMethods
700
805
  HOOKS.each{|h| class_eval("def #{h}; end", __FILE__, __LINE__)}
806
+ AROUND_HOOKS.each{|h| class_eval("def #{h}; yield end", __FILE__, __LINE__)}
701
807
 
702
808
  # Define instance method(s) that calls class method(s) of the
703
809
  # same name, caching the result in an instance variable. Define
@@ -735,7 +841,7 @@ module Sequel
735
841
  #
736
842
  # Arguments:
737
843
  # values :: should be a hash to pass to set.
738
- # from_db :: should only be set by <tt>Model.load</tt>, forget it exists.
844
+ # from_db :: only for backwards compatibility, forget it exists.
739
845
  #
740
846
  # Artist.new(:name=>'Bob')
741
847
  #
@@ -744,13 +850,12 @@ module Sequel
744
850
  # end
745
851
  def initialize(values = {}, from_db = false)
746
852
  if from_db
747
- @new = false
748
853
  set_values(values)
749
854
  else
750
855
  @values = {}
751
856
  @new = true
752
857
  @modified = true
753
- set(values)
858
+ initialize_set(values)
754
859
  changed_columns.clear
755
860
  yield self if block_given?
756
861
  end
@@ -766,7 +871,7 @@ module Sequel
766
871
 
767
872
  # Sets the value for the given column. If typecasting is enabled for
768
873
  # 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
874
+ # If this is a new record or the typecasted value isn't the same
770
875
  # as the current value for the column, mark the column as changed.
771
876
  #
772
877
  # a = Artist.new
@@ -874,14 +979,25 @@ module Sequel
874
979
 
875
980
  # Returns true when current instance exists, false otherwise.
876
981
  # Generally an object that isn't new will exist unless it has
877
- # been deleted.
982
+ # been deleted. Uses a database query to check for existence,
983
+ # unless the model object is new, in which case this is always
984
+ # false.
878
985
  #
879
986
  # Artist[1].exists? # SELECT 1 FROM artists WHERE (id = 1)
880
987
  # # => true
988
+ # Artist.new.exists?
989
+ # # => false
881
990
  def exists?
882
- !this.get(1).nil?
991
+ new? ? false : !this.get(1).nil?
883
992
  end
884
993
 
994
+ # Ignore the model's setter method cache when this instances extends a module, as the
995
+ # module may contain setter methods.
996
+ def extend(mod)
997
+ @singleton_setter_added = true
998
+ super
999
+ end
1000
+
885
1001
  # Value that should be unique for objects with the same class and pk (if pk is not nil), or
886
1002
  # the same class and values (if pk is nil).
887
1003
  #
@@ -890,7 +1006,14 @@ module Sequel
890
1006
  # Artist.new.hash == Artist.new.hash # true
891
1007
  # Artist.new(:name=>'Bob').hash == Artist.new.hash # false
892
1008
  def hash
893
- [model, pk.nil? ? @values.sort_by{|k,v| k.to_s} : pk].hash
1009
+ case primary_key
1010
+ when Array
1011
+ [model, !pk.all? ? @values.sort_by{|k,v| k.to_s} : pk].hash
1012
+ when Symbol
1013
+ [model, pk.nil? ? @values.sort_by{|k,v| k.to_s} : pk].hash
1014
+ else
1015
+ [model, @values.sort_by{|k,v| k.to_s}].hash
1016
+ end
894
1017
  end
895
1018
 
896
1019
  # Returns value for the :id attribute, even if the primary key is
@@ -967,11 +1090,11 @@ module Sequel
967
1090
  # Artist.new.new? # => true
968
1091
  # Artist[1].new? # => false
969
1092
  def new?
970
- @new
1093
+ defined?(@new) ? @new : (@new = false)
971
1094
  end
972
1095
 
973
1096
  # Returns the primary key value identifying the model instance.
974
- # Raises an error if this model does not have a primary key.
1097
+ # Raises an +Error+ if this model does not have a primary key.
975
1098
  # If the model has a composite primary key, returns an array of values.
976
1099
  #
977
1100
  # Artist[1].pk # => 1
@@ -1016,26 +1139,31 @@ module Sequel
1016
1139
  #
1017
1140
  # If +save+ fails and either raise_on_save_failure or the
1018
1141
  # :raise_on_failure option is true, it raises ValidationFailed
1019
- # or BeforeHookFailed. Otherwise it returns nil.
1142
+ # or HookFailed. Otherwise it returns nil.
1020
1143
  #
1021
1144
  # If it succeeds, it returns self.
1022
1145
  #
1023
1146
  # You can provide an optional list of columns to update, in which
1024
- # case it only updates those columns.
1147
+ # case it only updates those columns, or a options hash.
1025
1148
  #
1026
1149
  # Takes the following options:
1027
1150
  #
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
1151
+ # :changed :: save all changed columns, instead of all columns or the columns given
1152
+ # :raise_on_failure :: set to true or false to override the current
1153
+ # +raise_on_save_failure+ setting
1154
+ # :server :: set the server/shard on the object before saving, and use that
1155
+ # server/shard in any transaction.
1156
+ # :transaction :: set to true or false to override the current
1157
+ # +use_transactions+ setting
1158
+ # :validate :: set to false to skip validation
1034
1159
  def save(*columns)
1035
1160
  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
1161
+ set_server(opts[:server]) if opts[:server]
1162
+ if opts[:validate] != false
1163
+ unless checked_save_failure(opts){_valid?(true, opts)}
1164
+ raise(ValidationFailed.new(errors)) if raise_on_failure?(opts)
1165
+ return
1166
+ end
1039
1167
  end
1040
1168
  checked_save_failure(opts){checked_transaction(opts){_save(columns, opts)}}
1041
1169
  end
@@ -1075,7 +1203,8 @@ module Sequel
1075
1203
  end
1076
1204
 
1077
1205
  # Set all values using the entries in the hash, except for the keys
1078
- # given in except.
1206
+ # given in except. You should probably use +set_fields+ or +set_only+
1207
+ # instead of this method, as blacklist approaches to security are a bad idea.
1079
1208
  #
1080
1209
  # artist.set_except({:name=>'Jim'}, :hometown)
1081
1210
  # artist.name # => 'Jim'
@@ -1086,10 +1215,10 @@ module Sequel
1086
1215
  # For each of the fields in the given array +fields+, call the setter
1087
1216
  # method with the value of that +hash+ entry for the field. Returns self.
1088
1217
  #
1089
- # artist.set_fields({:name=>'Jim'}, :name)
1218
+ # artist.set_fields({:name=>'Jim'}, [:name])
1090
1219
  # artist.name # => 'Jim'
1091
1220
  #
1092
- # artist.set_fields({:hometown=>'LA'}, :name)
1221
+ # artist.set_fields({:hometown=>'LA'}, [:name])
1093
1222
  # artist.name # => nil
1094
1223
  # artist.hometown # => 'Sac'
1095
1224
  def set_fields(hash, fields)
@@ -1098,16 +1227,31 @@ module Sequel
1098
1227
  end
1099
1228
 
1100
1229
  # Set the values using the entries in the hash, only if the key
1101
- # is included in only.
1230
+ # is included in only. It may be a better idea to use +set_fields+
1231
+ # instead of this method.
1102
1232
  #
1103
1233
  # artist.set_only({:name=>'Jim'}, :name)
1104
1234
  # artist.name # => 'Jim'
1105
1235
  #
1106
- # artist.set_only({:hometown=>'LA'}, :name) # Raise error
1236
+ # artist.set_only({:hometown=>'LA'}, :name) # Raise Error
1107
1237
  def set_only(hash, *only)
1108
1238
  set_restricted(hash, only.flatten, false)
1109
1239
  end
1110
1240
 
1241
+ # Set the shard that this object is tied to. Returns self.
1242
+ def set_server(s)
1243
+ @server = s
1244
+ @this.opts[:server] = s if @this
1245
+ self
1246
+ end
1247
+
1248
+ # Replace the current values with hash. Should definitely not be
1249
+ # used with untrusted input, and should probably not be called
1250
+ # directly by user code.
1251
+ def set_values(hash)
1252
+ @values = hash
1253
+ end
1254
+
1111
1255
  # Clear the setter_methods cache when a method is added
1112
1256
  def singleton_method_added(meth)
1113
1257
  @singleton_setter_added = true if meth.to_s =~ SETTER_METHOD_REGEXP
@@ -1119,10 +1263,10 @@ module Sequel
1119
1263
  # Artist[1].this
1120
1264
  # # SELECT * FROM artists WHERE (id = 1) LIMIT 1
1121
1265
  def this
1122
- @this ||= model.dataset.filter(pk_hash).limit(1).naked
1266
+ @this ||= use_server(model.dataset.filter(pk_hash).limit(1).naked)
1123
1267
  end
1124
1268
 
1125
- # Runs set with the passed hash and then runs save_changes.
1269
+ # Runs #set with the passed hash and then runs save_changes.
1126
1270
  #
1127
1271
  # artist.update(:name=>'Jim') # UPDATE artists SET name = 'Jim' WHERE (id = 1)
1128
1272
  def update(hash)
@@ -1130,7 +1274,7 @@ module Sequel
1130
1274
  end
1131
1275
 
1132
1276
  # Update all values using the entries in the hash, ignoring any setting of
1133
- # allowed_columns or restricted columns in the model.
1277
+ # +allowed_columns+ or +restricted_columns+ in the model.
1134
1278
  #
1135
1279
  # Artist.set_restricted_columns(:name)
1136
1280
  # artist.update_all(:name=>'Jim') # UPDATE artists SET name = 'Jim' WHERE (id = 1)
@@ -1139,7 +1283,8 @@ module Sequel
1139
1283
  end
1140
1284
 
1141
1285
  # Update all values using the entries in the hash, except for the keys
1142
- # given in except.
1286
+ # given in except. You should probably use +update_fields+ or +update_only+
1287
+ # instead of this method, as blacklist approaches to security are a bad idea.
1143
1288
  #
1144
1289
  # artist.update_except({:name=>'Jim'}, :hometown) # UPDATE artists SET name = 'Jim' WHERE (id = 1)
1145
1290
  def update_except(hash, *except)
@@ -1149,10 +1294,10 @@ module Sequel
1149
1294
  # Update the instances values by calling +set_fields+ with the +hash+
1150
1295
  # and +fields+, then save any changes to the record. Returns self.
1151
1296
  #
1152
- # artist.update_fields({:name=>'Jim'}, :name)
1297
+ # artist.update_fields({:name=>'Jim'}, [:name])
1153
1298
  # # UPDATE artists SET name = 'Jim' WHERE (id = 1)
1154
1299
  #
1155
- # artist.update_fields({:hometown=>'LA'}, :name)
1300
+ # artist.update_fields({:hometown=>'LA'}, [:name])
1156
1301
  # # UPDATE artists SET name = NULL WHERE (id = 1)
1157
1302
  def update_fields(hash, fields)
1158
1303
  set_fields(hash, fields)
@@ -1160,7 +1305,8 @@ module Sequel
1160
1305
  end
1161
1306
 
1162
1307
  # Update the values using the entries in the hash, only if the key
1163
- # is included in only.
1308
+ # is included in only. It may be a better idea to use +update_fields+
1309
+ # instead of this method.
1164
1310
  #
1165
1311
  # artist.update_only({:name=>'Jim'}, :name)
1166
1312
  # # UPDATE artists SET name = 'Jim' WHERE (id = 1)
@@ -1185,21 +1331,15 @@ module Sequel
1185
1331
  # artist(:name=>'Invalid').valid? # => false
1186
1332
  # artist.errors.full_messages # => ['name cannot be Invalid']
1187
1333
  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?
1334
+ _valid?(false, opts)
1196
1335
  end
1197
1336
 
1198
1337
  private
1199
1338
 
1200
- # Actually do the deletion of the object's dataset.
1339
+ # Do the deletion of the object's dataset, and check that the row
1340
+ # was actually deleted.
1201
1341
  def _delete
1202
- n = _delete_dataset.delete
1342
+ n = _delete_without_checking
1203
1343
  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
1344
  n
1205
1345
  end
@@ -1210,12 +1350,27 @@ module Sequel
1210
1350
  this
1211
1351
  end
1212
1352
 
1353
+ # Actually do the deletion of the object's dataset. Return the
1354
+ # number of rows modified.
1355
+ def _delete_without_checking
1356
+ _delete_dataset.delete
1357
+ end
1358
+
1213
1359
  # Internal destroy method, separted from destroy to
1214
1360
  # allow running inside a transaction
1215
1361
  def _destroy(opts)
1216
- raise_hook_failure(:destroy) if before_destroy == false
1217
- _destroy_delete
1218
- after_destroy
1362
+ sh = {:server=>this_server}
1363
+ db.after_rollback(sh){after_destroy_rollback}
1364
+ called = false
1365
+ around_destroy do
1366
+ called = true
1367
+ raise_hook_failure(:destroy) if before_destroy == false
1368
+ _destroy_delete
1369
+ after_destroy
1370
+ true
1371
+ end
1372
+ raise_hook_failure(:destroy) unless called
1373
+ db.after_commit(sh){after_destroy_commit}
1219
1374
  self
1220
1375
  end
1221
1376
 
@@ -1230,11 +1385,11 @@ module Sequel
1230
1385
  # the record should be refreshed from the database.
1231
1386
  def _insert
1232
1387
  ds = _insert_dataset
1233
- if ds.respond_to?(:insert_select) and h = ds.insert_select(@values)
1388
+ if !ds.opts[:select] and ds.supports_insert_select? and h = _insert_select_raw(ds)
1234
1389
  @values = h
1235
1390
  nil
1236
1391
  else
1237
- iid = ds.insert(@values)
1392
+ iid = _insert_raw(ds)
1238
1393
  # if we have a regular primary key and it's not set in @values,
1239
1394
  # we assume it's the last inserted id
1240
1395
  if (pk = autoincrementing_primary_key) && pk.is_a?(Symbol) && !@values[pk]
@@ -1243,55 +1398,95 @@ module Sequel
1243
1398
  pk
1244
1399
  end
1245
1400
  end
1246
-
1401
+
1247
1402
  # The dataset to use when inserting a new object. The same as the model's
1248
1403
  # dataset by default.
1249
1404
  def _insert_dataset
1250
- model.dataset
1405
+ use_server(model.dataset)
1251
1406
  end
1252
1407
 
1408
+ # Insert into the given dataset and return the primary key created (if any).
1409
+ def _insert_raw(ds)
1410
+ ds.insert(@values)
1411
+ end
1412
+
1413
+ # Insert into the given dataset and return the hash of column values.
1414
+ def _insert_select_raw(ds)
1415
+ ds.insert_select(@values)
1416
+ end
1417
+
1253
1418
  # Refresh using a particular dataset, used inside save to make sure the same server
1254
1419
  # is used for reading newly inserted values from the database
1255
1420
  def _refresh(dataset)
1256
- set_values(dataset.first || raise(Error, "Record not found"))
1421
+ set_values(_refresh_get(dataset) || raise(Error, "Record not found"))
1257
1422
  changed_columns.clear
1258
1423
  self
1259
1424
  end
1425
+
1426
+ # Get the row of column data from the database.
1427
+ def _refresh_get(dataset)
1428
+ dataset.first
1429
+ end
1260
1430
 
1261
1431
  # Internal version of save, split from save to allow running inside
1262
1432
  # it's own transaction.
1263
1433
  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
1434
+ sh = {:server=>this_server}
1435
+ db.after_rollback(sh){after_rollback}
1436
+ was_new = false
1437
+ pk = nil
1438
+ called_save = false
1439
+ called_cu = false
1440
+ around_save do
1441
+ called_save = true
1442
+ raise_hook_failure(:save) if before_save == false
1443
+ if new?
1444
+ was_new = true
1445
+ around_create do
1446
+ called_cu = true
1447
+ raise_hook_failure(:create) if before_create == false
1448
+ pk = _insert
1449
+ @this = nil
1450
+ @new = false
1451
+ @was_new = true
1452
+ after_create
1453
+ true
1454
+ end
1455
+ raise_hook_failure(:create) unless called_cu
1456
+ else
1457
+ around_update do
1458
+ called_cu = true
1459
+ raise_hook_failure(:update) if before_update == false
1460
+ if columns.empty?
1461
+ @columns_updated = if opts[:changed]
1462
+ @values.reject{|k,v| !changed_columns.include?(k)}
1463
+ else
1464
+ _save_update_all_columns_hash
1465
+ end
1466
+ changed_columns.clear
1467
+ else # update only the specified columns
1468
+ @columns_updated = @values.reject{|k, v| !columns.include?(k)}
1469
+ changed_columns.reject!{|c| columns.include?(c)}
1470
+ end
1471
+ _update_columns(@columns_updated)
1472
+ @this = nil
1473
+ after_update
1474
+ true
1475
+ end
1476
+ raise_hook_failure(:update) unless called_cu
1477
+ end
1272
1478
  after_save
1479
+ true
1480
+ end
1481
+ raise_hook_failure(:save) unless called_save
1482
+ if was_new
1273
1483
  @was_new = nil
1274
1484
  pk ? _save_refresh : changed_columns.clear
1275
1485
  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
1486
  @columns_updated = nil
1293
1487
  end
1294
1488
  @modified = false
1489
+ db.after_commit(sh){after_commit}
1295
1490
  self
1296
1491
  end
1297
1492
 
@@ -1321,9 +1516,10 @@ module Sequel
1321
1516
  _update(columns) unless columns.empty?
1322
1517
  end
1323
1518
 
1324
- # Update this instance's dataset with the supplied column hash.
1519
+ # Update this instance's dataset with the supplied column hash,
1520
+ # checking that only a single row was modified.
1325
1521
  def _update(columns)
1326
- n = _update_dataset.update(columns)
1522
+ n = _update_without_checking(columns)
1327
1523
  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
1524
  n
1329
1525
  end
@@ -1334,7 +1530,46 @@ module Sequel
1334
1530
  this
1335
1531
  end
1336
1532
 
1337
- # If not raising on failure, check for BeforeHookFailed
1533
+ # Update this instances dataset with the supplied column hash.
1534
+ def _update_without_checking(columns)
1535
+ _update_dataset.update(columns)
1536
+ end
1537
+
1538
+ # Internal validation method. If +raise_errors+ is +true+, hook
1539
+ # failures will be raised as HookFailure exceptions. If it is
1540
+ # +false+, +false+ will be returned instead.
1541
+ def _valid?(raise_errors, opts)
1542
+ errors.clear
1543
+ called = false
1544
+ error = false
1545
+ around_validation do
1546
+ called = true
1547
+ if before_validation == false
1548
+ if raise_errors
1549
+ raise_hook_failure(:validation)
1550
+ else
1551
+ error = true
1552
+ end
1553
+ false
1554
+ else
1555
+ validate
1556
+ after_validation
1557
+ errors.empty?
1558
+ end
1559
+ end
1560
+ error = true unless called
1561
+ if error
1562
+ if raise_errors
1563
+ raise_hook_failure(:validation)
1564
+ else
1565
+ false
1566
+ end
1567
+ else
1568
+ errors.empty?
1569
+ end
1570
+ end
1571
+
1572
+ # If not raising on failure, check for HookFailed
1338
1573
  # being raised by yielding and swallow it.
1339
1574
  def checked_save_failure(opts)
1340
1575
  if raise_on_failure?(opts)
@@ -1342,7 +1577,7 @@ module Sequel
1342
1577
  else
1343
1578
  begin
1344
1579
  yield
1345
- rescue BeforeHookFailed
1580
+ rescue HookFailed
1346
1581
  nil
1347
1582
  end
1348
1583
  end
@@ -1350,7 +1585,14 @@ module Sequel
1350
1585
 
1351
1586
  # If transactions should be used, wrap the yield in a transaction block.
1352
1587
  def checked_transaction(opts={})
1353
- use_transaction?(opts) ? db.transaction(opts){yield} : yield
1588
+ use_transaction?(opts) ? db.transaction({:server=>this_server}.merge(opts)){yield} : yield
1589
+ end
1590
+
1591
+ # Set the columns with the given hash. By default, the same as +set+, but
1592
+ # exists so it can be overridden. This is called only for new records, before
1593
+ # changed_columns is cleared.
1594
+ def initialize_set(h)
1595
+ set(h)
1354
1596
  end
1355
1597
 
1356
1598
  # Default inspection output for the values hash, overwrite to change what #inspect displays.
@@ -1369,7 +1611,7 @@ module Sequel
1369
1611
  # Raise an error appropriate to the hook type. May be swallowed by
1370
1612
  # checked_save_failure depending on the raise_on_failure? setting.
1371
1613
  def raise_hook_failure(type)
1372
- raise BeforeHookFailed, "one of the before_#{type} hooks returned false"
1614
+ raise HookFailed, "one of the before_#{type} hooks returned false"
1373
1615
  end
1374
1616
 
1375
1617
  # Set the columns, filtered by the only and except arrays.
@@ -1385,17 +1627,21 @@ module Sequel
1385
1627
  if meths.include?(m)
1386
1628
  send(m, v)
1387
1629
  elsif strict
1388
- raise Error, "method #{m} doesn't exist or access is restricted to it"
1630
+ # Avoid using respond_to? or creating symbols from user input
1631
+ if public_methods.map{|s| s.to_s}.include?(m)
1632
+ if Array(model.primary_key).map{|s| s.to_s}.member?(k.to_s) && model.restrict_primary_key?
1633
+ raise Error, "#{k} is a restricted primary key"
1634
+ else
1635
+ raise Error, "#{k} is a restricted column"
1636
+ end
1637
+ else
1638
+ raise Error, "method #{m} doesn't exist"
1639
+ end
1389
1640
  end
1390
1641
  end
1391
1642
  self
1392
1643
  end
1393
1644
 
1394
- # Replace the current values with hash.
1395
- def set_values(hash)
1396
- @values = hash
1397
- end
1398
-
1399
1645
  # Returns all methods that can be used for attribute
1400
1646
  # assignment (those that end with =), modified by the only
1401
1647
  # and except arguments:
@@ -1424,6 +1670,12 @@ module Sequel
1424
1670
  meths
1425
1671
  end
1426
1672
  end
1673
+
1674
+ # The server/shard that the model object's dataset uses, or :default if the
1675
+ # model object's dataset does not have an associated shard.
1676
+ def this_server
1677
+ primary_key ? (this.opts[:server] || :default) : (model.dataset.opts[:server] || :default)
1678
+ end
1427
1679
 
1428
1680
  # Typecast the value to the column's type if typecasting. Calls the database's
1429
1681
  # typecast_value method, so database adapters can override/augment the handling
@@ -1444,6 +1696,11 @@ module Sequel
1444
1696
  set_restricted(hash, only, except)
1445
1697
  save_changes
1446
1698
  end
1699
+
1700
+ # Set the given dataset to use the current object's shard.
1701
+ def use_server(ds)
1702
+ @server ? ds.server(@server) : ds
1703
+ end
1447
1704
 
1448
1705
  # Whether to use a transaction for this action. If the :transaction
1449
1706
  # option is present in the hash, use that, otherwise, fallback to the
@@ -1461,6 +1718,18 @@ module Sequel
1461
1718
  # Artist.dataset.model # => Artist
1462
1719
  attr_accessor :model
1463
1720
 
1721
+ # Assume if a single integer is given that it is a lookup by primary
1722
+ # key, and call with_pk with the argument.
1723
+ #
1724
+ # Artist.dataset[1] # SELECT * FROM artists WHERE (id = 1) LIMIT 1
1725
+ def [](*args)
1726
+ if args.length == 1 && (i = args.at(0)) && i.is_a?(Integer)
1727
+ with_pk(i)
1728
+ else
1729
+ super
1730
+ end
1731
+ end
1732
+
1464
1733
  # Destroy each row in the dataset by instantiating it and then calling
1465
1734
  # destroy on the resulting model object. This isn't as fast as deleting
1466
1735
  # the dataset, which does a single SQL call, but this runs any destroy
@@ -1472,7 +1741,7 @@ module Sequel
1472
1741
  # # ...
1473
1742
  def destroy
1474
1743
  pr = proc{all{|r| r.destroy}.length}
1475
- model.use_transactions ? @db.transaction(&pr) : pr.call
1744
+ model.use_transactions ? @db.transaction(:server=>opts[:server], &pr) : pr.call
1476
1745
  end
1477
1746
 
1478
1747
  # This allows you to call +to_hash+ without any arguments, which will
@@ -1491,6 +1760,19 @@ module Sequel
1491
1760
  super(pk, value_column)
1492
1761
  end
1493
1762
  end
1763
+
1764
+ # Given a primary key value, return the first record in the dataset with that primary key
1765
+ # value.
1766
+ #
1767
+ # # Single primary key
1768
+ # Artist.dataset.with_pk(1) # SELECT * FROM artists WHERE (id = 1) LIMIT 1
1769
+ #
1770
+ # # Composite primary key
1771
+ # Artist.dataset.with_pk([1, 2]) # SELECT * FROM artists
1772
+ # # WHERE ((id1 = 1) AND (id2 = 2)) LIMIT 1
1773
+ def with_pk(pk)
1774
+ first(model.qualified_primary_key_hash(pk))
1775
+ end
1494
1776
  end
1495
1777
 
1496
1778
  extend ClassMethods