I enabled optimistic locking on my User model to handle possible conflicts in various parts of my codebase. However, I'm experiencing an unexpected conflict and I don't know how to handle it because I don't know what's causing it.
I'm using the Devise gem for authentication and I'm using the before_logout method to reset a security token...
class SessionsController < Devise::SessionsController
after_filter :after_login, :only => :create
before_filter :before_logout, :only => :destroy
def after_login
# logic to set the security token
end
def before_logout
current_user.update(security_token: nil) # <--"Attempted to update a stale object: User"
end
end
I'm new to Rails (only a few weeks in), so I don't really know where else in my code to look or why this code is throwing the exception. Any suggestions or ideas are greatly appreciated.
Thanks in advance for your wisdom!
You need to set a default value (0) for the lock_version
column in the migration file
Optimistic locking means you want multiple threads to be able to edit the same user record.
What's happening in your app is that two (or more) edits are happening to the same user record, and your app is correctly detecting that, and raising the suitable error.
What you would typically do is handle the error:
For example, you could print a message such as "Sorry, someone else edited this, so your changes were not saved".
For example, you could code any way to reconcile the edits, such as detecting that one edit changed the person's name, wherease the other edit changed the person's phone number, and then you could code a way to merge these two into one save.
In general, you can also use user.reload
to fetch a fresh version of the user.
I suggest you first try turning off optimistic locking, while you're learning Rails. Optimistic locking is a great optimization for speed, yet it shouldn't be needed to get a typical Rails & Devise app running successfully.
You asked about what specifically Devise may be doing:
When a user record is updated, the typical Rails ActiveRecord setup updates a table field updated_at
with the current timestamp.
A typical Devise setup of modules such as trackable
updates the user record with the most recent sign-in and/or sign-out timestamps.
An easy way to watch what's happening is to look at the lock_version
field:
When Rails loads a record (e.g. a user) then Rails increments the lock version number.
When your app saves the user, Rails ActiveRecord compares the user object lock_version number with the database lock_version number.
If the numbers match, then the save succeeds; if the number mismatch, then Rails raises the stale object exception.
Example code:
def before_logout
logger.debug current_user.lock_version
current_user.reload
logger.debug current_user.lock_version
current_user.update(security_token: nil)
end
Devise and some of its various modules add fields to the user
table. This is quick and convenient for typical web apps, yet these added fields tend to disrupt caching, locking, persistence, serializing, and the like.
(As an aside, you may want to consider evaluating Devise vs. other security solutions. One in particular that I like is called Sorcery; it provides a toolkit of a handful of methods that you can assemble the way you want. In my personal experience, Sorcery is a great way to learn exactly what each step is doing in your own code, and you can control the caching and locking in the ways that you want.)
I've faced a similar issue. The error is due to the difference in the lock_version of the object you're trying to update. You can attempt calling reload on the object before performing the update. This would ensure the object is of the most recent lock_version.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With