LoginSignup
7
2

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-12-14

概要

先日の仕事中、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 より引用)

7
2
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
7
2