Skip to content

Commit 0aa832e

Browse files
authored
Merge pull request rails#41082 from kamipo/fix_update_optimistic_locking_without_default
Restore the ability that update/destroy optimistic locking object without default
2 parents 15f6b64 + 4db95d8 commit 0aa832e

File tree

3 files changed

+57
-14
lines changed

3 files changed

+57
-14
lines changed

activemodel/lib/active_model/attribute.rb

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,13 @@ def encode_with(coder)
133133
coder["value"] = value if defined?(@value)
134134
end
135135

136-
protected
137-
def original_value_for_database
138-
if assigned?
139-
original_attribute.original_value_for_database
140-
else
141-
_original_value_for_database
142-
end
136+
def original_value_for_database
137+
if assigned?
138+
original_attribute.original_value_for_database
139+
else
140+
_original_value_for_database
143141
end
142+
end
144143

145144
private
146145
attr_reader :original_attribute
@@ -165,9 +164,10 @@ def type_cast(value)
165164
type.deserialize(value)
166165
end
167166

168-
def _original_value_for_database
169-
value_before_type_cast
170-
end
167+
private
168+
def _original_value_for_database
169+
value_before_type_cast
170+
end
171171
end
172172

173173
class FromUser < Attribute # :nodoc:

activerecord/lib/active_record/locking/optimistic.rb

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ def _update_row(attribute_names, attempted_action = "update")
8989

9090
begin
9191
locking_column = self.class.locking_column
92-
previous_lock_value = attribute_before_type_cast(locking_column)
92+
lock_attribute_was = @attributes[locking_column]
93+
lock_value_for_database = _lock_value_for_database(locking_column)
94+
9395
attribute_names = attribute_names.dup if attribute_names.frozen?
9496
attribute_names << locking_column
9597

@@ -98,7 +100,7 @@ def _update_row(attribute_names, attempted_action = "update")
98100
affected_rows = self.class._update_record(
99101
attributes_with_values(attribute_names),
100102
@primary_key => id_in_database,
101-
locking_column => previous_lock_value
103+
locking_column => lock_value_for_database
102104
)
103105

104106
if affected_rows != 1
@@ -109,7 +111,7 @@ def _update_row(attribute_names, attempted_action = "update")
109111

110112
# If something went wrong, revert the locking_column value.
111113
rescue Exception
112-
self[locking_column] = previous_lock_value.to_i
114+
@attributes[locking_column] = lock_attribute_was
113115
raise
114116
end
115117
end
@@ -121,7 +123,7 @@ def destroy_row
121123

122124
affected_rows = self.class._delete_record(
123125
@primary_key => id_in_database,
124-
locking_column => attribute_before_type_cast(locking_column)
126+
locking_column => _lock_value_for_database(locking_column)
125127
)
126128

127129
if affected_rows != 1
@@ -131,6 +133,14 @@ def destroy_row
131133
affected_rows
132134
end
133135

136+
def _lock_value_for_database(locking_column)
137+
if will_save_change_to_attribute?(locking_column)
138+
@attributes[locking_column].value_for_database
139+
else
140+
@attributes[locking_column].original_value_for_database
141+
end
142+
end
143+
134144
module ClassMethods
135145
DEFAULT_LOCKING_COLUMN = "lock_version"
136146

activerecord/test/cases/locking_test.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,39 @@ def test_lock_without_default_should_work_with_null_in_the_database
340340
assert_equal "new title2", t2.title
341341
end
342342

343+
def test_update_with_lock_version_without_default_should_work_on_dirty_value_before_type_cast
344+
ActiveRecord::Base.connection.execute("INSERT INTO lock_without_defaults(title) VALUES('title1')")
345+
t1 = LockWithoutDefault.last
346+
347+
assert_equal 0, t1.lock_version
348+
assert_nil t1.lock_version_before_type_cast
349+
350+
t1.lock_version = t1.lock_version
351+
352+
assert_equal 0, t1.lock_version
353+
assert_equal 0, t1.lock_version_before_type_cast
354+
355+
assert_nothing_raised { t1.update!(title: "new title1") }
356+
assert_equal 1, t1.lock_version
357+
assert_equal "new title1", t1.title
358+
end
359+
360+
def test_destroy_with_lock_version_without_default_should_work_on_dirty_value_before_type_cast
361+
ActiveRecord::Base.connection.execute("INSERT INTO lock_without_defaults(title) VALUES('title1')")
362+
t1 = LockWithoutDefault.last
363+
364+
assert_equal 0, t1.lock_version
365+
assert_nil t1.lock_version_before_type_cast
366+
367+
t1.lock_version = t1.lock_version
368+
369+
assert_equal 0, t1.lock_version
370+
assert_equal 0, t1.lock_version_before_type_cast
371+
372+
assert_nothing_raised { t1.destroy! }
373+
assert_predicate t1, :destroyed?
374+
end
375+
343376
def test_lock_without_default_queries_count
344377
t1 = LockWithoutDefault.create(title: "title1")
345378

0 commit comments

Comments
 (0)