環境
- Rails 5.2.2
やりたいこと
-
FistNameクラス(first_namesテーブル)とLastNameクラス(last_namesテーブル)をmany-to-manyで繋ぐFullNameクラス(full_namesテーブル)を作りたい。
手順
rails g model の実行
rails generate model FullName first_name:references last_name:references
生成されるマイグレーションファイル
class CreateFullNames < ActiveRecord::Migration[5.2]
def change
create_table :full_names do |t|
t.references :first_name, foreign_key: true
t.references :last_name, foreign_key: true
t.timestamps
end
end
end
マイグレーションファイルを編集
-
t.references(belongs_toはreferencesのalias)はindexも作成してくれます。-
foreign_key: trueがあれば、外部キー制約もつけてくれます。
-
-
t.referencesを使いつつ、中間テーブルでは、以下の通り変更します。- 複合キーでunique制約を張ります。(※1)
- 上記のunique制約で
first_name_idに対するindexも張られるため、index: falseを指定します。(※2) -
last_name_idに対するindexも不要であれば、index: falseを指定します。
class CreateFullNames < ActiveRecord::Migration[5.2]
def change
create_table :full_names do |t|
t.references :first_name, foreign_key: true, null: false, index: false # (※2)
t.references :last_name, foreign_key: true, null: false
t.timestamps
end
add_index :full_names, [:first_name_id, :last_name_id], unique: true # (※1)
end
end
モデル
マイグレーションを実行して生成される中間テーブルのモデル
app/models/full_name.rb
class FullName < ApplicationRecord
belongs_to :first_name
belongs_to :last_name
# DB レベルでは外部キー制約、複合ユニークインデックスが付いているため、以下の validates が無くても
# 意図しないデータが保存されることはありません(なので、無くても大丈夫です)。
# 但し、その場合 DB のエラーが返されます。
# Rails のエラーを返したい時は、validates をつけましょう。
# 但し、Rails の処理のほうが遅いはずなので、メッセージへのこだわりが無く、スピード重視の場合はここの validates は外してください。
#
# ※上記については、belongs_to のデフォルトで required: true となっているため、presence: true の validates については、
# Rails レベルで担保されそうです(エラー内容は validates ~ presence: true と belongs_to で異なるかもしれませんが)。
validates :first_name_id, presence: true
validates :last_name_id, presence: true
validates :first_name_id, uniqueness: { scope: :last_name_id }
end
その他のモデル
app/models/first_name.rb
class FirstName < ApplicationRecord
has_many :full_names, dependent: :destroy
has_many :last_names, through: :full_names
end
app/models/last_name.rb
class LastName < ApplicationRecord
has_many :full_names, dependent: :destroy
has_many :first_names, through: :full_names
end