sequel 4.38.0 → 4.39.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +28 -0
  3. data/doc/association_basics.rdoc +1 -1
  4. data/doc/core_extensions.rdoc +8 -8
  5. data/doc/object_model.rdoc +7 -8
  6. data/doc/querying.rdoc +2 -2
  7. data/doc/release_notes/4.39.0.txt +127 -0
  8. data/doc/sql.rdoc +31 -31
  9. data/doc/transactions.rdoc +1 -1
  10. data/doc/virtual_rows.rdoc +1 -1
  11. data/lib/sequel/adapters/mock.rb +22 -66
  12. data/lib/sequel/adapters/shared/access.rb +2 -0
  13. data/lib/sequel/adapters/shared/cubrid.rb +2 -0
  14. data/lib/sequel/adapters/shared/db2.rb +2 -0
  15. data/lib/sequel/adapters/shared/firebird.rb +2 -0
  16. data/lib/sequel/adapters/shared/informix.rb +2 -0
  17. data/lib/sequel/adapters/shared/mssql.rb +10 -2
  18. data/lib/sequel/adapters/shared/mysql.rb +17 -4
  19. data/lib/sequel/adapters/shared/oracle.rb +9 -0
  20. data/lib/sequel/adapters/shared/postgres.rb +34 -1
  21. data/lib/sequel/adapters/shared/sqlanywhere.rb +1 -0
  22. data/lib/sequel/adapters/shared/sqlite.rb +8 -0
  23. data/lib/sequel/core.rb +10 -0
  24. data/lib/sequel/database.rb +5 -0
  25. data/lib/sequel/database/connecting.rb +28 -0
  26. data/lib/sequel/database/misc.rb +0 -51
  27. data/lib/sequel/database/query.rb +1 -1
  28. data/lib/sequel/database/schema_generator.rb +9 -0
  29. data/lib/sequel/database/transactions.rb +65 -0
  30. data/lib/sequel/dataset/actions.rb +1 -1
  31. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  32. data/lib/sequel/extensions/core_extensions.rb +1 -1
  33. data/lib/sequel/extensions/core_refinements.rb +1 -1
  34. data/lib/sequel/extensions/migration.rb +17 -1
  35. data/lib/sequel/extensions/no_auto_literal_strings.rb +1 -1
  36. data/lib/sequel/extensions/pg_array_ops.rb +1 -1
  37. data/lib/sequel/extensions/pg_hstore_ops.rb +1 -1
  38. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  39. data/lib/sequel/extensions/pg_interval.rb +1 -1
  40. data/lib/sequel/extensions/pg_json_ops.rb +28 -15
  41. data/lib/sequel/extensions/pg_range_ops.rb +1 -1
  42. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  43. data/lib/sequel/extensions/select_remove.rb +1 -1
  44. data/lib/sequel/extensions/sql_expr.rb +1 -1
  45. data/lib/sequel/model/associations.rb +1 -1
  46. data/lib/sequel/plugins/active_model.rb +19 -8
  47. data/lib/sequel/plugins/dataset_associations.rb +1 -1
  48. data/lib/sequel/plugins/hook_class_methods.rb +38 -2
  49. data/lib/sequel/plugins/list.rb +1 -1
  50. data/lib/sequel/plugins/pg_array_associations.rb +3 -3
  51. data/lib/sequel/plugins/timestamps.rb +15 -2
  52. data/lib/sequel/plugins/touch.rb +6 -0
  53. data/lib/sequel/sql.rb +15 -11
  54. data/lib/sequel/version.rb +1 -1
  55. data/spec/adapters/mysql_spec.rb +18 -0
  56. data/spec/adapters/postgres_spec.rb +30 -1
  57. data/spec/core/database_spec.rb +29 -0
  58. data/spec/core/mock_adapter_spec.rb +32 -1
  59. data/spec/extensions/migration_spec.rb +46 -0
  60. data/spec/extensions/pg_interval_spec.rb +1 -0
  61. data/spec/extensions/pg_json_ops_spec.rb +13 -0
  62. data/spec/extensions/timestamps_spec.rb +11 -0
  63. data/spec/extensions/touch_spec.rb +8 -0
  64. metadata +4 -2
@@ -88,7 +88,7 @@ module Sequel
88
88
  # :schema :: An explicit schema to use. It may also be implicitly provided
89
89
  # via the table name.
90
90
  #
91
- # If schema parsing is supported by the database, the column information should hash at least contain the
91
+ # If schema parsing is supported by the database, the column information hash should contain at least the
92
92
  # following entries:
93
93
  #
94
94
  # :allow_null :: Whether NULL is an allowed value for the column.
@@ -336,6 +336,15 @@ module Sequel
336
336
  # See CreateTableGenerator#column for the available options.
337
337
  #
338
338
  # add_column(:name, String) # ADD COLUMN name varchar(255)
339
+ #
340
+ # PostgreSQL specific options:
341
+ #
342
+ # :if_not_exists :: Set to true to not add the column if it already exists (PostgreSQL 9.6+)
343
+ #
344
+ # MySQL specific options:
345
+ #
346
+ # :after :: The name of an existing column that the new column should be positioned after
347
+ # :first :: Create this new column before all other existing columns
339
348
  def add_column(name, type, opts = OPTS)
340
349
  @operations << {:op => :add_column, :name => name, :type => type}.merge!(opts)
341
350
  end
@@ -32,6 +32,58 @@ module Sequel
32
32
  # on the same connection.
33
33
  attr_accessor :transaction_isolation_level
34
34
 
35
+ # If a transaction is not currently in process, yield to the block immediately.
36
+ # Otherwise, add the block to the list of blocks to call after the currently
37
+ # in progress transaction commits (and only if it commits).
38
+ # Options:
39
+ # :server :: The server/shard to use.
40
+ def after_commit(opts=OPTS, &block)
41
+ raise Error, "must provide block to after_commit" unless block
42
+ synchronize(opts[:server]) do |conn|
43
+ if h = _trans(conn)
44
+ raise Error, "cannot call after_commit in a prepared transaction" if h[:prepare]
45
+ add_transaction_hook(conn, :after_commit, block)
46
+ else
47
+ yield
48
+ end
49
+ end
50
+ end
51
+
52
+ # If a transaction is not currently in progress, ignore the block.
53
+ # Otherwise, add the block to the list of the blocks to call after the currently
54
+ # in progress transaction rolls back (and only if it rolls back).
55
+ # Options:
56
+ # :server :: The server/shard to use.
57
+ def after_rollback(opts=OPTS, &block)
58
+ raise Error, "must provide block to after_rollback" unless block
59
+ synchronize(opts[:server]) do |conn|
60
+ if h = _trans(conn)
61
+ raise Error, "cannot call after_rollback in a prepared transaction" if h[:prepare]
62
+ add_transaction_hook(conn, :after_rollback, block)
63
+ end
64
+ end
65
+ end
66
+
67
+ # Return true if already in a transaction given the options,
68
+ # false otherwise. Respects the :server option for selecting
69
+ # a shard.
70
+ def in_transaction?(opts=OPTS)
71
+ synchronize(opts[:server]){|conn| !!_trans(conn)}
72
+ end
73
+
74
+ # Returns a proc that you can call to check if the transaction
75
+ # has been rolled back. The proc will return nil if the
76
+ # transaction is still in progress, true if the transaction was
77
+ # rolled back, and false if it was committed. Raises an
78
+ # Error if called outside a transaction. Respects the :server
79
+ # option for selecting a shard.
80
+ def rollback_checker(opts=OPTS)
81
+ synchronize(opts[:server]) do |conn|
82
+ raise Error, "not in a transaction" unless t = _trans(conn)
83
+ t[:rollback_checker] ||= proc{t[:rolled_back]}
84
+ end
85
+ end
86
+
35
87
  # Starts a database transaction. When a database transaction is used,
36
88
  # either all statements are successful or none of the statements are
37
89
  # successful. Note that MySQL MyISAM tables do not support transactions.
@@ -215,6 +267,13 @@ module Sequel
215
267
  Sequel.synchronize{@transactions[conn] = hash}
216
268
  end
217
269
 
270
+ # Set the given callable as a hook to be called. Type should be either
271
+ # :after_commit or :after_rollback.
272
+ def add_transaction_hook(conn, type, block)
273
+ hooks = _trans(conn)[type] ||= []
274
+ hooks << block
275
+ end
276
+
218
277
  # Whether the current thread/connection is already inside a transaction
219
278
  def already_in_transaction?(conn, opts)
220
279
  _trans(conn) && (!supports_savepoints? || !opts[:savepoint])
@@ -305,6 +364,11 @@ module Sequel
305
364
  :execute
306
365
  end
307
366
 
367
+ # Which transaction errors to translate, blank by default.
368
+ def database_error_classes
369
+ []
370
+ end
371
+
308
372
  # Retrieve the transaction hooks that should be run for the given
309
373
  # connection and commit status.
310
374
  def transaction_hooks(conn, committed)
@@ -318,6 +382,7 @@ module Sequel
318
382
  callbacks = transaction_hooks(conn, committed)
319
383
 
320
384
  if transaction_finished?(conn)
385
+ @transactions[conn][:rolled_back] = !committed
321
386
  Sequel.synchronize{@transactions.delete(conn)}
322
387
  end
323
388
 
@@ -817,7 +817,7 @@ module Sequel
817
817
  # DB[:table].update(:x=>nil) # UPDATE table SET x = NULL
818
818
  # # => 10
819
819
  #
820
- # DB[:table].update(:x=>Sequel.expr(:x)+1, :y=>0) # UPDATE table SET x = (x + 1), y = 0
820
+ # DB[:table].update(:x=>Sequel[:x]+1, :y=>0) # UPDATE table SET x = (x + 1), y = 0
821
821
  # # => 10
822
822
  def update(values=OPTS, &block)
823
823
  sql = update_sql(values)
@@ -444,7 +444,7 @@ module Sequel
444
444
  # for all columns unless the :allow_nil option is given.
445
445
  def generator_add_constraint_from_validation(generator, val, cons)
446
446
  if val[:allow_nil]
447
- nil_cons = Sequel.expr(val[:columns].map{|c| [c, nil]})
447
+ nil_cons = Sequel[val[:columns].map{|c| [c, nil]}]
448
448
  cons = Sequel.|(nil_cons, cons) if cons
449
449
  else
450
450
  nil_cons = Sequel.negate(val[:columns].map{|c| [c, nil]})
@@ -58,7 +58,7 @@ class Array
58
58
  # [[:a, true]].sql_expr # SQL: a IS TRUE
59
59
  # [[:a, 1], [:b, [2, 3]]].sql_expr # SQL: a = 1 AND b IN (2, 3)
60
60
  def sql_expr
61
- Sequel.expr(self)
61
+ Sequel[self]
62
62
  end
63
63
 
64
64
  # Return a <tt>Sequel::SQL::BooleanExpression</tt> created from this array, matching none
@@ -55,7 +55,7 @@ module Sequel::CoreRefinements
55
55
  # [[:a, true]].sql_expr # SQL: a IS TRUE
56
56
  # [[:a, 1], [:b, [2, 3]]].sql_expr # SQL: a = 1 AND b IN (2, 3)
57
57
  def sql_expr
58
- Sequel.expr(self)
58
+ Sequel[self]
59
59
  end
60
60
 
61
61
  # Return a <tt>Sequel::SQL::BooleanExpression</tt> created from this array, matching none
@@ -387,6 +387,8 @@ module Sequel
387
387
  # :column :: The column in the :table argument storing the migration version (default: :version).
388
388
  # :current :: The current version of the database. If not given, it is retrieved from the database
389
389
  # using the :table and :column options.
390
+ # :relative :: Run the given number of migrations, with a positive number being migrations to migrate
391
+ # up, and a negative number being migrations to migrate down (IntegerMigrator only).
390
392
  # :table :: The table containing the schema version (default: :schema_info).
391
393
  # :target :: The target version to which to migrate. If not given, migrates to the maximum version.
392
394
  #
@@ -520,8 +522,22 @@ module Sequel
520
522
  # Set up all state for the migrator instance
521
523
  def initialize(db, directory, opts=OPTS)
522
524
  super
523
- @target = opts[:target] || latest_migration_version
524
525
  @current = opts[:current] || current_migration_version
526
+ latest_version = latest_migration_version
527
+
528
+ @target = if opts[:target]
529
+ opts[:target]
530
+ elsif opts[:relative]
531
+ @current + opts[:relative]
532
+ else
533
+ latest_version
534
+ end
535
+
536
+ if @target > latest_version
537
+ @target = latest_version
538
+ elsif @target < 0
539
+ @target = 0
540
+ end
525
541
 
526
542
  raise(Error, "No current version available") unless current
527
543
  raise(Error, "No target version available, probably because no migration files found or filenames don't follow the migration filename convention") unless target
@@ -40,7 +40,7 @@
40
40
  #
41
41
  # or construct the same SQL using a non-string based approach:
42
42
  #
43
- # DB[:table].update(:column => Sequel.expr(:column) + 1)
43
+ # DB[:table].update(:column => Sequel[:column] + 1)
44
44
  #
45
45
  # Related module: Sequel::Dataset::NoAutoLiteralStrings
46
46
 
@@ -19,7 +19,7 @@
19
19
  # Also, on most Sequel expression objects, you can call the pg_array
20
20
  # method:
21
21
  #
22
- # ia = Sequel.expr(:int_array_column).pg_array
22
+ # ia = Sequel[:int_array_column].pg_array
23
23
  #
24
24
  # If you have loaded the {core_extensions extension}[rdoc-ref:doc/core_extensions.rdoc],
25
25
  # or you have loaded the core_refinements extension
@@ -20,7 +20,7 @@
20
20
  # Also, on most Sequel expression objects, you can call the hstore
21
21
  # method:
22
22
  #
23
- # h = Sequel.expr(:hstore_column).hstore
23
+ # h = Sequel[:hstore_column].hstore
24
24
  #
25
25
  # If you have loaded the {core_extensions extension}[rdoc-ref:doc/core_extensions.rdoc],
26
26
  # or you have loaded the core_refinements extension
@@ -14,7 +14,7 @@
14
14
  # Also, on most Sequel expression objects, you can call the pg_inet
15
15
  # method:
16
16
  #
17
- # r = Sequel.expr(:ip).pg_inet
17
+ # r = Sequel[:ip].pg_inet
18
18
  #
19
19
  # If you have loaded the {core_extensions extension}[rdoc-ref:doc/core_extensions.rdoc],
20
20
  # or you have loaded the core_refinements extension
@@ -42,7 +42,7 @@ module Sequel
42
42
  module Postgres
43
43
  module IntervalDatabaseMethods
44
44
  EMPTY_INTERVAL = '0'.freeze
45
- DURATION_UNITS = [:years, :months, :days, :minutes, :seconds].freeze
45
+ DURATION_UNITS = [:years, :months, :weeks, :days, :hours, :minutes, :seconds].freeze
46
46
 
47
47
  # Return an unquoted string version of the duration object suitable for
48
48
  # use as a bound variable.
@@ -24,8 +24,8 @@
24
24
  # Also, on most Sequel expression objects, you can call the pg_json
25
25
  # or pg_jsonb # method:
26
26
  #
27
- # j = Sequel.expr(:json_column).pg_json
28
- # jb = Sequel.expr(:jsonb_column).pg_jsonb
27
+ # j = Sequel[:json_column].pg_json
28
+ # jb = Sequel[:jsonb_column].pg_jsonb
29
29
  #
30
30
  # If you have loaded the {core_extensions extension}[rdoc-ref:doc/core_extensions.rdoc],
31
31
  # or you have loaded the core_refinements extension
@@ -61,16 +61,17 @@
61
61
  #
62
62
  # There are additional methods are are only supported on JSONBOp instances:
63
63
  #
64
- # j - 1 # (jsonb_column - 1)
65
- # j.concat(:h) # (jsonb_column || h)
66
- # j.contain_all(:a) # (jsonb_column ?& a)
67
- # j.contain_any(:a) # (jsonb_column ?| a)
68
- # j.contains(:h) # (jsonb_column @> h)
69
- # j.contained_by(:h) # (jsonb_column <@ h)
70
- # j.delete_path(%w'0 a') # (jsonb_column #- ARRAY['0','a'])
71
- # j.has_key?('a') # (jsonb_column ? 'a')
72
- # j.pretty # jsonb_pretty(jsonb_column)
73
- # j.set(%w'0 a', :h) # jsonb_set(jsonb_column, ARRAY['0','a'], h, true)
64
+ # j - 1 # (jsonb_column - 1)
65
+ # j.concat(:h) # (jsonb_column || h)
66
+ # j.contain_all(:a) # (jsonb_column ?& a)
67
+ # j.contain_any(:a) # (jsonb_column ?| a)
68
+ # j.contains(:h) # (jsonb_column @> h)
69
+ # j.contained_by(:h) # (jsonb_column <@ h)
70
+ # j.delete_path(%w'0 a') # (jsonb_column #- ARRAY['0','a'])
71
+ # j.has_key?('a') # (jsonb_column ? 'a')
72
+ # j.insert(%w'0 a', 'a'=>1) # jsonb_insert(jsonb_column, ARRAY[0, 'a'], '{"a":1}'::jsonb, false)
73
+ # j.pretty # jsonb_pretty(jsonb_column)
74
+ # j.set(%w'0 a', :h) # jsonb_set(jsonb_column, ARRAY['0','a'], h, true)
74
75
  #
75
76
  # If you are also using the pg_json extension, you should load it before
76
77
  # loading this extension. Doing so will allow you to use the #op method on
@@ -336,7 +337,7 @@ module Sequel
336
337
  bool_op(CONTAINED_BY, wrap_input_jsonb(other))
337
338
  end
338
339
 
339
- # Check if the other jsonb contains all entries in the receiver:
340
+ # Removes the given path from the receiver.
340
341
  #
341
342
  # jsonb_op.delete_path(:h) # (jsonb #- h)
342
343
  def delete_path(other)
@@ -351,19 +352,31 @@ module Sequel
351
352
  end
352
353
  alias include? has_key?
353
354
 
355
+ # Inserts the given jsonb value at the given path in the receiver.
356
+ # The default is to insert the value before the given path, but
357
+ # insert_after can be set to true to insert it after the given path.
358
+ #
359
+ # jsonb_op.insert(['a', 'b'], h) # jsonb_insert(jsonb, ARRAY['a', 'b'], h, false)
360
+ # jsonb_op.insert(['a', 'b'], h, true) # jsonb_insert(jsonb, ARRAY['a', 'b'], h, true)
361
+ def insert(path, other, insert_after=false)
362
+ self.class.new(function(:insert, wrap_input_array(path), wrap_input_jsonb(other), insert_after))
363
+ end
364
+
354
365
  # Return the receiver, since it is already a JSONBOp.
355
366
  def pg_jsonb
356
367
  self
357
368
  end
358
369
 
359
- # Returns a json value for the object at the given path.
370
+ # Return a pretty printed version of the receiver as a string expression.
360
371
  #
361
372
  # jsonb_op.pretty # jsonb_pretty(jsonb)
362
373
  def pretty
363
374
  Sequel::SQL::StringExpression.new(:NOOP, function(:pretty))
364
375
  end
365
376
 
366
- # Returns a json value for the object at the given path.
377
+ # Set the given jsonb value at the given path in the receiver.
378
+ # By default, this will create the value if it does not exist, but
379
+ # create_missing can be set to false to not create a new value.
367
380
  #
368
381
  # jsonb_op.set(['a', 'b'], h) # jsonb_set(jsonb, ARRAY['a', 'b'], h, true)
369
382
  # jsonb_op.set(['a', 'b'], h, false) # jsonb_set(jsonb, ARRAY['a', 'b'], h, false)
@@ -19,7 +19,7 @@
19
19
  # Also, on most Sequel expression objects, you can call the pg_range
20
20
  # method:
21
21
  #
22
- # r = Sequel.expr(:range).pg_range
22
+ # r = Sequel[:range].pg_range
23
23
  #
24
24
  # If you have loaded the {core_extensions extension}[rdoc-ref:doc/core_extensions.rdoc],
25
25
  # or you have loaded the core_refinements extension
@@ -19,7 +19,7 @@
19
19
  # Also, on most Sequel expression objects, you can call the pg_row
20
20
  # method:
21
21
  #
22
- # r = Sequel.expr(:row_column).pg_row
22
+ # r = Sequel[:row_column].pg_row
23
23
  #
24
24
  # If you have loaded the {core_extensions extension}[rdoc-ref:doc/core_extensions.rdoc],
25
25
  # or you have loaded the core_refinements extension
@@ -35,7 +35,7 @@ module Sequel
35
35
  # In this case, the code will currently use unqualified column names for all columns
36
36
  # the dataset returns, except for the columns given.
37
37
  # * This dataset has an existing explicit selection containing an item that returns
38
- # multiple database columns (e.g. Sequel.expr(:table).*, Sequel.lit('column1, column2')). In this case,
38
+ # multiple database columns (e.g. Sequel[:table].*, Sequel.lit('column1, column2')). In this case,
39
39
  # the behavior is undefined and this method should not be used.
40
40
  #
41
41
  # There may be other cases where this method does not work correctly, use it with caution.
@@ -17,6 +17,6 @@
17
17
  class Object
18
18
  # Return the object wrapper in an appropriate Sequel expression object.
19
19
  def sql_expr
20
- Sequel.expr(self)
20
+ Sequel[self]
21
21
  end
22
22
  end
@@ -2792,7 +2792,7 @@ END
2792
2792
  # included by the association's conditions.
2793
2793
  def add_association_filter_conditions(ref, obj, expr)
2794
2794
  if expr != SQL::Constants::FALSE && ref.filter_by_associations_add_conditions?
2795
- Sequel.expr(ref.filter_by_associations_conditions_expression(obj))
2795
+ Sequel[ref.filter_by_associations_conditions_expression(obj)]
2796
2796
  else
2797
2797
  expr
2798
2798
  end
@@ -46,13 +46,6 @@ module Sequel
46
46
  @destroyed = true
47
47
  end
48
48
 
49
- # Mark current instance as destroyed if the transaction in which this
50
- # instance is created is rolled back.
51
- def before_create
52
- db.after_rollback{@destroyed = true}
53
- super
54
- end
55
-
56
49
  # Return ::ActiveModel::Name instance for the class.
57
50
  def model_name
58
51
  model.model_name
@@ -60,7 +53,16 @@ module Sequel
60
53
 
61
54
  # False if the object is new? or has been destroyed, true otherwise.
62
55
  def persisted?
63
- !new? && @destroyed != true
56
+ return false if new?
57
+ return false if defined?(@destroyed)
58
+
59
+ if defined?(@rollback_checker)
60
+ if @rollback_checker.call
61
+ return false
62
+ end
63
+ end
64
+
65
+ true
64
66
  end
65
67
 
66
68
  # An array of primary key values, or nil if the object is not persisted.
@@ -93,6 +95,15 @@ module Sequel
93
95
 
94
96
  private
95
97
 
98
+ # For new objects, add a rollback checker to check if the transaction
99
+ # in which this instance is created is rolled back.
100
+ def _save(opts)
101
+ if new? && db.in_transaction?(opts)
102
+ @rollback_checker = db.rollback_checker(opts)
103
+ end
104
+ super
105
+ end
106
+
96
107
  # Use ActiveModel compliant errors class.
97
108
  def errors_class
98
109
  Errors
@@ -103,7 +103,7 @@ module Sequel
103
103
  edges.each{|e| mds = mds.join(e[:table], Array(e[:right]).zip(Array(e[:left])))}
104
104
  ds.filter(r.qualified_right_primary_key=>r.send(:apply_filter_by_associations_limit_strategy, mds))
105
105
  when :pg_array_to_many
106
- ds.filter(Sequel.expr(r.primary_key=>sds.select{Sequel.pg_array_op(r.qualify(r[:model].table_name, r[:key])).unnest}))
106
+ ds.filter(Sequel[r.primary_key=>sds.select{Sequel.pg_array_op(r.qualify(r[:model].table_name, r[:key])).unnest}])
107
107
  when :many_to_pg_array
108
108
  ds.filter(Sequel.function(:coalesce, Sequel.pg_array_op(r[:key]).overlaps(sds.select{array_agg(r.qualify(r[:model].table_name, r.primary_key))}), false))
109
109
  else
@@ -119,8 +119,44 @@ module Sequel
119
119
  end
120
120
 
121
121
  module InstanceMethods
122
- Model::BEFORE_HOOKS.each{|h| class_eval("def #{h}; model.hook_blocks(:#{h}){|b| return false if instance_eval(&b) == false}; super; end", __FILE__, __LINE__)}
123
- Model::AFTER_HOOKS.each{|h| class_eval("def #{h}; super; model.hook_blocks(:#{h}){|b| instance_eval(&b)}; end", __FILE__, __LINE__)}
122
+ (Model::BEFORE_HOOKS - [:before_save, :before_destroy]).each{|h| class_eval("def #{h}; model.hook_blocks(:#{h}){|b| return false if instance_eval(&b) == false}; super; end", __FILE__, __LINE__)}
123
+ (Model::AFTER_HOOKS - [:after_save, :after_destroy, :after_commit, :after_rollback, :after_destroy_commit, :after_destroy_rollback]).each{|h| class_eval("def #{h}; super; model.hook_blocks(:#{h}){|b| instance_eval(&b)}; end", __FILE__, __LINE__)}
124
+
125
+ def after_destroy
126
+ super
127
+ model.hook_blocks(:after_destroy){|b| instance_eval(&b)}
128
+ if model.has_hooks?(:after_destroy_commit)
129
+ db.after_rollback{model.hook_blocks(:after_destroy_commit){|b| instance_eval(&b)}}
130
+ end
131
+ end
132
+
133
+ def after_save
134
+ super
135
+ model.hook_blocks(:after_save){|b| instance_eval(&b)}
136
+ if model.has_hooks?(:after_commit)
137
+ db.after_rollback{model.hook_blocks(:after_commit){|b| instance_eval(&b)}}
138
+ end
139
+ end
140
+
141
+ def before_destroy
142
+ model.hook_blocks(:before_destroy) do |b|
143
+ return false if instance_eval(&b) == false
144
+ end
145
+ super
146
+ if model.has_hooks?(:after_destroy_rollback)
147
+ db.after_rollback{model.hook_blocks(:after_destroy_rollback){|b| instance_eval(&b)}}
148
+ end
149
+ end
150
+
151
+ def before_save
152
+ model.hook_blocks(:before_save) do |b|
153
+ return false if instance_eval(&b) == false
154
+ end
155
+ super
156
+ if model.has_hooks?(:after_rollback)
157
+ db.after_rollback{model.hook_blocks(:after_rollback){|b| instance_eval(&b)}}
158
+ end
159
+ end
124
160
  end
125
161
  end
126
162
  end