debian-mirror-gitlab/doc/development/foreign_keys.md

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

130 lines
4.7 KiB
Markdown
Raw Normal View History

2021-01-29 00:20:46 +05:30
---
2022-07-23 23:45:48 +05:30
stage: Data Stores
2021-01-29 00:20:46 +05:30
group: Database
2021-02-22 17:27:13 +05:30
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
2021-01-29 00:20:46 +05:30
---
2017-09-10 17:25:29 +05:30
# 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
```
2022-07-23 23:45:48 +05:30
Add a foreign key here on column `posts.user_id`. This ensures
2017-09-10 17:25:29 +05:30
that data consistency is enforced on database level. Foreign keys also mean that
2021-09-30 23:02:18 +05:30
the database can very quickly remove associated data (for example, when removing a
2017-09-10 17:25:29 +05:30
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`
2022-07-23 23:45:48 +05:30
does not take care of this so you need to do so manually. See
2021-09-30 23:02:18 +05:30
[adding foreign key constraint to an existing column](database/add_foreign_key_to_existing_column.md).
2017-09-10 17:25:29 +05:30
## 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,
2022-07-23 23:45:48 +05:30
thus you must also add a concurrent index. Not doing so results in cascading
2017-09-10 17:25:29 +05:30
deletes being very slow.
2020-07-28 23:09:34 +05:30
## Naming foreign keys
By default Ruby on Rails uses the `_id` suffix for foreign keys. So we should
only use this suffix for associations between two tables. If you want to
reference an ID on a third party platform the `_xid` suffix is recommended.
2022-07-23 23:45:48 +05:30
The spec `spec/db/schema_spec.rb` tests if all columns with the `_id` suffix
2020-07-28 23:09:34 +05:30
have a foreign key constraint. So if that spec fails, don't add the column to
`IGNORED_FK_COLUMNS`, but instead add the FK constraint, or consider naming it
differently.
2017-09-10 17:25:29 +05:30
## Dependent Removals
Don't define options such as `dependent: :destroy` or `dependent: :delete` when
2022-07-23 23:45:48 +05:30
defining an association. Defining these options means Rails handles the
2017-09-10 17:25:29 +05:30
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
2021-03-11 19:13:27 +05:30
foreign keys to remove the data as this would result in the file system data
2017-09-10 17:25:29 +05:30
being left behind. In such a case you should use a service class instead that
takes care of removing non database data.
2020-03-13 15:44:24 +05:30
2022-07-23 23:45:48 +05:30
In cases where the relation spans multiple databases you have even
2022-05-07 20:08:51 +05:30
further problems using `dependent: :destroy` or the above hooks. You can
read more about alternatives at [Avoid `dependent: :nullify` and
`dependent: :destroy` across
databases](database/multiple_databases.md#avoid-dependent-nullify-and-dependent-destroy-across-databases).
2022-07-23 23:45:48 +05:30
## Alternative primary keys with `has_one` associations
2020-03-13 15:44:24 +05:30
Sometimes a `has_one` association is used to create a one-to-one relationship:
```ruby
class User < ActiveRecord::Base
has_one :user_config
end
class UserConfig < ActiveRecord::Base
belongs_to :user
end
```
In these cases, there may be an opportunity to remove the unnecessary `id`
column on the associated table, `user_config.id` in this example. Instead,
the originating table ID can be used as the primary key for the associated
table:
```ruby
create_table :user_configs, id: false do |t|
t.references :users, primary_key: true, default: nil, index: false, foreign_key: { on_delete: :cascade }
...
end
```
2020-04-22 19:07:51 +05:30
2022-07-23 23:45:48 +05:30
Setting `default: nil` ensures a primary key sequence is not created, and since the primary key
automatically gets an index, we set `index: false` to avoid creating a duplicate.
You also need to add the new primary key to the model:
2020-04-22 19:07:51 +05:30
```ruby
class UserConfig < ActiveRecord::Base
self.primary_key = :user_id
belongs_to :user
end
```
2022-07-16 23:28:13 +05:30
Using a foreign key as primary key saves space but can make
[batch counting](service_ping/implement.md#batch-counters) in [Service Ping](service_ping/index.md) less efficient.
2022-07-23 23:45:48 +05:30
Consider using a regular `id` column if the table is relevant for Service Ping.