class Ticket
enum :status, [:unassigned, :assigned, :resolved, :punt], prefix: true
belongs_to :assigned_to, classname: "User"
def self.get_me_a_ticket(user)
t = Ticket.unassigned.first
# race condition here
t.update!(status: :assigned, assigned_to: user)
t
end
end
If this is active enough, you get conflicts. So you have to bring back a bunch of records and mark them as assigned and hope you don’t get a double hit.
Does anyone do something like this and know how to do this without introducing a new method to ActiveRecord?
My proposal is to use Postgres and sqlite’s update and RETURNING
# UPDATE tickets
# SET status = 1,
# assigned_to = 55
# WHERE status = 0
# LIMIT 1
# RETURNING *
def self.get_me_a_ticket(user)
Ticket.unassigned
.limit(1)
.update_all_returning(status: :assigned, assigned_to: user)
end
I thought of a couple of design tradeoffs, and chose what I thought worked best:
- I did not want to introduce a new method, but
update_allcurrently returns aninteger, and changing that return type isn’t ideal. Alternative is a newupdate_all_returningmethod or another name. - Postgres and SqlLite3 support
RETURNINGwithINSERT,DELETEandUPDATE, and MySql does not, as reflected bysupports_insert_returning. MiriaDB supports all except forUPDATE RETURNING, so I introducedsupports_update_returningwith the expected default value. - MySql does not support
RETURNINGat all. Callingupdate_allthrows an exception rather than implementing a shim. I did the same. update_all_returningandupdate_allhave most of the same code. I could have modifiedupdate_all, refactoredupdate_allandupdate_all_returningto use a common method, or move it out to a query helper method. I chose to keep the code local.- Since the
upsertcase is implemented with different SQL on different databases, this is implemented in the connection layer. Forupdate_all_returningI opted to introduceAreland anupdate_returningmethod. - Will circle back to convert our current SQL munging to use
Arelin another PR since that seemed out of scope.
I’d appreciate any feedback or testimonials on how this will change your life.
EDIT: oops, Add update_all_returning for atomic update-and-fetch by kbrock · Pull Request #57073 · rails/rails · GitHub