外部キーと関連付け
モデルに関連を追加する際には、外部キーも追加する必要があります。例えば、以下のようなモデルがあるとします:
class User < ActiveRecord::Base
has_many :posts
end
カラムposts.user_id
に外部キーを追加します。これにより、データベースレベルでデータの一貫性が保証されます。外部キーはまた、(ユーザーを削除する場合などに)Railsが行うのではなく、データベースが関連データを非常に迅速に削除できることを意味します。
マイグレーションでの外部キーの追加
Gitlab::Database::MigrationHelpers
で定義されているように、add_concurrent_foreign_key
を使って外部キーを同時に追加することができます。詳細はマイグレーションスタイルガイドを参照してください。
既存のテーブルに外部キーを安全に追加できるのは、孤児となった行を削除した後であることに注意してください。add_concurrent_foreign_key
のメソッドではこの処理は行われませんので、手動で行う必要があります。既存のカラムへの外部キー制約の追加を参照してください。
マイグレーションにおける外部キーの更新
外部キー制約を変更し、カラムは保持したまま制約条件を更新しなければならないことがあります。例えば、ON DELETE CASCADE
からON DELETE SET NULL
へ、あるいはその逆です。
PostgreSQLは、重複する外部キーの追加を防ぎます。PostgreSQLは、最近追加された制約を尊重します。これにより、列の外部キー保護を失うことなく外部キーを置き換えることができます。
外部キーを置き換えるには
-
古い外部キーを削除する前に、新しい外部キーを追加するために外部キー制約の名前を変更する必要があります。
class ReplaceFkOnPackagesPackagesProjectId < Gitlab::Database::Migration[2.1] disable_ddl_transaction! NEW_CONSTRAINT_NAME = 'fk_new' def up add_concurrent_foreign_key(:packages_packages, :projects, column: :project_id, on_delete: :nullify, validate: false, name: NEW_CONSTRAINT_NAME) end def down with_lock_retries do remove_foreign_key_if_exists(:packages_packages, column: :project_id, on_delete: :nullify, name: NEW_CONSTRAINT_NAME) end end end
-
class ValidateFkNew < Gitlab::Database::Migration[2.1] NEW_CONSTRAINT_NAME = 'fk_new' # foreign key added in <link to MR or path to migration adding new FK> def up validate_foreign_key(:packages_packages, name: NEW_CONSTRAINT_NAME) end def down # no-op end end
-
古い外部キーを削除します:
class RemoveFkOld < Gitlab::Database::Migration[2.1] OLD_CONSTRAINT_NAME = 'fk_old' # new foreign key added in <link to MR or path to migration adding new FK> # and validated in <link to MR or path to migration validating new FK> def up remove_foreign_key_if_exists(:packages_packages, column: :project_id, on_delete: :cascade, name: OLD_CONSTRAINT_NAME) end def down # Validation is skipped here, so if rolled back, this will need to be revalidated in a separate migration add_concurrent_foreign_key(:packages_packages, :projects, column: :project_id, on_delete: :cascade, validate: false, name: OLD_CONSTRAINT_NAME) end end
カスケード削除
すべての外部キーはON DELETE
節を定義する必要があり、99%の場合、これはCASCADE
に設定されるべきです。
インデックス
PostgreSQLで外部キーを追加する場合、列は自動的にインデックス付けされません。これを行わないと、カスケード削除が非常に遅くなります。
外部キーの命名
デフォルトでは、Ruby on Railsは外部キーに_id
というサフィックスを使用します。したがって、2つのテーブル間の関連付けにのみこのサフィックスを使うべきです。サードパーティのプラットフォームでIDを参照する場合は、_xid
サフィックスを使用することをお勧めします。
仕様spec/db/schema_spec.rb
は、_id
サフィックスを持つすべてのカラムが外部キー制約を持っているかどうかをテストします。そのため、この仕様が失敗した場合は、IGNORED_FK_COLUMNS
に列を追加せず、代わりにFK制約を追加するか、別の名前を付けることを検討してください。
依存関係の削除
アソシエーションを定義するときに、dependent: :destroy
やdependent: :delete
といったオプションを定義しないでください。これらのオプションを定義すると、データの削除をデータベースに任せる代わりにRailsが最も効率的な方法で処理することになります。
つまり、これは良くないことなので、絶対に避けるべきです:
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
end
本当に必要であれば、まずデータベースの専門家の承認が必要です。
また、before_destroy
やafter_destroy
コールバックをモデル上で after_destroy
定義することは、after_destroy
絶対に必要な after_destroy
場合をafter_destroy
除き、データベースの専門家が承認した場合にのみ after_destroy
行うようにしてください。たとえば、after_destroy
テーブルの各行がファイルシステム上に対応するファイルを持って after_destroy
いる場合、フックをafter_destroy
追加したくなるかもしれません after_destroy
。しかし、これはモデルにデータベース以外のロジックを導入することになり、データを削除するために外部キーに頼ることができなくなります。このような場合は、代わりにデータベース以外のデータを削除するサービスクラスを使うべきです。
リレーションが複数のデータベースにまたがっている場合、dependent: :destroy
や上記のフックを使用するとさらに問題が生じます。 dependent: :nullify
やdependent: :destroy
を避ける方法については、を参照してください。
has_one
関連付けを持つ代替主キー
一対一のリレーションシップを作成するためにhas_one
のアソシエーションを使用することがあります:
class User < ActiveRecord::Base
has_one :user_config
end
class UserConfig < ActiveRecord::Base
belongs_to :user
end
このような場合、関連テーブル(この例ではuser_config.id
)の不要なid
列を削除することができます。このような場合、関連テーブル(この例では)の不要な 列を削除することができます。代わりに、関連テーブルの主キーとして元のテーブル ID を使用することができます:
create_table :user_configs, id: false do |t|
t.references :users, primary_key: true, default: nil, index: false, foreign_key: { on_delete: :cascade }
...
end
default: nil
を設定することで、主キーシーケンスが作成されないようにします。また、主キーは自動的にインデックスを取得するため、index: false
を設定して重複を作成しないようにします。また、新しい主キーをモデルに追加する必要があります:
class UserConfig < ActiveRecord::Base
self.primary_key = :user_id
belongs_to :user
end
主キーとして外部キーを使用すると、スペースを節約できますが、Service Pingでの バッチカウントの効率が悪くなります。テーブルがService Pingに関連する場合は、通常のid
列の使用を検討してください。