sequel 5.19.0 → 5.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +24 -0
  3. data/doc/release_notes/5.20.0.txt +89 -0
  4. data/doc/transactions.rdoc +38 -0
  5. data/lib/sequel/adapters/mysql2.rb +2 -2
  6. data/lib/sequel/adapters/shared/postgres.rb +5 -7
  7. data/lib/sequel/database/query.rb +1 -1
  8. data/lib/sequel/database/schema_generator.rb +1 -1
  9. data/lib/sequel/database/transactions.rb +57 -5
  10. data/lib/sequel/dataset/placeholder_literalizer.rb +4 -1
  11. data/lib/sequel/dataset/query.rb +1 -1
  12. data/lib/sequel/extensions/schema_dumper.rb +1 -1
  13. data/lib/sequel/model/associations.rb +35 -9
  14. data/lib/sequel/model/plugins.rb +104 -0
  15. data/lib/sequel/plugins/association_dependencies.rb +3 -3
  16. data/lib/sequel/plugins/association_pks.rb +14 -4
  17. data/lib/sequel/plugins/class_table_inheritance.rb +1 -0
  18. data/lib/sequel/plugins/composition.rb +13 -9
  19. data/lib/sequel/plugins/finder.rb +2 -2
  20. data/lib/sequel/plugins/hook_class_methods.rb +17 -5
  21. data/lib/sequel/plugins/inverted_subsets.rb +2 -2
  22. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +61 -32
  23. data/lib/sequel/plugins/subset_conditions.rb +2 -2
  24. data/lib/sequel/plugins/validation_class_methods.rb +5 -3
  25. data/lib/sequel/version.rb +1 -1
  26. data/spec/adapters/postgres_spec.rb +32 -0
  27. data/spec/core/database_spec.rb +73 -2
  28. data/spec/core/schema_spec.rb +6 -0
  29. data/spec/extensions/class_table_inheritance_spec.rb +30 -8
  30. data/spec/extensions/core_refinements_spec.rb +1 -1
  31. data/spec/extensions/hook_class_methods_spec.rb +22 -0
  32. data/spec/extensions/migration_spec.rb +13 -0
  33. data/spec/extensions/pg_auto_constraint_validations_spec.rb +8 -0
  34. data/spec/extensions/s_spec.rb +1 -1
  35. data/spec/extensions/schema_dumper_spec.rb +4 -2
  36. data/spec/integration/plugin_test.rb +15 -0
  37. data/spec/integration/transaction_test.rb +50 -0
  38. data/spec/model/associations_spec.rb +84 -4
  39. data/spec/model/plugins_spec.rb +111 -0
  40. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 96bc5f24c2b61f558037958fedfa152895eaef5e66b50999e7da139763bf0436
4
- data.tar.gz: c8376d13d1204c5c2ba969ed18d60c72fdbabd3dd9a7aa8c7729240b33678e84
3
+ metadata.gz: e092b5439a78eec1bb50a0bbc8a62c1d84ee5b27b9c5f034c250b632d03e17af
4
+ data.tar.gz: 231708d52405407b11113c55d5fdcd95e7d6155b0a8283f736876569ced6e4b1
5
5
  SHA512:
6
- metadata.gz: 1f2153dde7f8e53f24ba06dcb45155ed822d3b2a25485d03dcda52df80e2fa35a0b8f42e08cb2854db838585992226a90830ce4b37ef89939c7cbbffbf308abe
7
- data.tar.gz: af2107662ee35e87fa0089f1a66c8e621c5ab738ff53e1a35fbb7572c5137fae4bce53c2b716ec1b72b873a9415a4458eb96dd01e08be421a1b23f7d3fd4d111
6
+ metadata.gz: 4c2d7e19056e53f2d780f57f5da3c4675531f9678c6a54e922e0d59895bd2e485f12ee0b2644ed801c832a53653f75531d849cb7d60dcd9fb6a4edbfa8cb4c0b
7
+ data.tar.gz: 701b041185cc57c1966ea3b4be040e26a9e0aabfd6df45f0192f5b0b51133bfb94629f0634fa6edfc513a1c9193d455cb6222b388683d638e474b344e663ca50
data/CHANGELOG CHANGED
@@ -1,3 +1,27 @@
1
+ === 5.20.0 (2019-05-01)
2
+
3
+ * Fix reversing of alter_table add_foreign_key when :type option is used (jeremyevans) (#1615)
4
+
5
+ * Switch from using instance_exec to define_method for model associations and in some plugins (jeremyevans)
6
+
7
+ * Fix Database#server_version when using mysql2 adapter with mysql driver on MariaDB 10+ database (v-kolesnikov) (#1614)
8
+
9
+ * Make one_to_one setter method handle models that use joined datasets (jeremyevans) (#1612)
10
+
11
+ * Make auto_validations plugin work with the class_table_inheritance plugin (jeremyevans) (#1611)
12
+
13
+ * Avoid use of instance_exec for PlaceholderLiteralString#with_dataset (jeremyevans)
14
+
15
+ * Recognize float unsigned database types as float (keeguon, jeremyevans) (#1609)
16
+
17
+ * Support :savepoint options to Database#{after_commit,after_rollback} for making the hooks handle savepoints (jeremyevans)
18
+
19
+ * Avoid use of instance_exec in association_dependencies plugin (jeremyevans)
20
+
21
+ * Add pg_auto_constraint_validation_override to the pg_auto_constraint_validations plugin, for customizing columns and error message per constraint (jeremyevans)
22
+
23
+ * Make Database#check_constraints on PostgreSQL also include constraints where the related columns are not known (jeremyevans)
24
+
1
25
  === 5.19.0 (2019-04-02)
2
26
 
3
27
  * Use more optimized approach to merging hashes in ruby 2.5+ (jeremyevans)
@@ -0,0 +1,89 @@
1
+ = New Features
2
+
3
+ * Database#after_commit and #after_rollback transaction hook methods
4
+ now support a :savepoint option. Using the :savepoint option makes
5
+ the hooks savepoint-aware, so after_commit will only be called if
6
+ all enclosing savepoints and the transaction are committed, and
7
+ after_rollback will be called when any of the enclosing savepoints
8
+ are rolled back (which may be before transaction commit/rollback).
9
+ Examples:
10
+
11
+ x = nil
12
+ DB.transaction do # BEGIN
13
+ DB.transaction(savepoint: true) do # SAVEPOINT
14
+ DB.after_commit(savepoint: true){x = 1}
15
+ DB.after_rollback(savepoint: true){x = 2}
16
+ x # nil
17
+ end # RELEASE SAVEPOINT
18
+ x # nil
19
+ end # COMMIT
20
+ x # 1
21
+
22
+ x = nil
23
+ DB.transaction do # BEGIN
24
+ DB.transaction(savepoint: true) do # SAVEPOINT
25
+ DB.after_commit(savepoint: true){x = 1}
26
+ DB.after_rollback(savepoint: true){x = 2}
27
+ x # nil
28
+ raise Sequel::Rollback
29
+ end # ROLLBACK TO SAVEPOINT
30
+ x # 2
31
+ end # COMMIT
32
+ x # 2
33
+
34
+ x = nil
35
+ DB.transaction do # BEGIN
36
+ DB.transaction(savepoint: true) do # SAVEPOINT
37
+ DB.after_commit(savepoint: true){x = 1}
38
+ DB.after_rollback(savepoint: true){x = 2}
39
+ end # RELEASE SAVEPOINT
40
+ x # nil
41
+ raise Sequel::Rollback
42
+ end
43
+ x # 2
44
+
45
+ * The pg_auto_constraint_validations plugin now supports a
46
+ pg_auto_constraint_validation_override method for overriding
47
+ the columns and message for a specific constraint. This is
48
+ useful if the database cannot determine the columns (due
49
+ to the constraint containing a database function call), or
50
+ if you would like to customize the message per constraint.
51
+
52
+ = Other Improvements
53
+
54
+ * The one_to_one association setter now works with models that use
55
+ joined datasets, such as child models when using the
56
+ class_table_inheritance plugin.
57
+
58
+ * Database#check_constraints on PostgreSQL now also includes CHECK
59
+ constraints where the related columns are not known. The :columns
60
+ entry in the hash will be an empty array in such cases. The
61
+ exclusion of such constraints in previous versions was not
62
+ intentional, and the documentation implied that all CHECK
63
+ constraints were returned.
64
+
65
+ * Many cases where instance_exec was previously used on model
66
+ instances have been changed so that instance methods are defined
67
+ and called instead. This avoids the creation of singleton classes
68
+ for model instances, and can significantly improve performance in
69
+ some cases. This affects all associations as well as the
70
+ following plugins:
71
+
72
+ * composition
73
+ * hook_class_methods
74
+ * validation_class_methods
75
+
76
+ Other cases where instance_exec is now avoided and a different
77
+ approach is used:
78
+
79
+ * association_dependencies plugin
80
+ * PlaceholderLiteralString#with_dataset
81
+
82
+ * The auto_validations plugin now works with child models when using
83
+ the class_table_inheritance plugin.
84
+
85
+ * Database#server_version now works correctly in the mysql2 adapter
86
+ when using the MySQL driver with MariaDB 10+.
87
+
88
+ * The float unsigned type is now recognized and supported in the
89
+ schema parser and schema_dumper extension.
@@ -169,6 +169,44 @@ If you want the current savepoint and potentially enclosing savepoints to be rol
169
169
  end # ROLLBACK TO SAVEPOINT
170
170
  end # ROLLBACK
171
171
 
172
+ === Savepoint Hooks
173
+
174
+ When using savepoints, you can use the +:savepoint+ option to +after_commit+ or +after_rollback+ to use a savepoint hook. For +after_commit+, this will only run the hook after transaction commit if all enclosing savepoints are released (not rolled back). For +after_rollback+, this will run the hook after any enclosing savepoint is rolled back (before transaction commit), or after the transaction is rolled back if all enclosing savepoints are released:
175
+
176
+ x = nil
177
+ DB.transaction do # BEGIN
178
+ DB.transaction(savepoint: true) do # SAVEPOINT
179
+ DB.after_commit(savepoint: true){x = 1}
180
+ DB.after_rollback(savepoint: true){x = 2}
181
+ x # nil
182
+ end # RELEASE SAVEPOINT
183
+ x # nil
184
+ end # COMMIT
185
+ x # 1
186
+
187
+ x = nil
188
+ DB.transaction do # BEGIN
189
+ DB.transaction(savepoint: true) do # SAVEPOINT
190
+ DB.after_commit(savepoint: true){x = 1}
191
+ DB.after_rollback(savepoint: true){x = 2}
192
+ x # nil
193
+ raise Sequel::Rollback
194
+ end # ROLLBACK TO SAVEPOINT
195
+ x # 2
196
+ end # COMMIT
197
+ x # 2
198
+
199
+ x = nil
200
+ DB.transaction do # BEGIN
201
+ DB.transaction(savepoint: true) do # SAVEPOINT
202
+ DB.after_commit(savepoint: true){x = 1}
203
+ DB.after_rollback(savepoint: true){x = 2}
204
+ end # RELEASE SAVEPOINT
205
+ x # nil
206
+ raise Sequel::Rollback
207
+ end
208
+ x # 2
209
+
172
210
  == Prepared Transactions / Two-Phase Commit
173
211
 
174
212
  Sequel supports database prepared transactions on PostgreSQL, MySQL, and H2. With prepared transactions, at the end of the transaction, the transaction is not immediately committed (it acts like a rollback). Later, you can call +commit_prepared_transaction+ to commit the transaction or +rollback_prepared_transaction+ to roll the transaction back. Prepared transactions are usually used with distributed databases to make sure all databases commit the same transaction or none of them do.
@@ -78,8 +78,8 @@ module Sequel
78
78
  end
79
79
 
80
80
  # Return the version of the MySQL server to which we are connecting.
81
- def server_version(server=nil)
82
- @server_version ||= (synchronize(server){|conn| conn.server_info[:id]} || super)
81
+ def server_version(_server=nil)
82
+ @server_version ||= super()
83
83
  end
84
84
 
85
85
  private
@@ -220,24 +220,22 @@ module Sequel
220
220
  # A hash of metadata for CHECK constraints on the table.
221
221
  # Keys are CHECK constraint name symbols. Values are hashes with the following keys:
222
222
  # :definition :: An SQL fragment for the definition of the constraint
223
- # :columns :: An array of column symbols for the columns referenced in the constraint
223
+ # :columns :: An array of column symbols for the columns referenced in the constraint,
224
+ # can be an empty array if the database cannot deteremine the column symbols.
224
225
  def check_constraints(table)
225
226
  m = output_identifier_meth
226
227
 
227
228
  rows = metadata_dataset.
228
229
  from{pg_constraint.as(:co)}.
229
- join(Sequel[:pg_attribute].as(:att), :attrelid=>:conrelid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:conkey])).
230
+ left_join(Sequel[:pg_attribute].as(:att), :attrelid=>:conrelid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:conkey])).
230
231
  where(:conrelid=>regclass_oid(table), :contype=>'c').
231
232
  select{[co[:conname].as(:constraint), att[:attname].as(:column), pg_get_constraintdef(co[:oid]).as(:definition)]}
232
233
 
233
234
  hash = {}
234
235
  rows.each do |row|
235
236
  constraint = m.call(row[:constraint])
236
- if entry = hash[constraint]
237
- entry[:columns] << m.call(row[:column])
238
- else
239
- hash[constraint] = {:definition=>row[:definition], :columns=>[m.call(row[:column])]}
240
- end
237
+ entry = hash[constraint] ||= {:definition=>row[:definition], :columns=>[]}
238
+ entry[:columns] << m.call(row[:column]) if row[:column]
241
239
  end
242
240
 
243
241
  hash
@@ -331,7 +331,7 @@ module Sequel
331
331
  :time
332
332
  when /\A(bool(ean)?)\z/io
333
333
  :boolean
334
- when /\A(real|float|double( precision)?|double\(\d+,\d+\)( unsigned)?)\z/io
334
+ when /\A(real|float( unsigned)?|double( precision)?|double\(\d+,\d+\)( unsigned)?)\z/io
335
335
  :float
336
336
  when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(\d+|false|true)\))?))\z/io
337
337
  $1 && ['0', 'false'].include?($1) ? :integer : :decimal
@@ -634,7 +634,7 @@ module Sequel
634
634
 
635
635
  # Drop a composite foreign key constraint
636
636
  def drop_composite_foreign_key(columns, opts)
637
- @operations << {:op => :drop_constraint, :type => :foreign_key, :columns => columns}.merge!(opts)
637
+ @operations << opts.merge(:op => :drop_constraint, :type => :foreign_key, :columns => columns)
638
638
  nil
639
639
  end
640
640
  end
@@ -25,13 +25,19 @@ module Sequel
25
25
  # Otherwise, add the block to the list of blocks to call after the currently
26
26
  # in progress transaction commits (and only if it commits).
27
27
  # Options:
28
+ # :savepoint :: If currently inside a savepoint, only run this hook on transaction
29
+ # commit if all enclosing savepoints have been released.
28
30
  # :server :: The server/shard to use.
29
31
  def after_commit(opts=OPTS, &block)
30
32
  raise Error, "must provide block to after_commit" unless block
31
33
  synchronize(opts[:server]) do |conn|
32
34
  if h = _trans(conn)
33
35
  raise Error, "cannot call after_commit in a prepared transaction" if h[:prepare]
34
- add_transaction_hook(conn, :after_commit, block)
36
+ if opts[:savepoint] && in_savepoint?(conn)
37
+ add_savepoint_hook(conn, :after_commit, block)
38
+ else
39
+ add_transaction_hook(conn, :after_commit, block)
40
+ end
35
41
  else
36
42
  yield
37
43
  end
@@ -42,13 +48,20 @@ module Sequel
42
48
  # Otherwise, add the block to the list of the blocks to call after the currently
43
49
  # in progress transaction rolls back (and only if it rolls back).
44
50
  # Options:
51
+ # :savepoint :: If currently inside a savepoint, run this hook immediately when
52
+ # any enclosing savepoint is rolled back, which may be before the transaction
53
+ # commits or rollsback.
45
54
  # :server :: The server/shard to use.
46
55
  def after_rollback(opts=OPTS, &block)
47
56
  raise Error, "must provide block to after_rollback" unless block
48
57
  synchronize(opts[:server]) do |conn|
49
58
  if h = _trans(conn)
50
59
  raise Error, "cannot call after_rollback in a prepared transaction" if h[:prepare]
51
- add_transaction_hook(conn, :after_rollback, block)
60
+ if opts[:savepoint] && in_savepoint?(conn)
61
+ add_savepoint_hook(conn, :after_rollback, block)
62
+ else
63
+ add_transaction_hook(conn, :after_rollback, block)
64
+ end
52
65
  end
53
66
  end
54
67
  end
@@ -298,6 +311,13 @@ module Sequel
298
311
  Sequel.synchronize{@transactions[conn] = hash}
299
312
  end
300
313
 
314
+ # Set the given callable as a hook to be called. Type should be either
315
+ # :after_commit or :after_rollback.
316
+ def add_savepoint_hook(conn, type, block)
317
+ savepoint = _trans(conn)[:savepoints].last
318
+ (savepoint[type] ||= []) << block
319
+ end
320
+
301
321
  # Set the given callable as a hook to be called. Type should be either
302
322
  # :after_commit or :after_rollback.
303
323
  def add_transaction_hook(conn, type, block)
@@ -401,6 +421,14 @@ module Sequel
401
421
  supports_savepoints? && savepoint_level(conn) > 1
402
422
  end
403
423
 
424
+ # Retrieve the savepoint hooks that should be run for the given
425
+ # connection and commit status.
426
+ def savepoint_hooks(conn, committed)
427
+ if in_savepoint?(conn)
428
+ _trans(conn)[:savepoints].last[committed ? :after_commit : :after_rollback]
429
+ end
430
+ end
431
+
404
432
  # Retrieve the transaction hooks that should be run for the given
405
433
  # connection and commit status.
406
434
  def transaction_hooks(conn, committed)
@@ -411,16 +439,40 @@ module Sequel
411
439
 
412
440
  # Remove the current thread from the list of active transactions
413
441
  def remove_transaction(conn, committed)
414
- callbacks = transaction_hooks(conn, committed)
442
+ if in_savepoint?(conn)
443
+ savepoint_callbacks = savepoint_hooks(conn, committed)
444
+ if committed
445
+ savepoint_rollback_callbacks = savepoint_hooks(conn, false)
446
+ end
447
+ else
448
+ callbacks = transaction_hooks(conn, committed)
449
+ end
415
450
 
416
451
  if transaction_finished?(conn)
417
452
  h = _trans(conn)
418
453
  rolled_back = !committed
419
454
  Sequel.synchronize{h[:rolled_back] = rolled_back}
420
455
  Sequel.synchronize{@transactions.delete(conn)}
456
+ callbacks.each(&:call) if callbacks
457
+ elsif savepoint_callbacks || savepoint_rollback_callbacks
458
+ if committed
459
+ meth = in_savepoint?(conn) ? :add_savepoint_hook : :add_transaction_hook
460
+
461
+ if savepoint_callbacks
462
+ savepoint_callbacks.each do |block|
463
+ send(meth, conn, :after_commit, block)
464
+ end
465
+ end
466
+
467
+ if savepoint_rollback_callbacks
468
+ savepoint_rollback_callbacks.each do |block|
469
+ send(meth, conn, :after_rollback, block)
470
+ end
471
+ end
472
+ else
473
+ savepoint_callbacks.each(&:call)
474
+ end
421
475
  end
422
-
423
- callbacks.each(&:call) if callbacks
424
476
  end
425
477
 
426
478
  # SQL to rollback to a savepoint
@@ -170,7 +170,10 @@ module Sequel
170
170
  # receiver's dataset to the block, and the block should return the new dataset
171
171
  # to use.
172
172
  def with_dataset
173
- dup.instance_exec{@dataset = yield @dataset; self}.freeze
173
+ dataset = yield @dataset
174
+ other = dup
175
+ other.instance_variable_set(:@dataset, dataset)
176
+ other.freeze
174
177
  end
175
178
 
176
179
  # Return an array of all objects by running the SQL query for the given arguments.
@@ -1091,7 +1091,7 @@ module Sequel
1091
1091
  # # SELECT i1.id, i1.parent_id FROM i1 INNER JOIN t ON (t.id = i1.parent_id)
1092
1092
  # # ) SELECT * FROM t
1093
1093
  def with_recursive(name, nonrecursive, recursive, opts=OPTS)
1094
- raise(Error, 'This datatset does not support common table expressions') unless supports_cte?
1094
+ raise(Error, 'This dataset does not support common table expressions') unless supports_cte?
1095
1095
  if hoist_cte?(nonrecursive)
1096
1096
  s, ds = hoist_cte(nonrecursive)
1097
1097
  s.with_recursive(name, ds, recursive, opts)
@@ -37,7 +37,7 @@ module Sequel
37
37
  {:type =>schema[:type] == :boolean ? TrueClass : Integer}
38
38
  when /\Abigint(?:\((?:\d+)\))?(?: unsigned)?\z/
39
39
  {:type=>:Bignum}
40
- when /\A(?:real|float|double(?: precision)?|double\(\d+,\d+\)(?: unsigned)?)\z/
40
+ when /\A(?:real|float(?: unsigned)?|double(?: precision)?|double\(\d+,\d+\)(?: unsigned)?)\z/
41
41
  {:type=>Float}
42
42
  when 'boolean', 'bit', 'bool'
43
43
  {:type=>TrueClass}
@@ -1617,9 +1617,10 @@ module Sequel
1617
1617
  # is hash or array of two element arrays. Consider also specifying the :graph_block
1618
1618
  # option if the value for this option is not a hash or array of two element arrays
1619
1619
  # and you plan to use this association in eager_graph or association_join.
1620
- # :dataset :: A proc that is instance_execed to get the base dataset to use (before the other
1620
+ # :dataset :: A proc that is used to define the method to get the base dataset to use (before the other
1621
1621
  # options are applied). If the proc accepts an argument, it is passed the related
1622
- # association reflection.
1622
+ # association reflection. It is a best practice to always have the dataset accept an argument
1623
+ # and use the argument to return the appropriate dataset.
1623
1624
  # :distinct :: Use the DISTINCT clause when selecting associating object, both when
1624
1625
  # lazy loading and eager loading via .eager (but not when using .eager_graph).
1625
1626
  # :eager :: The associations to eagerly load via +eager+ when loading the associated object(s).
@@ -1909,7 +1910,7 @@ module Sequel
1909
1910
  # can be easily overridden in the class itself while allowing for
1910
1911
  # super to be called.
1911
1912
  def association_module_def(name, opts=OPTS, &block)
1912
- association_module(opts).module_eval{define_method(name, &block)}
1913
+ association_module(opts).send(:define_method, name, &block)
1913
1914
  end
1914
1915
 
1915
1916
  # Add a private method to the module included in the class.
@@ -1944,6 +1945,13 @@ module Sequel
1944
1945
  end
1945
1946
 
1946
1947
  association_module_def(opts.dataset_method, opts){_dataset(opts)}
1948
+ if opts[:block]
1949
+ opts[:block_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_block", 1, &opts[:block])
1950
+ end
1951
+ if opts[:dataset]
1952
+ opts[:dataset_opt_arity] = opts[:dataset].arity == 0 ? 0 : 1
1953
+ opts[:dataset_opt_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_dataset_opt", opts[:dataset_opt_arity], &opts[:dataset])
1954
+ end
1947
1955
  def_association_method(opts)
1948
1956
 
1949
1957
  return if opts[:read_only]
@@ -2204,12 +2212,28 @@ module Sequel
2204
2212
  if one_to_one
2205
2213
  opts[:setter] ||= proc do |o|
2206
2214
  up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
2215
+
2216
+ if (froms = up_ds.opts[:from]) && (from = froms[0]) && (from.is_a?(Sequel::Dataset) || (from.is_a?(Sequel::SQL::AliasedExpression) && from.expression.is_a?(Sequel::Dataset)))
2217
+ if old = up_ds.first
2218
+ cks.each{|k| old.set_column_value(:"#{k}=", nil)}
2219
+ end
2220
+ save_old = true
2221
+ end
2222
+
2207
2223
  if o
2208
- up_ds = up_ds.exclude(o.pk_hash) unless o.new?
2224
+ if !o.new? && !save_old
2225
+ up_ds = up_ds.exclude(o.pk_hash)
2226
+ end
2209
2227
  cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2210
2228
  end
2229
+
2211
2230
  checked_transaction do
2212
- up_ds.skip_limit_check.update(ck_nil_hash)
2231
+ if save_old
2232
+ old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
2233
+ else
2234
+ up_ds.skip_limit_check.update(ck_nil_hash)
2235
+ end
2236
+
2213
2237
  o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2214
2238
  end
2215
2239
  end
@@ -2287,7 +2311,8 @@ module Sequel
2287
2311
  end
2288
2312
  ds = ds.clone(:model_object => self)
2289
2313
  ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph] && opts.eager_graph_lazy_dataset?
2290
- ds = instance_exec(ds, &opts[:block]) if opts[:block]
2314
+ # block method is private
2315
+ ds = send(opts[:block_method], ds) if opts[:block_method]
2291
2316
  ds
2292
2317
  end
2293
2318
 
@@ -2310,10 +2335,11 @@ module Sequel
2310
2335
  # Return an association dataset for the given association reflection
2311
2336
  def _dataset(opts)
2312
2337
  raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
2313
- ds = if opts[:dataset].arity == 1
2314
- instance_exec(opts, &opts[:dataset])
2338
+ ds = if opts[:dataset_opt_arity] == 1
2339
+ # dataset_opt_method is private
2340
+ send(opts[:dataset_opt_method], opts)
2315
2341
  else
2316
- instance_exec(&opts[:dataset])
2342
+ send(opts[:dataset_opt_method])
2317
2343
  end
2318
2344
  _apply_association_options(opts, ds)
2319
2345
  end