Skip to content

Commit

Permalink
ok, great
Browse files Browse the repository at this point in the history
  • Loading branch information
dickermoshe committed Oct 16, 2024
1 parent 86c68e4 commit e7488f9
Show file tree
Hide file tree
Showing 15 changed files with 305 additions and 355 deletions.
4 changes: 2 additions & 2 deletions docs/docs/Internals/index.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---

title: Drift internals
description: Work in progress documentation on drift internals
title: Install from GitHub
description: How to install drift from GitHub

---

Expand Down
2 changes: 2 additions & 0 deletions docs/docs/_redirects
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,6 @@
/docs/platforms/postgres/ /Platforms/postgres/
/docs/advanced-features/daos/ /dart_api/daos/
/api/* https://pub.dev/documentation/drift/latest/index.html
https://drift.simonbinder.eu/custom_row_classes/ /dart_api/dataclass/
https://drift.simonbinder.eu/type_converters/ /dart_api/tables/#custom-types

189 changes: 128 additions & 61 deletions docs/docs/custom_row_classes.md → docs/docs/dart_api/dataclass.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,112 @@
---

title: Custom row classes
description: Use your own classes as data classes for drift tables
title: Dataclass
description: Dataclass for reading and writing data to the database.

---

For each table declared in Dart or in a drift file, `drift_dev` generates a row class (sometimes also referred to as _data class_)
to hold a full row and a companion class for updates and inserts.
This works well for most cases: Drift knows what columns your table has, and it can generate a simple class for all of that.
In some cases, you might want to customize the generated classes though.
For instance, you might want to add a mixin, let it extend another class or interface, or use other builders like
`json_serializable` to customize how it gets serialized to json.

As a solution, drift allows you to use your own classes as data classes for the database.

## Using custom classes
# Generated Dataclass

To use a custom row class, simply annotate your table definition with `@UseRowClass`.
Drift generates a dataclass for each table in your database. These dataclasses represent query results and come with built-in equality, hashing, and serialization support. They also include a `copyWith` method for easy modification.

**Example:**

For a `Users` table, Drift automatically generates a `User` dataclass. This dataclass is used for all read operations from the `Users` table, ensuring type-safe and structured data retrieval.

{{ load_snippet('generated-dataclass','lib/snippets/dart_api/dataclass.dart.excerpt.json') }}

## Dataclass Name

The dataclass name is derived from the table name.

- If the name ends in `s`, the dataclass name will be the name with `s` removed.
- Example: `Users` -> `User`
- Otherwise, the dataclass name will be the name with `Data` appended.
- Example: `UserInfo` -> `UserInfoData`


To use a custom name use the `@DataClassName` annotation.

**Example:**

{{ load_snippet('data-class-name','lib/snippets/dart_api/dataclass.dart.excerpt.json') }}

## Json serialization

### Key names

When serializing to json, the generated dataclass will use the column name in `snake_case` for the json keys.

**Example:**

{{ load_snippet('default-json-keys','lib/snippets/dart_api/dataclass.dart.excerpt.json') }}

```json
{
"id": 1,
"title": "Todo 1",
"created_at": "2024-02-29T12:00:00Z"
}
```

### Custom json keys

To use a custom name for JSON serialization, use the `@JsonKey` annotation.

**Example:**

{{ load_snippet('custom-json-keys','lib/snippets/dart_api/dataclass.dart.excerpt.json') }}

```json
{
"id": 1,
"title": "Todo 1",
"created": "2024-02-29T12:00:00Z"
}
```

If you prefer to use the actual column name in SQL as the JSON key, set `use_sql_column_name_as_json_key` to `true` in the `build.yaml` file.

```yaml title="build.yaml"
targets:
$default:
builders:
drift_dev:
options:
use_sql_column_name_as_json_key : true
```
For more details on customizing column names in SQL, refer to the [column name](tables.md#named) documentation.
## Companions
In addition to the generated dataclass representing a complete row, Drift also generates a companion object for each table, which represents a partial row and can be used to update existing rows.
<div class="annotate" markdown>
{{ load_snippet('generated-companion','lib/snippets/dart_api/dataclass.dart.excerpt.json') }}
</div>
1. `o()` is just a helper function that creates a `UsersCompanion`.

### Value object
When using the companion object to update a row, optional fields must be wrapped in a `Value` object. This is used by Drift to distinguish between `null` and not present values.

{{ load_snippet('generated-value','lib/snippets/dart_api/dataclass.dart.excerpt.json') }}

## Custom dataclass

The generated dataclass works well for most cases, but you might want to use your own class as a dataclass for a table.

For instance, you might want to add a mixin, let it extend another class or interface, or use other builders like `json_serializable` to customize how it gets serialized to json.

!!! note "Row Class"

In the documentation, we use the terms _row class_ and _dataclass_ interchangeably.
Both refer to a class that represents a row of a database table.

To use a custom row class, simply annotate your table definition with `@UseRowClass`.

{{ load_snippet('start','lib/snippets/custom_row_classes/default.dart.excerpt.json','lib/snippets/custom_row_classes/named.dart.excerpt.json') }}

Expand Down Expand Up @@ -46,6 +135,32 @@ If you want to use another constructor, set the `constructor` parameter on the

{{ load_snippet('named','lib/snippets/custom_row_classes/default.dart.excerpt.json','lib/snippets/custom_row_classes/named.dart.excerpt.json') }}

### Custom companions

In most cases, generated companion classes are the right tool for updates and inserts.
If you prefer to use your custom row class for inserts, just make it implement `Insertable<T>`, where
`T` is the tye of your row class itself.
For instance, the previous class could be changed like this:

```dart
class User implements Insertable<User> {
final int id;
final String name;
final DateTime birthDate;
User({required this.id, required this.name, required this.birthDate});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
return UsersCompanion(
id: Value(id),
name: Value(name),
birthDate: Value(birthDate),
).toColumns(nullToAbsent);
}
}
```

### Static and asynchronous factories

Starting with drift 2.0, the custom constructor set with the `constructor`
Expand All @@ -65,7 +180,7 @@ class User {
}
```

### Existing row classes in drift files
### Custom dataclass in drift files

To use existing row classes in drift files, use the `WITH` keyword at the end of the
table declaration. Also, don't forget to import the Dart file declaring the row
Expand Down Expand Up @@ -98,33 +213,7 @@ CREATE TABLE users(
) WITH User.myNamedConstructor;
```

## Inserts and updates with custom classes

In most cases, generated companion classes are the right tool for updates and inserts.
If you prefer to use your custom row class for inserts, just make it implement `Insertable<T>`, where
`T` is the name of your row class itself.
For instance, the previous class could be changed like this:

```dart
class User implements Insertable<User> {
final int id;
final String name;
final DateTime birthDate;
User({required this.id, required this.name, required this.birthDate});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
return UsersCompanion(
id: Value(id),
name: Value(name),
birthDate: Value(birthDate),
).toColumns(nullToAbsent);
}
}
```

## Existing row classes for queries
#### Custom dataclass for queries

Existing row classes may also be applied to named queries defined in a `.drift` file.
They have a similar syntax, adding the `WITH` keyword after the name of the query:
Expand Down Expand Up @@ -257,25 +346,3 @@ If you have questions about existing result classes, or think you have found an
properly handled, please [start a discussion](https://github.com/simolus3/drift/discussions/new) in
the drift repository, thanks!

## When custom classes make sense

The default drift-generated classes are a good default for most applications.
In some advanced use-cases, custom classes can be a better alternative though:

- Reduce generated code size: Due to historical reasons and backwards-compatibility, drift's classes
contain a number of methods for json serialization and `copyWith` that might not be necessary
for all users.
Custom row classes can reduce bloat here.
- Custom superclasses: A custom row class can extend and class and implement or mix-in other classes
as desired.
- Other code generators: Since you control the row class, you can make better use of other builders like
`json_serializable` or `built_value`.

## Limitations

These restrictions will be gradually lifted in upcoming drift versions. Follow [#1134](https://github.com/simolus3/drift/issues/1134) for details.

For now, this feature is subject to the following limitations:

- In drift files, you can only use the default unnamed constructor

8 changes: 1 addition & 7 deletions docs/docs/dart_api/manager.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
---

title: Manager
title: Queries
description: Use easier bindings for common queries.

---



With generated code, drift allows writing SQL queries in type-safe Dart.
While this is provides lots of flexibility, it requires familiarity with SQL.
As a simpler alternative, drift 2.18 introduced a new set of APIs designed to
make common queries much easier to write.

The examples on this page use the database from the [setup](../setup.md)
instructions.

Expand Down
59 changes: 52 additions & 7 deletions docs/docs/dart_api/tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ Columns can be customized with several options. These options are available on a

Customize this by setting the `case_from_dart_to_sql` option in your `build.yaml` file.
```yaml
```yaml title="build.yaml"
targets:
$default:
builders:
Expand Down Expand Up @@ -258,7 +258,6 @@ Every table in a database should have a primary key - a column or set of columns

---


## Multi-column uniqueness

To enforce that a combination of columns is unique, override the `uniqueKeys` getter in your table class.
Expand Down Expand Up @@ -328,13 +327,22 @@ See the [DateTime Guide](../guides/datetime-migrations.md) for more information

---


## Custom types

Any Dart type can be stored in the database by converting it to one of the built-in types.

Define a class which extends `TypeConverter` and implement the `toSql` and `fromSql` methods to convert between the Dart type and the type stored in the database.
Use the `.map()` method on the column to apply a `TypeConverter` to a column.

Apply the converter to a column using the `.map()` method on the column.
To create a custom type converter:

1. Define a class that extends `TypeConverter<D, S>`, where:
- `D` is the Dart type you want to use in your code
- `S` is the SQL type that will be stored in the database
2. Implement the `toSql(D value)` and `fromSql(S fromDb)` methods
3. Mix in `JsonTypeConverter<D, S>` to enable serialization to/from JSON (Optional).
- This is optional, but recommended for dataclasses.
- Use the same generic parameters as the `TypeConverter<D, S>` class.

#### Example:

Expand All @@ -348,6 +356,9 @@ Apply the converter to a column using the `.map()` method on the column.
In this case, we are storing `Duration`.
2. Built-in type we are converting to.
In this case, we are converting `Duration` to `int`.
3. Mix in `JsonTypeConverter<Duration, int>` so that we can serialize dataclasses to/from JSON.

Apply the converter to a column using the `.map()` method on the column.

{{ load_snippet('apply_converter','lib/snippets/dart_api/tables.dart.excerpt.json') }}

Expand All @@ -361,9 +372,43 @@ Now we can use the `Duration` type as if it were a built-in type.

Consider using a package like `equatable`, `freezed` or `dart_mappable` to create classes which implement this automatically.

### JSON conversion
??? note "Different Types for JSON and SQL Serialization"

If you would like to convert to a different type for JSON and SQL serialization, use `JsonTypeConverter2` instead of `JsonTypeConverter`.

**Example:**

A common use case is to store a `Preferences` object as a JSON string in the database, but represent it as a `Map<String, Object?>` in JSON.

??? example "`Preferences` Class"

{{ load_snippet('jsonserializable_type','lib/snippets/dart_api/tables.dart.excerpt.json') }}

Drift offers a convenient way to store JSON serializable types using `TypeConverter.json()`.
<div class="annotate" markdown>
{{ load_snippet('custom_json_converter','lib/snippets/dart_api/tables.dart.excerpt.json', indent=4) }}
</div>

This is how the above example looks like in JSON:

**Before:**

```json
{
"preferences": "{\"isDarkMode\": true, \"language\": \"en\"}"
}
```

**After:**

```json
{
"preferences": {"isDarkMode": true, "language": "en"}
}
```

### JSON types

Drift offers a convenient way to store JSON serializable objects using `TypeConverter.json()`.

**Example:**

Expand All @@ -373,7 +418,7 @@ Drift offers a convenient way to store JSON serializable types using `TypeConver

{{ load_snippet('jsonserializable_type','lib/snippets/dart_api/tables.dart.excerpt.json') }}


---

### Enums

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/faq.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---

title: Frequently asked questions
title: FAQ

---

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/migrating_to_drift.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---

title: Migrating to drift
title: Migrate to Drift
description: Resources on how to migrate to drift from other database packages.

---
Expand Down
Empty file.
Loading

0 comments on commit e7488f9

Please sign in to comment.