Files
directus/docs/concepts/relationships.md
2020-10-01 16:26:11 -04:00

6.0 KiB

Relationships

As you might have guessed, relationships are a crucial part of any relational database. Directus supports all standard relationship types, as well as a few more compound types that offer greater flexibility.

Understanding Relationships

While it may seem complex at first, relational data is actually quite straightforward once you understand what's happeneing behind the confusing terminology. Before diving into the details of each type, let's first cover a few basics that will help you better visualize each in your mind's eye.

PKs vs FKs

Every item in a relational database has a unique "key" that identifies it within its collection. Because it's required, the key is the first field created within a collection, typically storing an "auto-increment" number, an automatically generated unique hash, or a manually entered value. They are often abbreviated to "PK" (Primary Key), "ID" (Identifier), "UID" (Unique Identifier), or "UUID" (Universally Unique Identifier), depending on the type of value they store. After it's created, the value of an item's PK should never change.

To link items together relationally, you simply save a reference of an item's PK in a different field. That reference is called a Foreign Key (FK).

::: Compound Keys We've ignored compound keys in this explanation to help keep things as simple as possible. :::

Perspective Matters

Just like primary and foreign keys are directional, relationships are always relative to their "parent" collection. Looking at the same relationship/key from the perspective of a different collection may change or reverse its type.

Many-to-One (M2O)

A Many-to-One (M2O) relationship exists on a field that saves a single FK. For example, a city can only be in one country, but a country can have many cities. So the M2O data model would look like this:

cities ("Many" Collection)
- id (PK)
- name
- country (M2O that stores the FK to a country)

country ("One" Collection)
- id (PK, the value saved to "cities.country")
- name

One-to-Many (O2M)

A One-to-Many (O2M) relationship uses an alias field to reference one or more FKs in a M2O. This is the exact same relationship as the M2O above, but looking at it from the opposite perspective (see Perspective Matters). So the O2M data model is the same, but adds an alias "cities" field to the Country collection, like this:

country ("One" Collection)
- id (PK, the value saved to "cities.country")
- name
- *cities* (O2M alias field that pulls in references from "cities.country")

cities ("Many" Collection)
- id (PK)
- name
- country (stores the FK to a country)

::: Manual Reordering To enable manual reordering for a O2M, simply add a field with the sort type to the "many" side (cities in the above example). :::

::: Translations The Translations interface allows creating multilingual content relationally. It is a standard O2M relatinship, but also includes an additional field on the "many" collection to hold the language key :::

Many-to-Many (M2M)

A Many-to-Many (M2M) relationship uses a "Junction Table" to connect many items from one collection, to many items of another collection. For example, a recipe can have many ingredients, and ingredients can be in many recipes. So the M2M data model would look like this:

recipes (Collection)
- id (PK, the value saved to "recipe_ingredients.recipe")
- name
- *ingredients* (M2M/O2M alias field that pulls in references from recipe_ingredients)

ingredients (Collection)
- id (PK, the value saved to "recipe_ingredients.ingredient")
- name
- *recipes* (M2M/O2M alias field that pulls in references from recipe_ingredients)

recipe_ingredients (Junction Collection)
- id (PK)
- recipe (stores the FK to a recipe)
- ingredient (stores the FK to a ingredient)
- quantity

Notice that the example above also has a quantity field on the junction table. You can add any contextual fields to the junction, and they will also be included in the App's relational edit form.

:::warning M2M == O2M x2 An M2M is technically two relationships viewed as one. Each side has a O2M to the Junction Table that sits in the middle. In that sense, there really is no "M2M". :::

::: Manual Reordering To enable manual reordering for a M2M, simply add a numeric field to the junction table and set it as the Collection Sort. :::

::: Self-Referencing You can also have a M2M relationship that connects items within the same collection. A common example of this is "Related Articles", where each article might relate to many other articles. :::

One-to-One (O2O)

Directus does not include a one-to-one (O2O) relationship type or interface. However a O2O saves is essentially the same as a M2O (storing a foreign key). The only difference is that a O2O enforces the cardinality. In other words, selecting a relational item in a O2O means that item can not be selected elsewhere (it can only be used once). This functionality can be added by checking and constraining uniqueness via a custom event hook or custom interface.

An example of a O2O is: a person only has one unique set of fingerprints, and those fingerprints only belong to one person.

Many-to-Any (M2X)

Sometimes called a "matrix field" or "replicator", the Many-to-Any (M2X) relationship is essentially the same as a M2M, but with one crucial difference: the junction table also stores the parent collection name of the FK. This "compound key" combines the collection name and FK to provide a unique reference to any other item within the project. You can then artificially limit which collections are valid through an "allow list".

An example of a M2X is a "page layout builder". These typically will have modular components across several different collections (eg: "Heading", "Text Block", "Media Asset", etc), and a Pages collections where you can piece them together. Therefore, the junction table will link a specific page with specific components, allowing the creation of relational page layouts.