YOSHINO日記

プログラミングに関すること

Rails: Referenceとforeign_keyの違い

referencesで”外部制約キーはつかない”とは、どういうことなのか?

参照整合性

参照整合性によって、テーブル(表)の列と列との依存関係を定義する。この依存関係において、参照する列を参照列(外部キー)、参照される列を被参照列と呼ぶ。参照列(外部キー)の値は、被参照列が持つ値に含まれるかNULL値でなければならない。

つまり、外部制約キーをつけないということは、参照整合性が保証されないということ。

例えば、migrationファイルが以下のようにある場合、

user

class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :name
      t.references :team

      t.timestamps
    end
  end
end

team

class CreateTeams < ActiveRecord::Migration[5.2]
  def change
    create_table :teams do |t|
      t.string :name

      t.timestamps
    end
  end
end

team_idが、作成されていることがわかる。 これが、referencesをmigrationで指定した時の効能である。

User.create(name: "hoge")
#=> [#<User id: 1, name: "hoge", team_id: nil, created_at: "2018-06-09 06:14:04", updated_at: "2018-06-09 06:14:04">]

しかし、参照整合性が保証されていないので以下のように、存在しないユーザーを作成することも可能である。

User.create(name: "foo", team_id: 123 )
#=>  #<User id: 1, name: "foo", team_id: 123, created_at: "2018-06-09 06:49:19", updated_at: "2018-06-09 06:49:19">

add_reference

外部制約キーをつける

以下のような時です。

class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :name
      t.references :team, foreign_key: true, column

      t.timestamps
    end
  end
end

存在しない参照キーを指定すると、以下のようにエラーが生じて作成できません。

User.create(name: "hoge", team_id: 2)
#=> ActiveRecord::InvalidForeignKey (SQLite3::ConstraintException: FOREIGN KEY constraint failed: INSERT INTO "users" ("name", "team_id", "created_at", "updated_at") VALUES (?, ?, ?, ?))

存在するidを指定する場合、ちゃんと作成できます。

Team.create(name: "大阪チーム")
User.create(name: "hoge", team_id: 1)

注意スべきは、参照整合性は定義よりnullを許容するので、以下も作成できてしまいます。

User.create(name: "team_idを指定しないユーザー")

なので、外部キーを作成する時は、null:falseをつけることが多いです。

foreign_keyだけ貼り直す

以下は、後からカスケードで削除のオプションを追加する例。

def change
  remove_foreign_key :users, :teams
  add_foreign_key :users, :teams, column: "team_id", on_delete: :cascade
end

カスケード削除のオプションをつけて作成する

t.references :user, index: true, null: false, foreign_key: { on_delete: :cascade }