Rails Namespace Modelを利用する
TL; DR
rails7の場合に(8だと解消するPRが含まれていたchange logがあったきがするが探せてないw)、nested classの参照解決をして、利用しやすいように適宜renameする。
Fix inference of association model on nested models with the same demodularized name.
0. 環境
name | version |
---|---|
rails | 7.2.1.1 |
ruby | 3.3.5 |
mysql | 8.4 |
1. 想定するModelとdatabase schema
1-1. Model
1-2. Model Files
flat directory modelだった際のファイル構成は以下を想定しています。
(throughについては後続で修正します。)
class Organization < ApplicationRecord
has_many :organization_users
end
class Organization::User
belongs_to :organization
belongs_to :user
end
class User < ApplicationRecord
has_many :organization_users
end
2. 修正
2-0. 現状のModel構成を動かす
Oragnization Modelから参照した場合のエラー
% rails c
api(dev)> Organization.first.organization_users
Organization Load (0.9ms) SELECT `organizations`.* FROM `organizations` ORDER BY `organizations`.`id` ASC LIMIT 1
(api):28:in `<main>': Missing model class OrganizationUser for the Organization#organization_users association. You can specify a different model class with the :class_name option. (NameError)
raise NameError.new(message, name)
^^^^^
/usr/local/bundle/gems/activerecord-7.2.1.1/lib/active_record/inheritance.rb:266:in `compute_type': uninitialized constant Organization::OrganizationUser (NameError)
raise NameError.new("uninitialized constant #{candidates.first}", candidates.first)
User Modelから参照した場合のエラー
% rails c
api(dev)> User.first.organization_users
User Load (1.4ms) SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
(api):61:in `<main>': Missing model class OrganizationUser for the User#organization_users association. You can specify a different model class with the :class_name option. (NameError)
raise NameError.new(message, name)
^^^^^
/usr/local/bundle/gems/activerecord-7.2.1.1/lib/active_record/inheritance.rb:266:in `compute_type': uninitialized constant User::OrganizationUser (NameError)
raise NameError.new("uninitialized constant #{candidates.first}", candidates.first)
^^^^^
2-1. nested classの参照を解決して、Organization ModelからUser Modelにアクセスする可能にする
2-1-1. 参照の解決
class Organization
からclass Organization::User
を参照する際には、nestedになっているため、has_many :organization_users
ではなく、has_many :users
を利用して参照をする。
class Organization < ApplicationRecord
has_many :users
end
% rails c
api(dev)> Organization.first.users
Organization Load (1.3ms) SELECT `organizations`.* FROM `organizations` ORDER BY `organizations`.`id` ASC LIMIT 1
Organization::User Load (0.3ms) SELECT `organization_users`.* FROM `organization_users` WHERE `organization_users`.`organization_id` = 1 /* loading for pp */ LIMIT 11
=> []
2-1-2. ModelのRelationを利用して、Userにアクセスする
現状のhas_many :users
で、organization_users
にアクセスする形式の場合は、認識齟齬が発生しやすいので、usersの場合にusers tableを取得するように変更を実施します。
class Organization < ApplicationRecord
has_many :organization_users, class_name: "Organization::User"
has_many :users, through: :organization_users
end
[補足]models/oragnization.rbの場合だとuserの循環参照が起きる
Organization.first.users
Organization Load (1.2ms) SELECT `organizations`.* FROM `organizations` ORDER BY `organizations`.`id` ASC LIMIT 1
Organization::User Load (2.3ms) SELECT `organization_users`.* FROM `organization_users` INNER JOIN `organization_users` `organization_users_users` ON `organization_users`.`id` = `organization_users_users`.`user_id` WHERE `organization_users_users`.`organization_id` = 1 /* loading for pp */ LIMIT 11
=>
[#<Organization::User:0x0000ffff68ede618
id: 1,
organization_id: 1,
user_id: 1,
created_at:
"2024-11-12 05:06:34.040329000 +0000",
updated_at:
"2024-11-12 05:06:34.040329000 +0000">]
belongs_to: :user
の参照先は、Organization -> Organization::User
が参照先になってしまうため、User Model
にアクセスさせる。
::
を入れて、参照先をUser(models/user.rb)
に参照先を変更する。
class Organization::User < ApplicationRecord
belongs_to :organization
belongs_to :user, class_name: "::User"
end
% rails c
api(dev)> Organization.first.users
Organization Load (2.9ms) SELECT `organizations`.* FROM `organizations` ORDER BY `organizations`.`id` ASC LIMIT 1
User Load (0.5ms) SELECT `users`.* FROM `users` INNER JOIN `organization_users` ON `users`.`id` = `organization_users`.`user_id` WHERE `organization_users`.`organization_id` = 1 /* loading for pp */ LIMIT 11
=>
[#<User:0x0000ffff63e246a0
id: 1,
name: "user",
created_at:
"2024-11-12 04:04:25.961929000 +0000",
updated_at:
"2024-11-12 04:04:25.961929000 +0000">]
2-2. nested classの参照を解決して、User ModelからOrganization Modelにアクセスする可能にする
2-2-1. 参照の解決
class User < ApplicationRecord
has_many :organization_users, class_name: "Organization::User"
end
2-2-2. Modelを利用して、UserからOrganizationに接続する
class User < ApplicationRecord
has_many :organization_users, class_name: "Organization::User"
has_many :organizations, through: :organization_users
end
User.first.organizations
User Load (6.7ms) SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
Organization Load (1.0ms) SELECT `organizations`.* FROM `organizations` INNER JOIN `organization_users` ON `organizations`.`id` = `organization_users`.`organization_id` WHERE `organization_users`.`user_id` = 1 /* loading for pp */ LIMIT 11
=>
[#<Organization:0x0000ffff63ed1cd8
id: 1,
name: "organization",
created_at:
"2024-11-12 04:04:10.864765000 +0000",
updated_at:
"2024-11-12 04:04:10.864765000 +0000">]
3. 結果
class Organization < ApplicationRecord
has_many :organization_users, class_name: "Organization::User"
has_many :users, through: :organization_users
end
class Organization::User < ApplicationRecord
belongs_to :organization
belongs_to :user, class_name: "::User"
end
class User < ApplicationRecord
has_many :organization_users, class_name: "Organization::User"
has_many :organizations, through: :organization_users
end