Kombajn zbożowy
Kombajn zbożowy

Reputation: 10693

Rails ActiveRecord - get belongs_to association with lock

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

Answers (3)

rafbm
rafbm

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

GuiGS
GuiGS

Reputation: 2160

You can carry on a scope to the association using ActiveRecord::Relation#scoping:

Team.lock.scoping { character.team }

Upvotes: 2

sandre89
sandre89

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

Related Questions