diff --git a/docs/06-concepts/03-data-and-the-database/01-models/02-inheritance-and-polymorphism.md b/docs/06-concepts/03-data-and-the-database/01-models/02-inheritance-and-polymorphism.md index 21ab514b..8cdec01b 100644 --- a/docs/06-concepts/03-data-and-the-database/01-models/02-inheritance-and-polymorphism.md +++ b/docs/06-concepts/03-data-and-the-database/01-models/02-inheritance-and-polymorphism.md @@ -29,7 +29,7 @@ fields: This will generate a class with both `name` and `age` fields. -In the generated class, inherited fields come first. To place a field at the end of the combined field list instead, mark it with the `tail` keyword, e.g. `notes: String?, tail`. Tail fields from child classes appear before tail fields from parent classes, and the keyword is not allowed on the `id` field. +In the generated class, inherited fields come first. To change that for a field, mark it with the `tail` keyword. See [Ordering inherited fields](#ordering-inherited-fields). #### Inheritance on table models @@ -73,6 +73,51 @@ As the example shows, indexes can be defined on inherited fields in a child clas To use a base model that is shared between server and client and extend it on the server with a table, see [Shared packages](./shared-packages). +### Ordering inherited fields + +Inherited fields are ordered from the root class down to the child class. Since a base class is declared first, its audit fields end up in front of the fields that describe the child. Mark a field with the `tail` keyword to push it behind every normal field in the hierarchy instead. + +Add `tail` after the field type and any other keywords: + +```yaml +class: BaseEntity +fields: + ownerId: UuidValue + createdAt: DateTime, default=now, tail + updatedAt: DateTime, default=now, tail +``` + +Tail ordering applies across the whole hierarchy. Normal fields keep their root-to-child order, and the tail fields follow, starting from the most specific child and working back toward the root parent: + +```yaml +class: Document +extends: BaseEntity +fields: + authorName: String + archivedAt: DateTime?, tail +``` + +```yaml +class: Article +extends: Document +fields: + title: String + publishedAt: DateTime?, tail +``` + +The generated `Article` class orders its fields like this: + +1. `ownerId`, a normal field from the root class. +2. `authorName`, a normal field from the parent class. +3. `title`, a normal field from the child class. +4. `publishedAt`, a tail field from the child class. +5. `archivedAt`, a tail field from the parent class. +6. `createdAt` and `updatedAt`, tail fields from the root class, in the order they are declared. + +The same order applies to the generated constructor parameters and the serialized output. For table models it also determines the generated table definition, where the primary `id` column always comes first. The `tail` keyword cannot be used on `id`. + +Adding or removing `tail` only affects generated code. It does not rename a database column, and it does not produce a migration that reorders the columns of an existing table. + ### Sealed classes In addition to the `extends` keyword, you can also use the `sealed` keyword to create sealed class hierarchies. The compiler then knows all subclasses, so a `switch` over the model is exhaustive and every possible case must be handled. diff --git a/docs/06-concepts/lookups/model-reference.md b/docs/06-concepts/lookups/model-reference.md index 494b70e3..22e5265d 100644 --- a/docs/06-concepts/lookups/model-reference.md +++ b/docs/06-concepts/lookups/model-reference.md @@ -20,7 +20,7 @@ Every keyword available in a Serverpod model file, and whether it applies to a ` | [**fields**](../data-and-the-database/models#class) | All fields in the generated class should be listed here. | ✅ | ✅ | | | [**type (fields)**](../data-and-the-database/models#class) | Denotes the data type for a field. | ✅ | ✅ | | | [**required**](../data-and-the-database/models#required-fields) | Makes the field required. This keyword can only be used for **nullable** fields. | ✅ | ✅ | | -| [**tail**](../data-and-the-database/models/inheritance-and-polymorphism#extending-a-class) | Places the field at the end of the combined field list when inherited. Not allowed on the `id` field. | ✅ | ✅ | | +| [**tail**](../data-and-the-database/models/inheritance-and-polymorphism#ordering-inherited-fields) | Places the field after all normal fields of an inheritance hierarchy. Child tail fields come before parent tail fields. Not allowed on the `id` field. | ✅ | ✅ | | | [**scope**](../data-and-the-database/models#limiting-visibility-of-a-generated-class) | Denotes the scope for a field. | ✅ | | | | [**jsonKey**](../data-and-the-database/models#json-key-aliasing) | Sets a custom key name for JSON serialization and deserialization. | ✅ | | | | [**column**](../data-and-the-database/database/tables#column-name-override) | Overrides the database column name for a field. | ✅ | | |