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