0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rails Namespace Modelを利用した際のrelation を修正する

Last updated at Posted at 2024-11-12

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.

change log

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については後続で修正します。)

app/models/organization.rb
class Organization < ApplicationRecord
    has_many :organization_users
end
app/models/organization/user.rb
class Organization::User
    belongs_to :organization
    belongs_to :user
end
app/modles/user.rb
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を利用して参照をする。

models/Organization.rb
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を取得するように変更を実施します。

app/models/organization.rb
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)に参照先を変更する。

app/models/organization/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. 参照の解決

models/user.rb
class User < ApplicationRecord
    has_many :organization_users, class_name: "Organization::User"
end

2-2-2. Modelを利用して、UserからOrganizationに接続する

models/user.rb
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. 結果

app/models/organization.rb
class Organization < ApplicationRecord
    has_many :organization_users, class_name: "Organization::User"
    has_many :users, through: :organization_users
end
app/models/organization/user.rb
class Organization::User < ApplicationRecord
    belongs_to :organization
    belongs_to :user, class_name: "::User"
end
app/models/user.rb
class User < ApplicationRecord
    has_many :organization_users, class_name: "Organization::User"
    has_many :organizations, through: :organization_users
end
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?