sequel 3.21.0 → 3.29.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 (200) hide show
  1. data/CHANGELOG +413 -3
  2. data/README.rdoc +20 -6
  3. data/Rakefile +23 -16
  4. data/bin/sequel +1 -5
  5. data/doc/association_basics.rdoc +210 -43
  6. data/doc/dataset_basics.rdoc +4 -4
  7. data/doc/mass_assignment.rdoc +54 -0
  8. data/doc/migration.rdoc +15 -538
  9. data/doc/model_hooks.rdoc +72 -27
  10. data/doc/opening_databases.rdoc +86 -50
  11. data/doc/prepared_statements.rdoc +40 -13
  12. data/doc/reflection.rdoc +8 -2
  13. data/doc/release_notes/3.22.0.txt +39 -0
  14. data/doc/release_notes/3.23.0.txt +172 -0
  15. data/doc/release_notes/3.24.0.txt +420 -0
  16. data/doc/release_notes/3.25.0.txt +88 -0
  17. data/doc/release_notes/3.26.0.txt +88 -0
  18. data/doc/release_notes/3.27.0.txt +82 -0
  19. data/doc/release_notes/3.28.0.txt +304 -0
  20. data/doc/release_notes/3.29.0.txt +459 -0
  21. data/doc/schema_modification.rdoc +547 -0
  22. data/doc/sharding.rdoc +7 -1
  23. data/doc/testing.rdoc +115 -0
  24. data/doc/transactions.rdoc +137 -0
  25. data/doc/virtual_rows.rdoc +2 -2
  26. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  27. data/lib/sequel/adapters/ado.rb +40 -18
  28. data/lib/sequel/adapters/amalgalite.rb +15 -7
  29. data/lib/sequel/adapters/db2.rb +175 -86
  30. data/lib/sequel/adapters/dbi.rb +15 -15
  31. data/lib/sequel/adapters/do/mysql.rb +0 -5
  32. data/lib/sequel/adapters/do/postgres.rb +0 -5
  33. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  34. data/lib/sequel/adapters/do.rb +17 -36
  35. data/lib/sequel/adapters/firebird.rb +27 -208
  36. data/lib/sequel/adapters/ibmdb.rb +448 -0
  37. data/lib/sequel/adapters/informix.rb +6 -23
  38. data/lib/sequel/adapters/jdbc/as400.rb +5 -31
  39. data/lib/sequel/adapters/jdbc/db2.rb +44 -0
  40. data/lib/sequel/adapters/jdbc/derby.rb +217 -0
  41. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  42. data/lib/sequel/adapters/jdbc/h2.rb +26 -17
  43. data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
  44. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  45. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  46. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  47. data/lib/sequel/adapters/jdbc/mysql.rb +9 -10
  48. data/lib/sequel/adapters/jdbc/oracle.rb +67 -25
  49. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -11
  50. data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
  51. data/lib/sequel/adapters/jdbc/sqlserver.rb +41 -0
  52. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  53. data/lib/sequel/adapters/jdbc.rb +107 -38
  54. data/lib/sequel/adapters/mock.rb +315 -0
  55. data/lib/sequel/adapters/mysql.rb +78 -182
  56. data/lib/sequel/adapters/mysql2.rb +24 -23
  57. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  58. data/lib/sequel/adapters/odbc/mssql.rb +0 -5
  59. data/lib/sequel/adapters/odbc.rb +28 -9
  60. data/lib/sequel/adapters/openbase.rb +2 -4
  61. data/lib/sequel/adapters/oracle.rb +349 -51
  62. data/lib/sequel/adapters/postgres.rb +169 -21
  63. data/lib/sequel/adapters/shared/access.rb +21 -6
  64. data/lib/sequel/adapters/shared/db2.rb +288 -0
  65. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  66. data/lib/sequel/adapters/shared/informix.rb +45 -0
  67. data/lib/sequel/adapters/shared/mssql.rb +108 -65
  68. data/lib/sequel/adapters/shared/mysql.rb +56 -13
  69. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  70. data/lib/sequel/adapters/shared/oracle.rb +185 -34
  71. data/lib/sequel/adapters/shared/postgres.rb +107 -58
  72. data/lib/sequel/adapters/shared/progress.rb +0 -6
  73. data/lib/sequel/adapters/shared/sqlite.rb +168 -41
  74. data/lib/sequel/adapters/sqlite.rb +27 -18
  75. data/lib/sequel/adapters/swift/mysql.rb +9 -5
  76. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  77. data/lib/sequel/adapters/swift/sqlite.rb +6 -4
  78. data/lib/sequel/adapters/swift.rb +5 -5
  79. data/lib/sequel/adapters/tinytds.rb +152 -17
  80. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +63 -0
  81. data/lib/sequel/ast_transformer.rb +190 -0
  82. data/lib/sequel/connection_pool/threaded.rb +3 -2
  83. data/lib/sequel/connection_pool.rb +1 -1
  84. data/lib/sequel/core.rb +46 -5
  85. data/lib/sequel/database/connecting.rb +5 -6
  86. data/lib/sequel/database/dataset.rb +4 -4
  87. data/lib/sequel/database/dataset_defaults.rb +59 -1
  88. data/lib/sequel/database/logging.rb +1 -1
  89. data/lib/sequel/database/misc.rb +100 -19
  90. data/lib/sequel/database/query.rb +153 -58
  91. data/lib/sequel/database/schema_generator.rb +8 -5
  92. data/lib/sequel/database/schema_methods.rb +59 -29
  93. data/lib/sequel/dataset/actions.rb +194 -57
  94. data/lib/sequel/dataset/features.rb +81 -8
  95. data/lib/sequel/dataset/graph.rb +8 -7
  96. data/lib/sequel/dataset/misc.rb +50 -23
  97. data/lib/sequel/dataset/mutation.rb +5 -6
  98. data/lib/sequel/dataset/prepared_statements.rb +32 -15
  99. data/lib/sequel/dataset/query.rb +223 -42
  100. data/lib/sequel/dataset/sql.rb +58 -61
  101. data/lib/sequel/dataset.rb +8 -0
  102. data/lib/sequel/exceptions.rb +4 -0
  103. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  104. data/lib/sequel/extensions/migration.rb +6 -4
  105. data/lib/sequel/extensions/named_timezones.rb +5 -0
  106. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  107. data/lib/sequel/extensions/to_dot.rb +95 -83
  108. data/lib/sequel/model/associations.rb +997 -331
  109. data/lib/sequel/model/base.rb +386 -129
  110. data/lib/sequel/model/errors.rb +1 -1
  111. data/lib/sequel/model/exceptions.rb +5 -1
  112. data/lib/sequel/model.rb +15 -8
  113. data/lib/sequel/plugins/association_pks.rb +22 -4
  114. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  115. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  116. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  117. data/lib/sequel/plugins/force_encoding.rb +6 -6
  118. data/lib/sequel/plugins/identity_map.rb +114 -7
  119. data/lib/sequel/plugins/many_through_many.rb +65 -7
  120. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  121. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  122. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  123. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  124. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  125. data/lib/sequel/plugins/serialization.rb +6 -1
  126. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  127. data/lib/sequel/plugins/sharding.rb +12 -25
  128. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  129. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  130. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  131. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  132. data/lib/sequel/sql.rb +107 -51
  133. data/lib/sequel/timezones.rb +44 -35
  134. data/lib/sequel/version.rb +1 -1
  135. data/spec/adapters/db2_spec.rb +146 -0
  136. data/spec/adapters/mssql_spec.rb +65 -29
  137. data/spec/adapters/mysql_spec.rb +122 -123
  138. data/spec/adapters/oracle_spec.rb +48 -76
  139. data/spec/adapters/postgres_spec.rb +213 -61
  140. data/spec/adapters/spec_helper.rb +6 -5
  141. data/spec/adapters/sqlite_spec.rb +35 -21
  142. data/spec/core/connection_pool_spec.rb +71 -92
  143. data/spec/core/core_sql_spec.rb +20 -31
  144. data/spec/core/database_spec.rb +729 -508
  145. data/spec/core/dataset_spec.rb +980 -1044
  146. data/spec/core/expression_filters_spec.rb +159 -42
  147. data/spec/core/mock_adapter_spec.rb +378 -0
  148. data/spec/core/object_graph_spec.rb +48 -114
  149. data/spec/core/schema_generator_spec.rb +3 -3
  150. data/spec/core/schema_spec.rb +298 -38
  151. data/spec/core/spec_helper.rb +6 -48
  152. data/spec/extensions/association_pks_spec.rb +38 -0
  153. data/spec/extensions/class_table_inheritance_spec.rb +1 -1
  154. data/spec/extensions/columns_introspection_spec.rb +91 -0
  155. data/spec/extensions/dataset_associations_spec.rb +199 -0
  156. data/spec/extensions/defaults_setter_spec.rb +64 -0
  157. data/spec/extensions/identity_map_spec.rb +162 -0
  158. data/spec/extensions/instance_hooks_spec.rb +71 -0
  159. data/spec/extensions/many_through_many_spec.rb +195 -20
  160. data/spec/extensions/migration_spec.rb +17 -17
  161. data/spec/extensions/named_timezones_spec.rb +22 -2
  162. data/spec/extensions/nested_attributes_spec.rb +4 -0
  163. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  164. data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
  165. data/spec/extensions/prepared_statements_spec.rb +72 -0
  166. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  167. data/spec/extensions/schema_dumper_spec.rb +2 -2
  168. data/spec/extensions/schema_spec.rb +13 -21
  169. data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
  170. data/spec/extensions/serialization_spec.rb +5 -8
  171. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  172. data/spec/extensions/spec_helper.rb +7 -1
  173. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  174. data/spec/extensions/to_dot_spec.rb +3 -5
  175. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  176. data/spec/extensions/xml_serializer_spec.rb +16 -4
  177. data/spec/integration/associations_test.rb +522 -21
  178. data/spec/integration/database_test.rb +4 -2
  179. data/spec/integration/dataset_test.rb +375 -62
  180. data/spec/integration/eager_loader_test.rb +19 -21
  181. data/spec/integration/model_test.rb +80 -1
  182. data/spec/integration/plugin_test.rb +304 -116
  183. data/spec/integration/prepared_statement_test.rb +200 -120
  184. data/spec/integration/schema_test.rb +161 -30
  185. data/spec/integration/spec_helper.rb +39 -30
  186. data/spec/integration/timezone_test.rb +38 -12
  187. data/spec/integration/transaction_test.rb +172 -5
  188. data/spec/integration/type_test.rb +17 -3
  189. data/spec/model/association_reflection_spec.rb +174 -7
  190. data/spec/model/associations_spec.rb +849 -661
  191. data/spec/model/base_spec.rb +255 -95
  192. data/spec/model/dataset_methods_spec.rb +7 -27
  193. data/spec/model/eager_loading_spec.rb +640 -676
  194. data/spec/model/hooks_spec.rb +309 -67
  195. data/spec/model/model_spec.rb +207 -167
  196. data/spec/model/plugins_spec.rb +24 -13
  197. data/spec/model/record_spec.rb +321 -218
  198. data/spec/model/spec_helper.rb +13 -71
  199. data/spec/model/validations_spec.rb +11 -0
  200. metadata +95 -38
@@ -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
 
@@ -217,9 +258,30 @@ module Sequel
217
258
  @dataset_methods[meth] = block
218
259
  dataset.meta_def(meth, &block) if @dataset
219
260
  end
220
- args.each{|arg| instance_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__) unless respond_to?(arg)}
261
+ args.each do |arg|
262
+ if arg.to_s =~ NORMAL_METHOD_NAME_REGEXP
263
+ instance_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__) unless respond_to?(arg, true)
264
+ else
265
+ def_model_dataset_method_block(arg)
266
+ end
267
+ end
221
268
  end
222
-
269
+
270
+ module_eval(if RUBY_VERSION < '1.8.7'
271
+ <<-END
272
+ def def_model_dataset_method_block(arg)
273
+ meta_def(arg){|*args| dataset.send(arg, *args)}
274
+ end
275
+ END
276
+ else
277
+ <<-END
278
+ def def_model_dataset_method_block(arg)
279
+ meta_def(arg){|*args, &block| dataset.send(arg, *args, &block)}
280
+ end
281
+ END
282
+ end, __FILE__, __LINE__ - 4)
283
+ private :def_model_dataset_method_block
284
+
223
285
  # Finds a single record according to the supplied filter.
224
286
  # You are encouraged to use Model.[] or Model.first instead of this method.
225
287
  #
@@ -248,6 +310,13 @@ module Sequel
248
310
  find(cond) || create(cond, &block)
249
311
  end
250
312
 
313
+ # Clear the setter_methods cache when a module is included, as it
314
+ # may contain setter methods.
315
+ def include(mod)
316
+ clear_setter_methods_cache
317
+ super
318
+ end
319
+
251
320
  # If possible, set the dataset for the model subclass as soon as it
252
321
  # is created. Also, make sure the inherited class instance variables
253
322
  # are copied into the subclass.
@@ -271,7 +340,8 @@ module Sequel
271
340
  db
272
341
  begin
273
342
  if self == Model || !@dataset
274
- subclass.set_dataset(subclass.implicit_table_name) unless subclass.name.empty?
343
+ n = subclass.name
344
+ subclass.set_dataset(subclass.implicit_table_name) unless n.nil? || n.empty?
275
345
  elsif @dataset
276
346
  subclass.set_dataset(@dataset.clone, :inherited=>true)
277
347
  end
@@ -290,12 +360,9 @@ module Sequel
290
360
  pluralize(underscore(demodulize(name))).to_sym
291
361
  end
292
362
 
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.
363
+ # Calls #call with the values hash. Only for backwards compatibility.
297
364
  def load(values)
298
- new(values, true)
365
+ call(values)
299
366
  end
300
367
 
301
368
  # Clear the setter_methods cache when a setter method is added
@@ -325,18 +392,13 @@ module Sequel
325
392
  unless @plugins.include?(m)
326
393
  @plugins << m
327
394
  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
395
+ include(m::InstanceMethods) if plugin_module_defined?(m, :InstanceMethods)
396
+ extend(m::ClassMethods)if plugin_module_defined?(m, :ClassMethods)
397
+ dataset_extend(m::DatasetMethods) if plugin_module_defined?(m, :DatasetMethods)
336
398
  end
337
399
  m.configure(self, *args, &blk) if m.respond_to?(:configure)
338
400
  end
339
-
401
+
340
402
  # Returns primary key attribute hash. If using a composite primary key
341
403
  # value such be an array with values for each primary key in the correct
342
404
  # order. For a standard primary key, value should be an object with a
@@ -389,8 +451,8 @@ module Sequel
389
451
  # setter methods (methods that end in =) that you want to be used during
390
452
  # mass assignment, they need to be listed here as well (without the =).
391
453
  #
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.
454
+ # It may be better to use a method such as +set_only+ or +set_fields+ that lets you specify
455
+ # the allowed fields per call.
394
456
  #
395
457
  # Artist.set_allowed_columns(:name, :hometown)
396
458
  # Artist.set(:name=>'Bob', :hometown=>'Sactown') # No Error
@@ -400,10 +462,12 @@ module Sequel
400
462
  @allowed_columns = cols
401
463
  end
402
464
 
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+.
465
+ # Sets the dataset associated with the Model class. +ds+ can be a +Symbol+,
466
+ # +LiteralString+, <tt>SQL::Identifier</tt>, <tt>SQL::QualifiedIdentifier</tt>,
467
+ # <tt>SQL::AliasedExpression</tt>
468
+ # (all specifying a table name in the current database), or a +Dataset+.
405
469
  # 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
470
+ # dataset. If a dataset is not used, a dataset is created from the current
407
471
  # database with the table name given. Other arguments raise an +Error+.
408
472
  # Returns self.
409
473
  #
@@ -418,17 +482,21 @@ module Sequel
418
482
  def set_dataset(ds, opts={})
419
483
  inherited = opts[:inherited]
420
484
  @dataset = case ds
421
- when Symbol
485
+ when Symbol, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression, LiteralString
422
486
  @simple_table = db.literal(ds)
423
- db[ds]
487
+ db.from(ds)
424
488
  when Dataset
425
- @simple_table = nil
489
+ @simple_table = if ds.send(:simple_select_all?)
490
+ ds.literal(ds.first_source_table)
491
+ else
492
+ nil
493
+ end
426
494
  @db = ds.db
427
495
  ds
428
496
  else
429
- raise(Error, "Model.set_dataset takes a Symbol or a Sequel::Dataset")
497
+ raise(Error, "Model.set_dataset takes one of the following classes as an argument: Symbol, LiteralString, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression, Dataset")
430
498
  end
431
- @dataset.row_proc = Proc.new{|r| load(r)}
499
+ @dataset.row_proc = self
432
500
  @require_modification = Sequel::Model.require_modification.nil? ? @dataset.provides_accurate_rows_matched? : Sequel::Model.require_modification
433
501
  if inherited
434
502
  @simple_table = superclass.simple_table
@@ -441,10 +509,11 @@ module Sequel
441
509
  check_non_connection_error{@db_schema = (inherited ? superclass.db_schema : get_db_schema)}
442
510
  self
443
511
  end
444
-
512
+
445
513
  # Sets the primary key for this model. You can use either a regular
446
514
  # or a composite primary key. To not use a primary key, set to nil
447
- # or use +no_primary_key+.
515
+ # or use +no_primary_key+. On most adapters, Sequel can automatically
516
+ # determine the primary key to use, so this method is not needed often.
448
517
  #
449
518
  # class Person < Sequel::Model
450
519
  # # regular key
@@ -458,7 +527,11 @@ module Sequel
458
527
  def set_primary_key(*key)
459
528
  clear_setter_methods_cache
460
529
  key = key.flatten
461
- @simple_pk = key.length == 1 ? db.literal(key.first) : nil
530
+ @simple_pk = if key.length == 1
531
+ (@dataset || db).literal(key.first)
532
+ else
533
+ nil
534
+ end
462
535
  @primary_key = (key.length == 1) ? key[0] : key
463
536
  end
464
537
 
@@ -468,10 +541,9 @@ module Sequel
468
541
  # If you have any virtual setter methods (methods that end in =) that you
469
542
  # want not to be used during mass assignment, they need to be listed here as well (without the =).
470
543
  #
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.
544
+ # It's generally a bad idea to rely on a blacklist approach for security. Using a whitelist
545
+ # approach such as set_allowed_columns or the instance level set_only or set_fields methods
546
+ # is usually a better choice. So use of this method is generally a bad idea.
475
547
  #
476
548
  # Artist.set_restricted_column(:records_sold)
477
549
  # Artist.set(:name=>'Bob', :hometown=>'Sactown') # No Error
@@ -511,6 +583,9 @@ module Sequel
511
583
  # 7 days ago.
512
584
  #
513
585
  # Both the args given and the block are passed to <tt>Dataset#filter</tt>.
586
+ #
587
+ # This method creates dataset methods that do not accept arguments. To create
588
+ # dataset methods that accept arguments, you have to use def_dataset_method.
514
589
  def subset(name, *args, &block)
515
590
  def_dataset_method(name){filter(*args, &block)}
516
591
  end
@@ -526,6 +601,7 @@ module Sequel
526
601
  end
527
602
 
528
603
  # Allow the setting of the primary key(s) when using the mass assignment methods.
604
+ # Using this method can open up security issues, be very careful before using it.
529
605
  #
530
606
  # Artist.set(:id=>1) # Error
531
607
  # Artist.unrestrict_primary_key
@@ -548,6 +624,16 @@ module Sequel
548
624
  end
549
625
  end
550
626
 
627
+ # Add the module to the class's dataset_method_modules. Extend the dataset with the
628
+ # module if the model has a dataset. Add dataset methods to the class for all
629
+ # public dataset methods.
630
+ def dataset_extend(mod)
631
+ dataset.extend(mod) if @dataset
632
+ dataset_method_modules << mod
633
+ meths = mod.public_instance_methods.reject{|x| NORMAL_METHOD_NAME_REGEXP !~ x.to_s}
634
+ def_dataset_method(*meths) unless meths.empty?
635
+ end
636
+
551
637
  # Create a column accessor for a column with a method name that is hard to use in ruby code.
552
638
  def def_bad_column_accessor(column)
553
639
  overridable_methods_module.module_eval do
@@ -578,10 +664,9 @@ module Sequel
578
664
  return nil unless @dataset
579
665
  schema_hash = {}
580
666
  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
667
  get_columns = proc{check_non_connection_error{columns} || []}
584
- if single_table && (schema_array = (db.schema(dataset.first_source_table, :reload=>reload) rescue nil))
668
+ schema_array = check_non_connection_error{db.schema(dataset, :reload=>reload)}
669
+ if schema_array
585
670
  schema_array.each{|k,v| schema_hash[k] = v}
586
671
  if ds_opts.include?(:select)
587
672
  # We don't remove the columns from the schema_hash,
@@ -589,6 +674,7 @@ module Sequel
589
674
  # even if they are not selected.
590
675
  cols = get_columns.call
591
676
  cols.each{|c| schema_hash[c] ||= {}}
677
+ def_column_accessor(*schema_hash.keys)
592
678
  else
593
679
  # Dataset is for a single table with all columns,
594
680
  # so set the columns based on the order they were
@@ -657,6 +743,15 @@ module Sequel
657
743
  Sequel::Plugins.const_get(module_name)
658
744
  end
659
745
 
746
+ # Check if the plugin module +plugin+ defines the constant named by +submod+.
747
+ def plugin_module_defined?(plugin, submod)
748
+ if RUBY_VERSION >= '1.9'
749
+ plugin.const_defined?(submod, false)
750
+ else
751
+ plugin.const_defined?(submod)
752
+ end
753
+ end
754
+
660
755
  # Find the row in the dataset that matches the primary key. Uses
661
756
  # a static SQL optimization if the table and primary key are simple.
662
757
  def primary_key_lookup(pk)
@@ -686,18 +781,19 @@ module Sequel
686
781
 
687
782
  # Sequel::Model instance methods that implement basic model functionality.
688
783
  #
689
- # * All of the methods in +HOOKS+ create instance methods that are called
784
+ # * All of the methods in +HOOKS+ and +AROUND_HOOKS+ create instance methods that are called
690
785
  # 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+.
786
+ # a model object, Sequel will call +around_destory+, which will call +before_destroy+, do
787
+ # the destroy, and then call +after_destroy+.
693
788
  # * The following instance_methods all call the class method of the same
694
- # name: columns, dataset, db, primary_key, db_schema.
789
+ # name: columns, db, primary_key, db_schema.
695
790
  # * 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
791
+ # to set values for the attribute. It also creates instance getters returning
697
792
  # the value of the setting. If the value has not yet been set, it
698
793
  # gets the default value from the class by calling the class method of the same name.
699
794
  module InstanceMethods
700
795
  HOOKS.each{|h| class_eval("def #{h}; end", __FILE__, __LINE__)}
796
+ AROUND_HOOKS.each{|h| class_eval("def #{h}; yield end", __FILE__, __LINE__)}
701
797
 
702
798
  # Define instance method(s) that calls class method(s) of the
703
799
  # same name, caching the result in an instance variable. Define
@@ -735,7 +831,7 @@ module Sequel
735
831
  #
736
832
  # Arguments:
737
833
  # values :: should be a hash to pass to set.
738
- # from_db :: should only be set by <tt>Model.load</tt>, forget it exists.
834
+ # from_db :: only for backwards compatibility, forget it exists.
739
835
  #
740
836
  # Artist.new(:name=>'Bob')
741
837
  #
@@ -744,13 +840,12 @@ module Sequel
744
840
  # end
745
841
  def initialize(values = {}, from_db = false)
746
842
  if from_db
747
- @new = false
748
843
  set_values(values)
749
844
  else
750
845
  @values = {}
751
846
  @new = true
752
847
  @modified = true
753
- set(values)
848
+ initialize_set(values)
754
849
  changed_columns.clear
755
850
  yield self if block_given?
756
851
  end
@@ -766,7 +861,7 @@ module Sequel
766
861
 
767
862
  # Sets the value for the given column. If typecasting is enabled for
768
863
  # 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
864
+ # If this is a new record or the typecasted value isn't the same
770
865
  # as the current value for the column, mark the column as changed.
771
866
  #
772
867
  # a = Artist.new
@@ -874,14 +969,25 @@ module Sequel
874
969
 
875
970
  # Returns true when current instance exists, false otherwise.
876
971
  # Generally an object that isn't new will exist unless it has
877
- # been deleted.
972
+ # been deleted. Uses a database query to check for existence,
973
+ # unless the model object is new, in which case this is always
974
+ # false.
878
975
  #
879
976
  # Artist[1].exists? # SELECT 1 FROM artists WHERE (id = 1)
880
977
  # # => true
978
+ # Artist.new.exists?
979
+ # # => false
881
980
  def exists?
882
- !this.get(1).nil?
981
+ new? ? false : !this.get(1).nil?
883
982
  end
884
983
 
984
+ # Ignore the model's setter method cache when this instances extends a module, as the
985
+ # module may contain setter methods.
986
+ def extend(mod)
987
+ @singleton_setter_added = true
988
+ super
989
+ end
990
+
885
991
  # Value that should be unique for objects with the same class and pk (if pk is not nil), or
886
992
  # the same class and values (if pk is nil).
887
993
  #
@@ -890,7 +996,14 @@ module Sequel
890
996
  # Artist.new.hash == Artist.new.hash # true
891
997
  # Artist.new(:name=>'Bob').hash == Artist.new.hash # false
892
998
  def hash
893
- [model, pk.nil? ? @values.sort_by{|k,v| k.to_s} : pk].hash
999
+ case primary_key
1000
+ when Array
1001
+ [model, !pk.all? ? @values.sort_by{|k,v| k.to_s} : pk].hash
1002
+ when Symbol
1003
+ [model, pk.nil? ? @values.sort_by{|k,v| k.to_s} : pk].hash
1004
+ else
1005
+ [model, @values.sort_by{|k,v| k.to_s}].hash
1006
+ end
894
1007
  end
895
1008
 
896
1009
  # Returns value for the :id attribute, even if the primary key is
@@ -967,11 +1080,11 @@ module Sequel
967
1080
  # Artist.new.new? # => true
968
1081
  # Artist[1].new? # => false
969
1082
  def new?
970
- @new
1083
+ defined?(@new) ? @new : (@new = false)
971
1084
  end
972
1085
 
973
1086
  # Returns the primary key value identifying the model instance.
974
- # Raises an error if this model does not have a primary key.
1087
+ # Raises an +Error+ if this model does not have a primary key.
975
1088
  # If the model has a composite primary key, returns an array of values.
976
1089
  #
977
1090
  # Artist[1].pk # => 1
@@ -1016,26 +1129,28 @@ module Sequel
1016
1129
  #
1017
1130
  # If +save+ fails and either raise_on_save_failure or the
1018
1131
  # :raise_on_failure option is true, it raises ValidationFailed
1019
- # or BeforeHookFailed. Otherwise it returns nil.
1132
+ # or HookFailed. Otherwise it returns nil.
1020
1133
  #
1021
1134
  # If it succeeds, it returns self.
1022
1135
  #
1023
1136
  # You can provide an optional list of columns to update, in which
1024
- # case it only updates those columns.
1137
+ # case it only updates those columns, or a options hash.
1025
1138
  #
1026
1139
  # Takes the following options:
1027
1140
  #
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
1141
+ # :changed :: save all changed columns, instead of all columns or the columns given
1142
+ # :transaction :: set to true or false to override the current
1143
+ # +use_transactions+ setting
1144
+ # :validate :: set to false to skip validation
1145
+ # :raise_on_failure :: set to true or false to override the current
1146
+ # +raise_on_save_failure+ setting
1034
1147
  def save(*columns)
1035
1148
  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
1149
+ if opts[:validate] != false
1150
+ unless checked_save_failure(opts){_valid?(true, opts)}
1151
+ raise(ValidationFailed.new(errors)) if raise_on_failure?(opts)
1152
+ return
1153
+ end
1039
1154
  end
1040
1155
  checked_save_failure(opts){checked_transaction(opts){_save(columns, opts)}}
1041
1156
  end
@@ -1075,7 +1190,8 @@ module Sequel
1075
1190
  end
1076
1191
 
1077
1192
  # Set all values using the entries in the hash, except for the keys
1078
- # given in except.
1193
+ # given in except. You should probably use +set_fields+ or +set_only+
1194
+ # instead of this method, as blacklist approaches to security are a bad idea.
1079
1195
  #
1080
1196
  # artist.set_except({:name=>'Jim'}, :hometown)
1081
1197
  # artist.name # => 'Jim'
@@ -1086,10 +1202,10 @@ module Sequel
1086
1202
  # For each of the fields in the given array +fields+, call the setter
1087
1203
  # method with the value of that +hash+ entry for the field. Returns self.
1088
1204
  #
1089
- # artist.set_fields({:name=>'Jim'}, :name)
1205
+ # artist.set_fields({:name=>'Jim'}, [:name])
1090
1206
  # artist.name # => 'Jim'
1091
1207
  #
1092
- # artist.set_fields({:hometown=>'LA'}, :name)
1208
+ # artist.set_fields({:hometown=>'LA'}, [:name])
1093
1209
  # artist.name # => nil
1094
1210
  # artist.hometown # => 'Sac'
1095
1211
  def set_fields(hash, fields)
@@ -1098,16 +1214,24 @@ module Sequel
1098
1214
  end
1099
1215
 
1100
1216
  # Set the values using the entries in the hash, only if the key
1101
- # is included in only.
1217
+ # is included in only. It may be a better idea to use +set_fields+
1218
+ # instead of this method.
1102
1219
  #
1103
1220
  # artist.set_only({:name=>'Jim'}, :name)
1104
1221
  # artist.name # => 'Jim'
1105
1222
  #
1106
- # artist.set_only({:hometown=>'LA'}, :name) # Raise error
1223
+ # artist.set_only({:hometown=>'LA'}, :name) # Raise Error
1107
1224
  def set_only(hash, *only)
1108
1225
  set_restricted(hash, only.flatten, false)
1109
1226
  end
1110
1227
 
1228
+ # Replace the current values with hash. Should definitely not be
1229
+ # used with untrusted input, and should probably not be called
1230
+ # directly by user code.
1231
+ def set_values(hash)
1232
+ @values = hash
1233
+ end
1234
+
1111
1235
  # Clear the setter_methods cache when a method is added
1112
1236
  def singleton_method_added(meth)
1113
1237
  @singleton_setter_added = true if meth.to_s =~ SETTER_METHOD_REGEXP
@@ -1122,7 +1246,7 @@ module Sequel
1122
1246
  @this ||= model.dataset.filter(pk_hash).limit(1).naked
1123
1247
  end
1124
1248
 
1125
- # Runs set with the passed hash and then runs save_changes.
1249
+ # Runs #set with the passed hash and then runs save_changes.
1126
1250
  #
1127
1251
  # artist.update(:name=>'Jim') # UPDATE artists SET name = 'Jim' WHERE (id = 1)
1128
1252
  def update(hash)
@@ -1130,7 +1254,7 @@ module Sequel
1130
1254
  end
1131
1255
 
1132
1256
  # Update all values using the entries in the hash, ignoring any setting of
1133
- # allowed_columns or restricted columns in the model.
1257
+ # +allowed_columns+ or +restricted_columns+ in the model.
1134
1258
  #
1135
1259
  # Artist.set_restricted_columns(:name)
1136
1260
  # artist.update_all(:name=>'Jim') # UPDATE artists SET name = 'Jim' WHERE (id = 1)
@@ -1139,7 +1263,8 @@ module Sequel
1139
1263
  end
1140
1264
 
1141
1265
  # Update all values using the entries in the hash, except for the keys
1142
- # given in except.
1266
+ # given in except. You should probably use +update_fields+ or +update_only+
1267
+ # instead of this method, as blacklist approaches to security are a bad idea.
1143
1268
  #
1144
1269
  # artist.update_except({:name=>'Jim'}, :hometown) # UPDATE artists SET name = 'Jim' WHERE (id = 1)
1145
1270
  def update_except(hash, *except)
@@ -1149,10 +1274,10 @@ module Sequel
1149
1274
  # Update the instances values by calling +set_fields+ with the +hash+
1150
1275
  # and +fields+, then save any changes to the record. Returns self.
1151
1276
  #
1152
- # artist.update_fields({:name=>'Jim'}, :name)
1277
+ # artist.update_fields({:name=>'Jim'}, [:name])
1153
1278
  # # UPDATE artists SET name = 'Jim' WHERE (id = 1)
1154
1279
  #
1155
- # artist.update_fields({:hometown=>'LA'}, :name)
1280
+ # artist.update_fields({:hometown=>'LA'}, [:name])
1156
1281
  # # UPDATE artists SET name = NULL WHERE (id = 1)
1157
1282
  def update_fields(hash, fields)
1158
1283
  set_fields(hash, fields)
@@ -1160,7 +1285,8 @@ module Sequel
1160
1285
  end
1161
1286
 
1162
1287
  # Update the values using the entries in the hash, only if the key
1163
- # is included in only.
1288
+ # is included in only. It may be a better idea to use +update_fields+
1289
+ # instead of this method.
1164
1290
  #
1165
1291
  # artist.update_only({:name=>'Jim'}, :name)
1166
1292
  # # UPDATE artists SET name = 'Jim' WHERE (id = 1)
@@ -1185,21 +1311,15 @@ module Sequel
1185
1311
  # artist(:name=>'Invalid').valid? # => false
1186
1312
  # artist.errors.full_messages # => ['name cannot be Invalid']
1187
1313
  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?
1314
+ _valid?(false, opts)
1196
1315
  end
1197
1316
 
1198
1317
  private
1199
1318
 
1200
- # Actually do the deletion of the object's dataset.
1319
+ # Do the deletion of the object's dataset, and check that the row
1320
+ # was actually deleted.
1201
1321
  def _delete
1202
- n = _delete_dataset.delete
1322
+ n = _delete_without_checking
1203
1323
  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
1324
  n
1205
1325
  end
@@ -1210,12 +1330,27 @@ module Sequel
1210
1330
  this
1211
1331
  end
1212
1332
 
1333
+ # Actually do the deletion of the object's dataset. Return the
1334
+ # number of rows modified.
1335
+ def _delete_without_checking
1336
+ _delete_dataset.delete
1337
+ end
1338
+
1213
1339
  # Internal destroy method, separted from destroy to
1214
1340
  # allow running inside a transaction
1215
1341
  def _destroy(opts)
1216
- raise_hook_failure(:destroy) if before_destroy == false
1217
- _destroy_delete
1218
- after_destroy
1342
+ sh = {:server=>this_server}
1343
+ db.after_rollback(sh){after_destroy_rollback}
1344
+ called = false
1345
+ around_destroy do
1346
+ called = true
1347
+ raise_hook_failure(:destroy) if before_destroy == false
1348
+ _destroy_delete
1349
+ after_destroy
1350
+ true
1351
+ end
1352
+ raise_hook_failure(:destroy) unless called
1353
+ db.after_commit(sh){after_destroy_commit}
1219
1354
  self
1220
1355
  end
1221
1356
 
@@ -1230,11 +1365,11 @@ module Sequel
1230
1365
  # the record should be refreshed from the database.
1231
1366
  def _insert
1232
1367
  ds = _insert_dataset
1233
- if ds.respond_to?(:insert_select) and h = ds.insert_select(@values)
1368
+ if !ds.opts[:select] and ds.supports_insert_select? and h = _insert_select_raw(ds)
1234
1369
  @values = h
1235
1370
  nil
1236
1371
  else
1237
- iid = ds.insert(@values)
1372
+ iid = _insert_raw(ds)
1238
1373
  # if we have a regular primary key and it's not set in @values,
1239
1374
  # we assume it's the last inserted id
1240
1375
  if (pk = autoincrementing_primary_key) && pk.is_a?(Symbol) && !@values[pk]
@@ -1243,55 +1378,95 @@ module Sequel
1243
1378
  pk
1244
1379
  end
1245
1380
  end
1246
-
1381
+
1247
1382
  # The dataset to use when inserting a new object. The same as the model's
1248
1383
  # dataset by default.
1249
1384
  def _insert_dataset
1250
1385
  model.dataset
1251
1386
  end
1252
1387
 
1388
+ # Insert into the given dataset and return the primary key created (if any).
1389
+ def _insert_raw(ds)
1390
+ ds.insert(@values)
1391
+ end
1392
+
1393
+ # Insert into the given dataset and return the hash of column values.
1394
+ def _insert_select_raw(ds)
1395
+ ds.insert_select(@values)
1396
+ end
1397
+
1253
1398
  # Refresh using a particular dataset, used inside save to make sure the same server
1254
1399
  # is used for reading newly inserted values from the database
1255
1400
  def _refresh(dataset)
1256
- set_values(dataset.first || raise(Error, "Record not found"))
1401
+ set_values(_refresh_get(dataset) || raise(Error, "Record not found"))
1257
1402
  changed_columns.clear
1258
1403
  self
1259
1404
  end
1405
+
1406
+ # Get the row of column data from the database.
1407
+ def _refresh_get(dataset)
1408
+ dataset.first
1409
+ end
1260
1410
 
1261
1411
  # Internal version of save, split from save to allow running inside
1262
1412
  # it's own transaction.
1263
1413
  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
1414
+ sh = {:server=>this_server}
1415
+ db.after_rollback(sh){after_rollback}
1416
+ was_new = false
1417
+ pk = nil
1418
+ called_save = false
1419
+ called_cu = false
1420
+ around_save do
1421
+ called_save = true
1422
+ raise_hook_failure(:save) if before_save == false
1423
+ if new?
1424
+ was_new = true
1425
+ around_create do
1426
+ called_cu = true
1427
+ raise_hook_failure(:create) if before_create == false
1428
+ pk = _insert
1429
+ @this = nil
1430
+ @new = false
1431
+ @was_new = true
1432
+ after_create
1433
+ true
1434
+ end
1435
+ raise_hook_failure(:create) unless called_cu
1436
+ else
1437
+ around_update do
1438
+ called_cu = true
1439
+ raise_hook_failure(:update) if before_update == false
1440
+ if columns.empty?
1441
+ @columns_updated = if opts[:changed]
1442
+ @values.reject{|k,v| !changed_columns.include?(k)}
1443
+ else
1444
+ _save_update_all_columns_hash
1445
+ end
1446
+ changed_columns.clear
1447
+ else # update only the specified columns
1448
+ @columns_updated = @values.reject{|k, v| !columns.include?(k)}
1449
+ changed_columns.reject!{|c| columns.include?(c)}
1450
+ end
1451
+ _update_columns(@columns_updated)
1452
+ @this = nil
1453
+ after_update
1454
+ true
1455
+ end
1456
+ raise_hook_failure(:update) unless called_cu
1457
+ end
1272
1458
  after_save
1459
+ true
1460
+ end
1461
+ raise_hook_failure(:save) unless called_save
1462
+ if was_new
1273
1463
  @was_new = nil
1274
1464
  pk ? _save_refresh : changed_columns.clear
1275
1465
  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
1466
  @columns_updated = nil
1293
1467
  end
1294
1468
  @modified = false
1469
+ db.after_commit(sh){after_commit}
1295
1470
  self
1296
1471
  end
1297
1472
 
@@ -1321,9 +1496,10 @@ module Sequel
1321
1496
  _update(columns) unless columns.empty?
1322
1497
  end
1323
1498
 
1324
- # Update this instance's dataset with the supplied column hash.
1499
+ # Update this instance's dataset with the supplied column hash,
1500
+ # checking that only a single row was modified.
1325
1501
  def _update(columns)
1326
- n = _update_dataset.update(columns)
1502
+ n = _update_without_checking(columns)
1327
1503
  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
1504
  n
1329
1505
  end
@@ -1334,7 +1510,46 @@ module Sequel
1334
1510
  this
1335
1511
  end
1336
1512
 
1337
- # If not raising on failure, check for BeforeHookFailed
1513
+ # Update this instances dataset with the supplied column hash.
1514
+ def _update_without_checking(columns)
1515
+ _update_dataset.update(columns)
1516
+ end
1517
+
1518
+ # Internal validation method. If +raise_errors+ is +true+, hook
1519
+ # failures will be raised as HookFailure exceptions. If it is
1520
+ # +false+, +false+ will be returned instead.
1521
+ def _valid?(raise_errors, opts)
1522
+ errors.clear
1523
+ called = false
1524
+ error = false
1525
+ around_validation do
1526
+ called = true
1527
+ if before_validation == false
1528
+ if raise_errors
1529
+ raise_hook_failure(:validation)
1530
+ else
1531
+ error = true
1532
+ end
1533
+ false
1534
+ else
1535
+ validate
1536
+ after_validation
1537
+ errors.empty?
1538
+ end
1539
+ end
1540
+ error = true unless called
1541
+ if error
1542
+ if raise_errors
1543
+ raise_hook_failure(:validation)
1544
+ else
1545
+ false
1546
+ end
1547
+ else
1548
+ errors.empty?
1549
+ end
1550
+ end
1551
+
1552
+ # If not raising on failure, check for HookFailed
1338
1553
  # being raised by yielding and swallow it.
1339
1554
  def checked_save_failure(opts)
1340
1555
  if raise_on_failure?(opts)
@@ -1342,7 +1557,7 @@ module Sequel
1342
1557
  else
1343
1558
  begin
1344
1559
  yield
1345
- rescue BeforeHookFailed
1560
+ rescue HookFailed
1346
1561
  nil
1347
1562
  end
1348
1563
  end
@@ -1350,7 +1565,14 @@ module Sequel
1350
1565
 
1351
1566
  # If transactions should be used, wrap the yield in a transaction block.
1352
1567
  def checked_transaction(opts={})
1353
- use_transaction?(opts) ? db.transaction(opts){yield} : yield
1568
+ use_transaction?(opts) ? db.transaction({:server=>this_server}.merge(opts)){yield} : yield
1569
+ end
1570
+
1571
+ # Set the columns with the given hash. By default, the same as +set+, but
1572
+ # exists so it can be overridden. This is called only for new records, before
1573
+ # changed_columns is cleared.
1574
+ def initialize_set(h)
1575
+ set(h)
1354
1576
  end
1355
1577
 
1356
1578
  # Default inspection output for the values hash, overwrite to change what #inspect displays.
@@ -1369,7 +1591,7 @@ module Sequel
1369
1591
  # Raise an error appropriate to the hook type. May be swallowed by
1370
1592
  # checked_save_failure depending on the raise_on_failure? setting.
1371
1593
  def raise_hook_failure(type)
1372
- raise BeforeHookFailed, "one of the before_#{type} hooks returned false"
1594
+ raise HookFailed, "one of the before_#{type} hooks returned false"
1373
1595
  end
1374
1596
 
1375
1597
  # Set the columns, filtered by the only and except arrays.
@@ -1385,17 +1607,21 @@ module Sequel
1385
1607
  if meths.include?(m)
1386
1608
  send(m, v)
1387
1609
  elsif strict
1388
- raise Error, "method #{m} doesn't exist or access is restricted to it"
1610
+ # Avoid using respond_to? or creating symbols from user input
1611
+ if public_methods.map{|s| s.to_s}.include?(m)
1612
+ if Array(model.primary_key).map{|s| s.to_s}.member?(k.to_s) && model.restrict_primary_key?
1613
+ raise Error, "#{k} is a restricted primary key"
1614
+ else
1615
+ raise Error, "#{k} is a restricted column"
1616
+ end
1617
+ else
1618
+ raise Error, "method #{m} doesn't exist"
1619
+ end
1389
1620
  end
1390
1621
  end
1391
1622
  self
1392
1623
  end
1393
1624
 
1394
- # Replace the current values with hash.
1395
- def set_values(hash)
1396
- @values = hash
1397
- end
1398
-
1399
1625
  # Returns all methods that can be used for attribute
1400
1626
  # assignment (those that end with =), modified by the only
1401
1627
  # and except arguments:
@@ -1424,6 +1650,12 @@ module Sequel
1424
1650
  meths
1425
1651
  end
1426
1652
  end
1653
+
1654
+ # The server/shard that the model object's dataset uses, or :default if the
1655
+ # model object's dataset does not have an associated shard.
1656
+ def this_server
1657
+ primary_key ? (this.opts[:server] || :default) : (model.dataset.opts[:server] || :default)
1658
+ end
1427
1659
 
1428
1660
  # Typecast the value to the column's type if typecasting. Calls the database's
1429
1661
  # typecast_value method, so database adapters can override/augment the handling
@@ -1461,6 +1693,18 @@ module Sequel
1461
1693
  # Artist.dataset.model # => Artist
1462
1694
  attr_accessor :model
1463
1695
 
1696
+ # Assume if a single integer is given that it is a lookup by primary
1697
+ # key, and call with_pk with the argument.
1698
+ #
1699
+ # Artist.dataset[1] # SELECT * FROM artists WHERE (id = 1) LIMIT 1
1700
+ def [](*args)
1701
+ if args.length == 1 && (i = args.at(0)) && i.is_a?(Integer)
1702
+ with_pk(i)
1703
+ else
1704
+ super
1705
+ end
1706
+ end
1707
+
1464
1708
  # Destroy each row in the dataset by instantiating it and then calling
1465
1709
  # destroy on the resulting model object. This isn't as fast as deleting
1466
1710
  # the dataset, which does a single SQL call, but this runs any destroy
@@ -1491,6 +1735,19 @@ module Sequel
1491
1735
  super(pk, value_column)
1492
1736
  end
1493
1737
  end
1738
+
1739
+ # Given a primary key value, return the first record in the dataset with that primary key
1740
+ # value.
1741
+ #
1742
+ # # Single primary key
1743
+ # Artist.dataset.with_pk(1) # SELECT * FROM artists WHERE (id = 1) LIMIT 1
1744
+ #
1745
+ # # Composite primary key
1746
+ # Artist.dataset.with_pk([1, 2]) # SELECT * FROM artists
1747
+ # # WHERE ((id1 = 1) AND (id2 = 2)) LIMIT 1
1748
+ def with_pk(pk)
1749
+ first(model.qualified_primary_key_hash(pk))
1750
+ end
1494
1751
  end
1495
1752
 
1496
1753
  extend ClassMethods