Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails 7: has_one :through関連付けでコンストラクタが使えるようになる(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

Rails 7: has_one :through関連付けでコンストラクタが使えるようになる(翻訳)

RailsのActive Recordモデルでbelongs_to関連付けを宣言すると、宣言したクラスで自動的にbuild_associationメソッドとcreate_associationのメソッドが使えるようになります。

たとえば以下の宣言があるとします。

class Book < ApplicationRecord
  belongs_to :author
end

このとき、Bookモデルの各インスタンスでbuild_authorメソッドとcreate_authorメソッドが使えるようになります。

今度は以下の3つのモデルで考えてみましょう。

# 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

サプライヤーのアカウントを作成する場合は、以下のようにSupplierオブジェクトでcreate_accountを呼べます。

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]]

has_one: :accountという直接の関連付けは期待どおりに動きますが、AccountHistoryを作成しようとすると以下のようにエラーになります。

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>

このように、has_one :throughリレーションシップを作成してもcreate_account_historyメソッドは使えるようになりません。

変更前

Ruby 7より前は、Supplierモデルにcreate_account_historyメソッドを自分で追加することで上の問題を解決できます。

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

これは期待どおり動作しますが、他のhas_one :through関連付けでも同様のメソッドを自分で追加することになります。

変更後

Rails 7ではこの問題を解決するため、has_one :through関連付けでbuild_<関連付け名>メソッドとcreate_<関連付け名>メソッドが使えるようになりました(#40007)。

以下のようにcreate_account_historyを書けば、SupplierオブジェクトでAccountHistoryを作成できます。

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"]]

build_account_historyも同様に利用でき、NoMethodError例外が発生しなくなります。

関連記事

Rails 7のActive Recordにinvert_whereメソッドが追加される(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。