Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 184 lines (155 sloc) 6.523 kb
fbf9281 @dhh Added automated optimistic locking if the field lock_version is prese…
dhh authored
1 module ActiveRecord
2 module Locking
194b4aa Provide brief introduction to what optimistic locking is. Closes #809…
Marcel Molina authored
3 # == What is Optimistic Locking
4 #
c4a3156 @jeremy Optimistic locking: revert the lock version when an update fails. Clo…
jeremy authored
5 # Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of
84e541e @smartinez87 Better doc styling in ActiveRecord::Locking
smartinez87 authored
6 # conflicts with the data. It does this by checking whether another process has made changes to a record since
7 # it was opened, an <tt>ActiveRecord::StaleObjectError</tt> exception is thrown if that has occurred
8 # and the update is ignored.
194b4aa Provide brief introduction to what optimistic locking is. Closes #809…
Marcel Molina authored
9 #
84e541e @smartinez87 Better doc styling in ActiveRecord::Locking
smartinez87 authored
10 # Check out <tt>ActiveRecord::Locking::Pessimistic</tt> for an alternative.
194b4aa Provide brief introduction to what optimistic locking is. Closes #809…
Marcel Molina authored
11 #
12 # == Usage
13 #
84e541e @smartinez87 Better doc styling in ActiveRecord::Locking
smartinez87 authored
14 # Active Records support optimistic locking if the field +lock_version+ is present. Each update to the
15 # record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice
16 # will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example:
e425493 @jeremy r4663@asus: jeremy | 2006-06-19 17:23:57 -0700
jeremy authored
17 #
18 # p1 = Person.find(1)
19 # p2 = Person.find(1)
c4a3156 @jeremy Optimistic locking: revert the lock version when an update fails. Clo…
jeremy authored
20 #
e425493 @jeremy r4663@asus: jeremy | 2006-06-19 17:23:57 -0700
jeremy authored
21 # p1.first_name = "Michael"
22 # p1.save
c4a3156 @jeremy Optimistic locking: revert the lock version when an update fails. Clo…
jeremy authored
23 #
e425493 @jeremy r4663@asus: jeremy | 2006-06-19 17:23:57 -0700
jeremy authored
24 # p2.first_name = "should fail"
25 # p2.save # Raises a ActiveRecord::StaleObjectError
26 #
0a51e43 @pacoguzman remove some blanks
pacoguzman authored
27 # Optimistic locking will also check for stale data when objects are destroyed. Example:
7e06494 @cghawthorne Destroy respects optimistic locking.
cghawthorne authored
28 #
29 # p1 = Person.find(1)
30 # p2 = Person.find(1)
31 #
32 # p1.first_name = "Michael"
33 # p1.save
34 #
35 # p2.destroy # Raises a ActiveRecord::StaleObjectError
36 #
e425493 @jeremy r4663@asus: jeremy | 2006-06-19 17:23:57 -0700
jeremy authored
37 # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
38 # or otherwise apply the business logic needed to resolve the conflict.
39 #
80bcfb0 @tilsammans Added a note that optimistic locking also needs a hidden field to fun…
tilsammans authored
40 # This locking mechanism will function inside a single Ruby process. To make it work across all
41 # web requests, the recommended approach is to add +lock_version+ as a hidden field to your form.
42 #
84e541e @smartinez87 Better doc styling in ActiveRecord::Locking
smartinez87 authored
43 # You must ensure that your database schema defaults the +lock_version+ column to 0.
e425493 @jeremy r4663@asus: jeremy | 2006-06-19 17:23:57 -0700
jeremy authored
44 #
45 # This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
84e541e @smartinez87 Better doc styling in ActiveRecord::Locking
smartinez87 authored
46 # To override the name of the +lock_version+ column, invoke the <tt>set_locking_column</tt> method.
e425493 @jeremy r4663@asus: jeremy | 2006-06-19 17:23:57 -0700
jeremy authored
47 # This method uses the same syntax as <tt>set_table_name</tt>
48 module Optimistic
4e50a35 @josh Break up DependencyModule's dual function of providing a "depend_on" …
josh authored
49 extend ActiveSupport::Concern
e425493 @jeremy r4663@asus: jeremy | 2006-06-19 17:23:57 -0700
jeremy authored
50
a2875be @brynary Use DependencyModule for included hooks in ActiveRecord
brynary authored
51 included do
52 cattr_accessor :lock_optimistically, :instance_writer => false
53 self.lock_optimistically = true
fbf9281 @dhh Added automated optimistic locking if the field lock_version is prese…
dhh authored
54 end
55
e425493 @jeremy r4663@asus: jeremy | 2006-06-19 17:23:57 -0700
jeremy authored
56 def locking_enabled? #:nodoc:
8375237 @jamis Made increment_counter/decrement_counter play nicely with optimistic …
jamis authored
57 self.class.locking_enabled?
e425493 @jeremy r4663@asus: jeremy | 2006-06-19 17:23:57 -0700
jeremy authored
58 end
59
07d8f46 @jeremy Consistent public/protected/private visibility for chained methods. C…
jeremy authored
60 private
9d8fdfe @joshk removed some duplication from LH issue 5505 regarding AR touch and op…
joshk authored
61 def increment_lock
62 lock_col = self.class.locking_column
63 previous_lock_value = send(lock_col).to_i
64 send(lock_col + '=', previous_lock_value + 1)
65 end
66
d916c62 @wycats eliminate alias_method_chain from ActiveRecord
wycats authored
67 def update(attribute_names = @attributes.keys) #:nodoc:
68 return super unless locking_enabled?
3610997 @danielmorrison Partial updates don't update lock_version if nothing changed. [#426 …
danielmorrison authored
69 return 0 if attribute_names.empty?
e425493 @jeremy r4663@asus: jeremy | 2006-06-19 17:23:57 -0700
jeremy authored
70
07d8f46 @jeremy Consistent public/protected/private visibility for chained methods. C…
jeremy authored
71 lock_col = self.class.locking_column
9d8fdfe @joshk removed some duplication from LH issue 5505 regarding AR touch and op…
joshk authored
72 previous_lock_value = send(lock_col).to_i
73 increment_lock
018e12d @jeremy r1605@asus: jeremy | 2005-07-02 14:50:23 -0700
jeremy authored
74
6b9448c @jeremy Partial updates include only unsaved attributes. Off by default; set …
jeremy authored
75 attribute_names += [lock_col]
76 attribute_names.uniq!
77
c4a3156 @jeremy Optimistic locking: revert the lock version when an update fails. Clo…
jeremy authored
78 begin
cd90dcb @lifo Rename Model.active_relation to Model.unscoped
lifo authored
79 relation = self.class.unscoped
fefb4c7 @miloops Cache arel_table when possible, use class method arel_table instead
miloops authored
80
8c9b5e4 @tenderlove removing more calls to deprecated methods
tenderlove authored
81 stmt = relation.where(
4fbd8ad @cmeiklejohn Don't quote ID's as Arel will quote them -- follow same conventions a…
cmeiklejohn authored
82 relation.table[self.class.primary_key].eq(id).and(
9d8fdfe @joshk removed some duplication from LH issue 5505 regarding AR touch and op…
joshk authored
83 relation.table[lock_col].eq(quote_value(previous_lock_value))
0e113a0 @miloops Refactored locking update
miloops authored
84 )
8c9b5e4 @tenderlove removing more calls to deprecated methods
tenderlove authored
85 ).arel.compile_update(arel_attributes_values(false, false, attribute_names))
86
7db90aa @jonleighton Make it the responsibility of the connection to hold onto an ARel vis…
jonleighton authored
87 affected_rows = connection.update stmt
0e113a0 @miloops Refactored locking update
miloops authored
88
89 unless affected_rows == 1
c6f0461 @fabrik42 Consider attempted action in exception message of ActiveRecord::Stale…
fabrik42 authored
90 raise ActiveRecord::StaleObjectError.new(self, "update")
c4a3156 @jeremy Optimistic locking: revert the lock version when an update fails. Clo…
jeremy authored
91 end
491554f @dhh Make #save return true on success, even if locking is enabled (closes…
dhh authored
92
c4a3156 @jeremy Optimistic locking: revert the lock version when an update fails. Clo…
jeremy authored
93 affected_rows
94
95 # If something went wrong, revert the version.
96 rescue Exception
9d8fdfe @joshk removed some duplication from LH issue 5505 regarding AR touch and op…
joshk authored
97 send(lock_col + '=', previous_lock_value)
c4a3156 @jeremy Optimistic locking: revert the lock version when an update fails. Clo…
jeremy authored
98 raise
99 end
07d8f46 @jeremy Consistent public/protected/private visibility for chained methods. C…
jeremy authored
100 end
491554f @dhh Make #save return true on success, even if locking is enabled (closes…
dhh authored
101
d916c62 @wycats eliminate alias_method_chain from ActiveRecord
wycats authored
102 def destroy #:nodoc:
103 return super unless locking_enabled?
7e06494 @cghawthorne Destroy respects optimistic locking.
cghawthorne authored
104
1f06652 @dchelimsky use persisted? instead of new_record? wherever possible
dchelimsky authored
105 if persisted?
475d1d1 @lifo Use arel instead of sql strings
lifo authored
106 table = self.class.arel_table
c4d31d0 @miloops Reuse lock_col variable instead calling locking_column class method.
miloops authored
107 lock_col = self.class.locking_column
108 predicate = table[self.class.primary_key].eq(id).
109 and(table[lock_col].eq(send(lock_col).to_i))
475d1d1 @lifo Use arel instead of sql strings
lifo authored
110
111 affected_rows = self.class.unscoped.where(predicate).delete_all
7e06494 @cghawthorne Destroy respects optimistic locking.
cghawthorne authored
112
113 unless affected_rows == 1
c6f0461 @fabrik42 Consider attempted action in exception message of ActiveRecord::Stale…
fabrik42 authored
114 raise ActiveRecord::StaleObjectError.new(self, "destroy")
7e06494 @cghawthorne Destroy respects optimistic locking.
cghawthorne authored
115 end
116 end
117
d10ecfe @jlewallen Set destroyed=true in opt locking's destroy [#5058 state:resolved]
jlewallen authored
118 @destroyed = true
7e06494 @cghawthorne Destroy respects optimistic locking.
cghawthorne authored
119 freeze
120 end
121
e425493 @jeremy r4663@asus: jeremy | 2006-06-19 17:23:57 -0700
jeremy authored
122 module ClassMethods
123 DEFAULT_LOCKING_COLUMN = 'lock_version'
fbf9281 @dhh Added automated optimistic locking if the field lock_version is prese…
dhh authored
124
84e541e @smartinez87 Better doc styling in ActiveRecord::Locking
smartinez87 authored
125 # Returns true if the +lock_optimistically+ flag is set to true
126 # (which it is, by default) and the table includes the
127 # +locking_column+ column (defaults to +lock_version+).
8375237 @jamis Made increment_counter/decrement_counter play nicely with optimistic …
jamis authored
128 def locking_enabled?
129 lock_optimistically && columns_hash[locking_column]
130 end
131
f3c84dc @jonleighton Deprecate set_locking_column in favour of self.locking_column=
jonleighton authored
132 def locking_column=(value)
133 @original_locking_column = @locking_column if defined?(@locking_column)
134 @locking_column = value.to_s
135 end
136
98dc582 @lifo Merge docrails.
lifo authored
137 # Set the column to use for optimistic locking. Defaults to +lock_version+.
e425493 @jeremy r4663@asus: jeremy | 2006-06-19 17:23:57 -0700
jeremy authored
138 def set_locking_column(value = nil, &block)
f3c84dc @jonleighton Deprecate set_locking_column in favour of self.locking_column=
jonleighton authored
139 deprecated_property_setter :locking_column, value, block
e425493 @jeremy r4663@asus: jeremy | 2006-06-19 17:23:57 -0700
jeremy authored
140 end
fbf9281 @dhh Added automated optimistic locking if the field lock_version is prese…
dhh authored
141
98dc582 @lifo Merge docrails.
lifo authored
142 # The version column used for optimistic locking. Defaults to +lock_version+.
e425493 @jeremy r4663@asus: jeremy | 2006-06-19 17:23:57 -0700
jeremy authored
143 def locking_column
f3c84dc @jonleighton Deprecate set_locking_column in favour of self.locking_column=
jonleighton authored
144 reset_locking_column unless defined?(@locking_column)
145 @locking_column
146 end
147
148 def original_locking_column #:nodoc:
149 deprecated_original_property_getter :locking_column
e425493 @jeremy r4663@asus: jeremy | 2006-06-19 17:23:57 -0700
jeremy authored
150 end
151
722e0b6 @jeremy r4664@asus: jeremy | 2006-06-19 18:55:36 -0700
jeremy authored
152 # Quote the column name used for optimistic locking.
153 def quoted_locking_column
154 connection.quote_column_name(locking_column)
155 end
156
98dc582 @lifo Merge docrails.
lifo authored
157 # Reset the column used for optimistic locking back to the +lock_version+ default.
e425493 @jeremy r4663@asus: jeremy | 2006-06-19 17:23:57 -0700
jeremy authored
158 def reset_locking_column
f3c84dc @jonleighton Deprecate set_locking_column in favour of self.locking_column=
jonleighton authored
159 self.locking_column = DEFAULT_LOCKING_COLUMN
e425493 @jeremy r4663@asus: jeremy | 2006-06-19 17:23:57 -0700
jeremy authored
160 end
8375237 @jamis Made increment_counter/decrement_counter play nicely with optimistic …
jamis authored
161
98dc582 @lifo Merge docrails.
lifo authored
162 # Make sure the lock version column gets updated when counters are
8375237 @jamis Made increment_counter/decrement_counter play nicely with optimistic …
jamis authored
163 # updated.
d916c62 @wycats eliminate alias_method_chain from ActiveRecord
wycats authored
164 def update_counters(id, counters)
8375237 @jamis Made increment_counter/decrement_counter play nicely with optimistic …
jamis authored
165 counters = counters.merge(locking_column => 1) if locking_enabled?
d916c62 @wycats eliminate alias_method_chain from ActiveRecord
wycats authored
166 super
8375237 @jamis Made increment_counter/decrement_counter play nicely with optimistic …
jamis authored
167 end
7edade3 @jonleighton Make read_attribute code path accessible at the class level
jonleighton authored
168
169 # If the locking column has no default value set,
170 # start the lock version at zero. Note we can't use
171 # <tt>locking_enabled?</tt> at this point as
172 # <tt>@attributes</tt> may not have been initialized yet.
173 def initialize_attributes(attributes) #:nodoc:
174 if attributes.key?(locking_column) && lock_optimistically
175 attributes[locking_column] ||= 0
176 end
177
178 attributes
179 end
a471e6b @NZKoz allow the 'lock_version' column to be configured with set_locking_col…
NZKoz authored
180 end
181 end
fbf9281 @dhh Added automated optimistic locking if the field lock_version is prese…
dhh authored
182 end
a250a98 @jeremy r1602@asus: jeremy | 2005-07-02 14:34:00 -0700
jeremy authored
183 end
Something went wrong with that request. Please try again.