インドカレーファンクラブ

パソコン、カメラ

【Rails】associationを張ったModelを生成する時のmigrationの記述

一人のユーザは一つのプロフィールを有する、というようなAssociationをつくってみる
1:1です

前提

以下のような構成を目指す

User

  • name
  • profile_id

Profile

  • text

モデルはこうなる

class User < ApplicationRecord
  belongs_to :profile
end

class Profile < ApplicationRecord
end

こんな感じのことをしたい

p = Profile.new(text: 'hello')
p.save!
u = User.new(name: 'test user', profile: p)
u.save!
User.first.profile.name # 'hello'

Profileの生成は特に迷うことはない

rails g model profile text:string

db/migrate/yyyyMMddHHmmss_create_profile.rb

class CreateProfiles < ActiveRecord::Migration[5.2]
  def change
    create_table :profiles do |t|
      t.string :text
      t.timestamps
    end
  end
end

で、Userを作っていく時に色々間違えたのでそのメモ

結論からいうと...

上記の

User

  • name
  • profile_id

こんな感じにUserにprofile_idを持たせてあげて、Modelにbelongs_to :profileの記述するだけではダメで、
schema.rbにもbelongs_toが記述されていないと、正しくActiveRecordのassociationが効いてくれない

正しいパターン

rails g model user name:string profile:belongs_to

※belongs_toはreferenceでもよい

参考:

belongs_to is an alias of reference
https://stackoverflow.com/questions/7788188/what-is-the-difference-between-t-belongs-to-and-t-references-in-rails

このgenerateコマンドによって上記のModelと以下のmigrationファイルが生成される

db/migrate/yyyyMMddHHmmss_create_user.rb

class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      # ↓が必要な行
      # ついでにprofilesじゃなくて単数形のprofileであることにも注意
      t.belongs_to :profile, foreign_key: true
      t.string :name
      t.timestamps
    end
  end
end

:nameがt.string、つまりstring型であるように
:profileは reference型 という型になる (belongs_to型とはあんまり言わない)

このreference型の:profileが、カラムprofile_idを生成してくれる

メモ:
ソース的には ActiveRecord::ConnectionAdapters::SchemaStatements#add_reference のあたり

add_reference(:table_name, :reference_name):
Adds a new column reference_name_id by default an integer.

ややこしい話はさておき、Migrationを適用するとこうなる

schema.rb

ActiveRecord::Schema.define(version: yyyy_MM_dd_HHmmss) do

  create_table "profiles", force: :cascade do |t|
    t.string "text"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "users", force: :cascade do |t|
    t.integer "profile_id"
    t.string "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["profile_id"], name: "index_users_on_profile_id" # ここがポイント!
  end

end

勘違いしてた失敗パターン

こんな感じただUserにprofile_idを持たせてあげて、Modelにbelongs_to :profileの記述するだけではダメで、 schema.rbにもbelongs_toが記述されていないと、正しくActiveRecordのassociationが効いてくれない

これをやってしまったパターン

rails g model user name:string profile_id:integer
# 正しいのは↓
# rails g model cat name:string profile:belongs_to

db/migrate/yyyyMMddHHmmss_create_user.rb

class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :name
      t.integer :profile_id
      # 正しいのは↓
      # t.belongs_to :profile, foreign_key: true
      t.timestamps
    end
  end
end

Migrationを適用するとこうなる

schema.rb

ActiveRecord::Schema.define(version: yyyy_MM_dd_HHmmss) do

  create_table "profiles", force: :cascade do |t|
    t.string "text"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "users", force: :cascade do |t|
    t.integer "profile_id"
    t.string "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    # ↓が足りてない
    # t.index ["profile_id"], name: "index_users_on_profile_id"0
  end

end