Whereas ActiveRecord works fairly nice throughout most eventualities, it struggles with multi-database architectures.
Fortuitously, the previous few releases have had a number of work completed on this side.
One main disadvantage is the way in which transactions work in a multi-database structure.
A transaction is principally a set of database operations which can be carried out as a single unit.
Right here’s an instance,
irb(primary):001:1* ActiveRecord::Base.transaction do
irb(primary):002:1* Firm.first.contact
irb(primary):003:1* Firm.final.contact
irb(primary):004:0> finish
TRANSACTION (0.1ms) start transaction
Firm Load (0.2ms) SELECT "firms".* FROM "firms" ORDER BY "firms"."id" ASC LIMIT ? [["LIMIT", 1]]
Firm Replace (1.2ms) UPDATE "firms" SET "updated_at" = ? WHERE "firms"."id" = ? [["updated_at", "2022-08-21 10:48:22.853590"], ["id", 1]]
Firm Load (0.1ms) SELECT "firms".* FROM "firms" ORDER BY "firms"."id" DESC LIMIT ? [["LIMIT", 1]]
Firm Replace (0.1ms) UPDATE "firms" SET "updated_at" = ? WHERE "firms"."id" = ? [["updated_at", "2022-08-21 10:48:22.860248"], ["id", 2]]
TRANSACTION (1.1ms) commit transaction
...
Although there are two database operations (Firm.first.contact
and Firm.final.contact
),
they’re carried out inside a single transaction.
Nonetheless, while you use a transaction throughout a number of databases,
it’s primarily left up-to the developer to make sure that the transactions are distributed throughout the databases in a approach that’s in keeping with the meant use case.
It’s because transactions work on a single database connection.
Earlier than
Let’s take the instance of a multi-database structure the place there’s a Firm mannequin on the first database
and a Freelancer mannequin on the secondary database.
Each these fashions are linked through the has_many :via
affiliation
through the Contract
mannequin on the first database.
irb(primary):001:0> firm = Firm.create! identify: 'Saeloun'
...
irb(primary):002:0> freelancer1 = Freelancer.create! identify: 'Joe'
...
irb(primary):003:0> freelancer2 = Freelancer.create! identify: 'Schmoe'
...
irb(primary):004:0> firm.freelancers = [freelancer1, freelancer2]
TRANSACTION (0.1ms) start transaction
Contract Create (0.8ms) INSERT INTO "contract" ("company_id", "freelancer_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["company_id", 1], ["freelancer_id", 1], ["created_at", "2022-08-21 07:23:41.521082"], ["updated_at", "2022-08-21 07:23:41.521082"]]
TRANSACTION (1.2ms) commit transaction
TRANSACTION (0.0ms) start transaction
Contract Create (0.4ms) INSERT INTO "contract" ("company_id", "freelancer_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["company_id", 2], ["freelancer_id", 1], ["created_at", "2022-08-21 07:23:41.525693"], ["updated_at", "2022-08-21 07:23:41.525693"]]
TRANSACTION (0.6ms) commit transaction
...
As you’ll be able to see, two transactions happen to insert two data into the contract
desk.
It’s because the firm.freelancers = [freelancer1, freelancer2]
operation is carried out on the first database connection
and the Contract.create!
operation is carried out on the secondary database connection.
It’s as much as the developer to wrap this round a double transaction block to make sure that the 2 operations are carried out
inside a single transaction.
irb(primary):001:1* Firm.transaction do
irb(primary):002:2* Freelancer.transaction do
irb(primary):003:2* firm.freelancers = [freelancer1, freelancer2]
irb(primary):004:1* finish
irb(primary):005:0> finish
TRANSACTION (0.1ms) start transaction
Contract Create (0.7ms) INSERT INTO "contract" ("company_id", "freelancer_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["company_id", 1], ["freelancer_id", 1], ["created_at", "2022-08-21 07:32:41.351072"], ["updated_at", "2022-08-21 07:32:41.351072"]]
Contract Create (0.1ms) INSERT INTO "contract" ("company_id", "freelancer_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["company_id", 2], ["freelancer_id", 1], ["created_at", "2022-08-21 07:32:41.351083"], ["updated_at", "2022-08-21 07:32:41.351083"]]
TRANSACTION (1.3ms) commit transaction
...
After
Due to this PR that updates
how through_reflection
distributes transactions throughout database connections,
we are able to now carry out the identical operation with out the necessity for a double transaction block.
irb(primary):001:0> firm.freelancers = [freelancer1, freelancer2]
TRANSACTION (0.1ms) start transaction
Contract Create (0.7ms) INSERT INTO "contract" ("company_id", "freelancer_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["company_id", 1], ["freelancer_id", 1], ["created_at", "2022-08-21 07:32:41.351072"], ["updated_at", "2022-08-21 07:32:41.351072"]]
Contract Create (0.1ms) INSERT INTO "contract" ("company_id", "freelancer_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["company_id", 2], ["freelancer_id", 1], ["created_at", "2022-08-21 07:32:41.351083"], ["updated_at", "2022-08-21 07:32:41.351083"]]
TRANSACTION (1.3ms) commit transaction
...
This was achieved through a quite simple replace to the ThroughAssociation module.
The transaction
methodology opens up the second block with the connection
of the through_reflection
as a substitute of the source_reflection
.
module ActiveRecord
module Associations
module ThroughAssociation
...
non-public
def transaction(&block)
through_reflection.klass.transaction(&block)
finish
...
finish
finish
finish