When we declare a belongs_to
association, the declaring class automatically
gains
build_association
and
create_association
methods.
For example, given the declaration:
class Book < ApplicationRecord
belongs_to :author
end
Each instance of the Book model will have build_author
and create_author
methods.
Now let’s consider the following models.
# app/models/supplier.rb
class Supplier < ApplicationRecord
has_one :account
has_one :account_history , through: :account
end
# app/models/account.rb
class Account < ApplicationRecord
belongs_to :supplier
has_one :account_history
end
# app/models/account_history.rb
class AccountHistory < ApplicationRecord
belongs_to :account
end
To create a supplier account, we can call create_account
on the Supplier object as shown below:
Supplier . first . create_account
# Supplier Load (0.5ms) SELECT "suppliers".* FROM "suppliers" ORDER BY "suppliers"."id" ASC LIMIT $1 [["LIMIT", 1]]
# Account Load (0.5ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."supplier_id" = $1 LIMIT $2
[[ "supplier_id" , "6700d0ba-b8ed-4ff5-bb69-c57e7ea7f285" ], [ "LIMIT" , 1 ]]
The direct association of has_one: :account
works as expected.
But when we try to create AccountHistory, we get the below error:
Supplier . first . create_account_history
# Supplier Load (0.5ms) SELECT "suppliers".* FROM "suppliers" ORDER BY "suppliers"."id" ASC LIMIT $1 [["LIMIT", 1]]
NoMethodError : undefined method `create_account_history' for #<Supplier:0x00007f949d6016c8>
As we can see, the create_account_history
method is missing when we created the has_one :through
relationship.
Before:
Before Rails 7,
to fix the above issue,
we can add a method create_account_history
to the Supplier
model.
class Supplier < ApplicationRecord
has_one :account
has_one :account_history , through: :account
def create_account_history
account = self . account
account . create_account_history
end
end
This change works as expected, but we would end up writing similar methods
for all our has_one :through
associations.
After:
To resolve the above issue,
Rails 7
allows
build_association
and create_association
on has_one :through associations.
AccountHistory can be created via Supplier object as shown below.
Supplier . first . create_account_history
# AccountHistory Create (1.3ms) INSERT INTO "account_histories" ("account_id", "user_id") VALUES ($1, $2) RETURNING "id" [["account_id", "5d671a08-1513-442a-8cfa-ac00e128d423"], ["user_id", "24571aff-3456-442a-8cfa-aerte128d423"], ["organization_user_id""6700d0ba-b8ed-4ff5-bb69-c57e7ea7f285"], ["created_at", "2021-04-09 06:26:59.002535"], ["updated_at", "2021-04-09 06:26:59.002535"]]
Similarly, build_account_history
will work and, it will not throw a NoMethodError
exception.