ネストした内側のクラスと同名クラスの関連がある場合は class_name が必要
Ruby on Rails(rails: 7.3.1
, 7.2.1
で確認した)を使って開発をしており、以下のような ActiveRecord のクラスがあるとする。
# db/schema.rb
ActiveRecord::Schema[7.1].define(version: 2024_10_27_143736) do
create_table "bars", force: :cascade do |t|
t.integer "foo_id", null: false
t.string "name"
t.string "type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["foo_id"], name: "index_bars_on_foo_id"
end
create_table "foos", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_foreign_key "bars", "foos"
end
# app/models/foo.rb
class Foo < ApplicationRecord
has_many :bars, dependent: :destroy
end
class Bar < ApplicationRecord
belongs_to :foo
end
# app/models/bar/foo.rb
class Bar
class Foo < ::Bar
end
end
この場合上記のように、Foo has_many Bars
である場合、Foo 側には has_many :bars
、Bar 側には belongs_to :foo
と記述する。
ただし、STI を使っていて、かつ、Bar の子クラスの内側のクラス名(Bar::Foo
の ‘Foo’)と同名のクラスとの関連がある場合(今回は ::Foo
)、class_name
オプションが必須になる。
まずは class_name オプションをつけていない場合、Bar::Foo インスタンスから Foo を参照しようとすると、自身のインスタンスを返えしてしまう。
❯ rails c
irb(main):001:0: development > Bar::Foo.create name: 'bar:foo', foo: ::Foo.first
Foo Load (0.0ms) SELECT "foos".* FROM "foos" ORDER BY "foos"."id" ASC LIMIT ? [["LIMIT", 1]]
TRANSACTION (0.0ms) begin transaction
Bar::Foo Create (0.3ms) INSERT INTO "bars" ("foo_id", "name", "type", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) RETURNING "id" [["foo_id", 1], ["name", "bar:foo"], ["type", "Bar::Foo"], ["created_at", "2024-10-27 14:42:36.572358"], ["updated_at", "2024-10-27 14:42:36.572358"]]
TRANSACTION (0.1ms) commit transaction
:
#<Bar::Foo:0x0000000105965f90
id: 1,
foo_id: 1,
name: "bar:foo",
type: "Bar::Foo",
created_at: Sun, 27 Oct 2024 14:42:36.572358000 UTC +00:00,
updated_at: Sun, 27 Oct 2024 14:42:36.572358000 UTC +00:00>
irb(main):002:0: development > bar_foo = Bar::Foo.first
Bar::Foo Load (0.3ms) SELECT "bars".* FROM "bars" WHERE "bars"."type" = ? ORDER BY "bars"."id" ASC LIMIT ? [["type", "Bar::Foo"], ["LIMIT", 1]]
:
#<Bar::Foo:0x0000000105a4d5c0
...
irb(main):003:0: development > bar_foo.foo
Bar::Foo Load (0.2ms) SELECT "bars".* FROM "bars" WHERE "bars"."type" = ? AND "bars"."id" = ? LIMIT ? [["type", "Bar::Foo"], ["id", 1], ["LIMIT", 1]]
:
#<Bar::Foo:0x0000000105b2d850
id: 1,
foo_id: 1,
name: "bar:foo",
type: "Bar::Foo",
created_at: Sun, 27 Oct 2024 14:42:36.572358000 UTC +00:00,
updated_at: Sun, 27 Oct 2024 14:42:36.572358000 UTC +00:00>
Bar
の関連を以下のように書き換える。すると、Bar::Foo
インスタンスから foo
を参照するとちゃんと ::Foo
インスタンスが返るようになる。
diff --git a/app/models/bar.rb b/app/models/bar.rb
index 80704b2..8769eea 100644
--- a/app/models/bar.rb
+++ b/app/models/bar.rb
@@ -1,3 +1,3 @@
class Bar < ApplicationRecord
- belongs_to :foo
+ belongs_to :foo, class_name: '::Foo'
end
変更前は bar_foo.foo
が Bar::Foo
インスタンスを返していたが、期待どおりの ::Foo
インスタンスを返すようになっている。
❯ rails c
irb(main):001:0: development > bar_foo = Bar::Foo.first
Bar::Foo Load (0.1ms) SELECT "bars".* FROM "bars" WHERE "bars"."type" = ? ORDER BY "bars"."id" ASC LIMIT ? [["type", "Bar::Foo"], ["LIMIT", 1]]
:
#<Bar::Foo:0x00000001075f4648
...
irb(main):002:0: development > bar_foo.foo
Foo Load (0.1ms) SELECT "foos".* FROM "foos" WHERE "foos"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
: #<Foo:0x0000000105bfffd0 id: 1, name: "foo", created_at: Sun, 27 Oct 2024 14:41:15.574606000 UTC +00:00, updated_at: Sun, 27 Oct 2024 14:41:15.574606000 UTC +00:00>