summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean McGivern <sean@mcgivern.me.uk>2017-05-08 10:40:11 +0000
committerSean McGivern <sean@mcgivern.me.uk>2017-05-08 10:40:11 +0000
commitecd37af60bdb0fb22cbf308efab2f7d933403772 (patch)
treedc628fab39bf7e351c14d56a657212c9e456357c
parent2e36ed66087ee32d5a5c29d56148156e066cbd8d (diff)
parentb879079b1aa471baff6be15b820e6249a182cfd5 (diff)
downloadgitlab-ce-ecd37af60bdb0fb22cbf308efab2f7d933403772.tar.gz
Merge branch 'document-foreign-keys' into 'master'
Add documentation about adding foreign keys See merge request !11117
-rw-r--r--changelogs/unreleased/document-foreign-keys.yml4
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/foreign_keys.md63
3 files changed, 68 insertions, 0 deletions
diff --git a/changelogs/unreleased/document-foreign-keys.yml b/changelogs/unreleased/document-foreign-keys.yml
new file mode 100644
index 00000000000..faa467e8185
--- /dev/null
+++ b/changelogs/unreleased/document-foreign-keys.yml
@@ -0,0 +1,4 @@
+---
+title: Add documentation about adding foreign keys
+merge_request:
+author:
diff --git a/doc/development/README.md b/doc/development/README.md
index d04380e5b33..63db332b557 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -48,6 +48,7 @@
- [What requires downtime?](what_requires_downtime.md)
- [Adding database indexes](adding_database_indexes.md)
- [Post Deployment Migrations](post_deployment_migrations.md)
+- [Foreign Keys & Associations](foreign_keys.md)
## Compliance
diff --git a/doc/development/foreign_keys.md b/doc/development/foreign_keys.md
new file mode 100644
index 00000000000..0ab0deb156f
--- /dev/null
+++ b/doc/development/foreign_keys.md
@@ -0,0 +1,63 @@
+# Foreign Keys & Associations
+
+When adding an association to a model you must also add a foreign key. For
+example, say you have the following model:
+
+```ruby
+class User < ActiveRecord::Base
+ has_many :posts
+end
+```
+
+Here you will need to add a foreign key on column `posts.user_id`. This ensures
+that data consistency is enforced on database level. Foreign keys also mean that
+the database can very quickly remove associated data (e.g. when removing a
+user), instead of Rails having to do this.
+
+## Adding Foreign Keys In Migrations
+
+Foreign keys can be added concurrently using `add_concurrent_foreign_key` as
+defined in `Gitlab::Database::MigrationHelpers`. See the [Migration Style
+Guide](migration_style_guide.md) for more information.
+
+Keep in mind that you can only safely add foreign keys to existing tables after
+you have removed any orphaned rows. The method `add_concurrent_foreign_key`
+does not take care of this so you'll need to do so manually.
+
+## Cascading Deletes
+
+Every foreign key must define an `ON DELETE` clause, and in 99% of the cases
+this should be set to `CASCADE`.
+
+## Indexes
+
+When adding a foreign key in PostgreSQL the column is not indexed automatically,
+thus you must also add a concurrent index. Not doing so will result in cascading
+deletes being very slow.
+
+## Dependent Removals
+
+Don't define options such as `dependent: :destroy` or `dependent: :delete` when
+defining an association. Defining these options means Rails will handle the
+removal of data, instead of letting the database handle this in the most
+efficient way possible.
+
+In other words, this is bad and should be avoided at all costs:
+
+```ruby
+class User < ActiveRecord::Base
+ has_many :posts, dependent: :destroy
+end
+```
+
+Should you truly have a need for this it should be approved by a database
+specialist first.
+
+You should also not define any `before_destroy` or `after_destroy` callbacks on
+your models _unless_ absolutely required and only when approved by database
+specialists. For example, if each row in a table has a corresponding file on a
+file system it may be tempting to add a `after_destroy` hook. This however
+introduces non database logic to a model, and means we can no longer rely on
+foreign keys to remove the data as this would result in the filesystem data
+being left behind. In such a case you should use a service class instead that
+takes care of removing non database data.