blob: 72bccd490666f0a34cd482e2da203f24ee7b0fa1 (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
# frozen_string_literal: true
module ActiveRecord
module Locking
# Locking::Pessimistic provides support for row-level locking using
# SELECT ... FOR UPDATE and other lock types.
#
# Chain <tt>ActiveRecord::Base#find</tt> to <tt>ActiveRecord::QueryMethods#lock</tt> to obtain an exclusive
# lock on the selected rows:
# # select * from accounts where id=1 for update
# Account.lock.find(1)
#
# Call <tt>lock('some locking clause')</tt> to use a database-specific locking clause
# of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
#
# Account.transaction do
# # select * from accounts where name = 'shugo' limit 1 for update
# shugo = Account.where("name = 'shugo'").lock(true).first
# yuko = Account.where("name = 'yuko'").lock(true).first
# shugo.balance -= 100
# shugo.save!
# yuko.balance += 100
# yuko.save!
# end
#
# You can also use <tt>ActiveRecord::Base#lock!</tt> method to lock one record by id.
# This may be better if you don't need to lock every row. Example:
#
# Account.transaction do
# # select * from accounts where ...
# accounts = Account.where(...)
# account1 = accounts.detect { |account| ... }
# account2 = accounts.detect { |account| ... }
# # select * from accounts where id=? for update
# account1.lock!
# account2.lock!
# account1.balance -= 100
# account1.save!
# account2.balance += 100
# account2.save!
# end
#
# You can start a transaction and acquire the lock in one go by calling
# <tt>with_lock</tt> with a block. The block is called from within
# a transaction, the object is already locked. Example:
#
# account = Account.first
# account.with_lock do
# # This block is called within a transaction,
# # account is already locked.
# account.balance -= 100
# account.save!
# end
#
# Database-specific information on row locking:
# MySQL: https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
# PostgreSQL: https://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
module Pessimistic
# Obtain a row lock on this record. Reloads the record to obtain the requested
# lock. Pass an SQL locking clause to append the end of the SELECT statement
# or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
# the locked record.
def lock!(lock = true)
if persisted?
if changed?
ActiveSupport::Deprecation.warn(<<-MSG.squish)
Locking a record with unpersisted changes is deprecated and will raise an
exception in Rails 5.2. Use `save` to persist the changes, or `reload` to
discard them explicitly.
MSG
end
reload(lock: lock)
end
self
end
# Wraps the passed block in a transaction, locking the object
# before yielding. You can pass the SQL locking clause
# as argument (see <tt>lock!</tt>).
def with_lock(lock = true)
transaction do
lock!(lock)
yield
end
end
end
end
end
|