Help us understand the problem. What is going on with this article?

ActiveRecordのassociationをmodule内のモデルで使う

概要

先日の仕事中、module内にあるActive Recordのモデルに対してassociationを作成しようとしたときに少し苦戦したので、その経緯をまとめます。
やり方だけ知りたい方は結論へどうぞ。

経緯

前提

次のような3つのモデルを作成し、BuzzFizz::FooFizz::FooFizz::Barをそれぞれhas_manyで持つようにしたい。

app/models/fizz.rb

module Fizz
  def self.table_name_prefix
    'fizz_'
  end
end

app/models/buzz.rb

class Buzz < ApplicationRecord
end

app/models/fizz/bar.rb

class Fizz::Bar < ApplicationRecord
end

app/models/fizz/foo.rb

class Fizz::Foo < ApplicationRecord
end

試したこと1 - テーブル名と同じprefixをつけてhas_manyで指定する

まず最初に、テーブル名と同じprefixであるfizz_has_manyで指定してみました。

app/models/buzz.rb

class Buzz < ApplicationRecord
  has_many :fizz_foos
end

app/models/fizz/bar.rb

class Fizz::Bar < ApplicationRecord
  belongs_to :fizz_foo
end

app/models/fizz/foo.rb

class Fizz::Foo < ApplicationRecord
  has_many :fizz_bars
  belongs_to :buzz
end

結果

メソッド 結果
Buzz.first.fizz_foos NameError (uninitialized constant Buzz::FizzFoo)が発生 ×
Fizz::Foo.first.fizz_bars NameError (uninitialized constant Fizz::Foo::FizzBar)が発生 ×
Fizz::Foo.first.buzz #<Buzz id: 1, created_at: "2019-12-11 14:40:32", updated_at: "2019-12-11 14:40:32">
Fizz::Bar.first.fizz_foo nil ×

Fizz::Foo.first.buzzのときしか正しく関係先が取得できなかった。

試したこと2 - prefixを外す

moduleに無いモデル同士のときは、並列のモデルが解決できているのに、1.ではuninitialized constant Fizz::Foo::FizzBar親モデル::has_manyで指定した名前となっている。
それを元に考えると、同じmodule内のモデル同士であればprefixがなくても解決できるのではと思い、試してみました。

app/models/buzz.rb

class Buzz < ApplicationRecord
  has_many :fizz_foos
end

app/models/fizz/bar.rb

class Fizz::Bar < ApplicationRecord
  belongs_to :foo
end

app/models/fizz/foo.rb

class Fizz::Foo < ApplicationRecord
  has_many :bars
  belongs_to :buzz
end

結果

メソッド 結果
Buzz.first.fizz_foos NameError (uninitialized constant Buzz::FizzFoo)が発生 ×
Fizz::Foo.first.bars #<ActiveRecord::Associations::CollectionProxy [#<Fizz::Bar id: 1, foo_id: 1, created_at: "2019-12-11 14:43:27", updated_at: "2019-12-11 14:43:27">]>
Fizz::Foo.first.buzz #<Buzz id: 1, created_at: "2019-12-11 14:40:32", updated_at: "2019-12-11 14:40:32">
Fizz::Bar.first.foo #<Fizz::Foo id: 1, buzz_id: 1, created_at: "2019-12-11 14:40:51", updated_at: "2019-12-11 14:40:51">

Fizzモジュール内同士の関係は正しく取れるようになったが、別モジュールからの時はまだエラーが起きている。

試したことに3 - class_nameを使う

下記2つを参考に別モジュールのときにclass_nameを使用するように変更。
https://stackoverflow.com/questions/25715426/rails-associations-with-modules
https://railsguides.jp/association_basics.html

app/models/buzz.rb

class Buzz < ApplicationRecord
  has_many :fizz_foos, class_name: 'Fizz::Foo'
end

app/models/fizz/bar.rb

class Fizz::Bar < ApplicationRecord
  belongs_to :foo
end

app/models/fizz/foo.rb

class Fizz::Foo < ApplicationRecord
  has_many :bars
  belongs_to :buzz
end

結果

メソッド 結果
Buzz.first.fizz_foos #<ActiveRecord::Associations::CollectionProxy [#<Fizz::Foo id: 1, buzz_id: 1, created_at: "2019-12-11 14:40:51", updated_at: "2019-12-11 14:40:51">]>
Fizz::Foo.first.bars #<ActiveRecord::Associations::CollectionProxy [#<Fizz::Bar id: 1, foo_id: 1, created_at: "2019-12-11 14:43:27", updated_at: "2019-12-11 14:43:27">]>
Fizz::Foo.first.buzz #<Buzz id: 1, created_at: "2019-12-11 14:40:32", updated_at: "2019-12-11 14:40:32">
Fizz::Bar.first.foo #<Fizz::Foo id: 1, buzz_id: 1, created_at: "2019-12-11 14:40:51", updated_at: "2019-12-11 14:40:51">

正しく動くようになった!

結論

  • 同じモジュール内のassociationの時、モジュールがない場合と同様にモジュールを除いたクラス名で作成できる
  • 別モジュールのモデル間のassociationの時、class_nameでモジュールを含めたクラス名を指定する

ちなみに

そもそも、この記事を書くために再度調べていたら、Ruby on Rails API にしっかり記載がありました。

By default, associations will look for objects within the current module scope.

( https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Modules より引用)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした