💭
トップレベルのモデル名と、STIなネストした側のモデル名が一致すると class_name オプションが必須
rails 7.3.1, 7.2.1, 8.0.0 で起きることを確認している。
Bar::Foo.foo すると自身のインスタンスが返ってしまう
以下の関係のあるモデルがあるとする。
ActiveRecord::Schema[8.0].define(version: 2024_11_25_145426) do
create_table "bars", force: :cascade do |t|
t.integer "foo_id", null: false
t.string "name", null: false
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", null: false
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
Bar::Foo
インスタンスが belongs_to
の関連を使って .foo
にアクセスすると、トップレベルの Foo ではなく、Bar という名前空間内の Foo にアクセスしようとした結果、自身にアクセスしてしまう。
特にエラーにはならないので、値を確認しないと見落とす可能性がある。
irb(main):001:0: > 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: > 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>
解決策
belongs_to
に class_name
オプションをつけて ::Foo
を指定すると解決する。
先にも書いたが、特にエラーにはならず、値を確認しないと気が付かないため注意する。
class Bar < ApplicationRecord
- belongs_to :foo
+ belongs_to :foo, class_name: '::Foo'
end
rails console で関連を確認すると、トップレベルの Foo が返っていることが確認できる。
irb(main):001:0: > 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: > 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>
Discussion