Reputation: 10693
I would like to retrieve a belongs_to association and acquire a database lock on its object.
> character.team.lock!
ActiveRecord::Base -- Team Load -- { :sql => "SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 1 LIMIT 1" }
ActiveRecord::Base -- Team Load -- { :sql => "SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 1 LIMIT 1 FOR UPDATE" }
Above runs two queries, which technically makes sense - character.team
loads the team, then team.lock!
selects once more with FOR UPDATE
.
The question is - how can I make it issue only one query?
Upvotes: 1
Views: 1641
Reputation: 265
Thankfully there is a solution:
character.association(:team).scope.lock.to_a[0]
# SELECT "teams".* FROM "teams" WHERE "teams"."id" = '...' LIMIT 1 FOR UPDATE
The only difference to the SQL query is the added FOR UPDATE
.
May be abstracted in ApplicationRecord
for all belongs_to
associations:
class ApplicationRecord < ActiveRecord::Base
# ...
def self.belongs_to(name, *)
class_eval <<~RUBY
def locked_#{name}
association(:#{name}).scope.lock.to_a[0]
end
RUBY
super
end
end
Then in app code:
character.locked_team
Upvotes: 1
Reputation: 2160
You can carry on a scope to the association using ActiveRecord::Relation#scoping
:
Team.lock.scoping { character.team }
Upvotes: 2
Reputation: 5908
Apparently you can't, because the .lock method will always reload the instance (issuing a second SQL load). From the docs:
.lock: 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.
Upvotes: 1