Skip to content

Commit

Permalink
updates
Browse files Browse the repository at this point in the history
  • Loading branch information
tshedor committed Sep 20, 2024
1 parent f743499 commit 747512e
Show file tree
Hide file tree
Showing 24 changed files with 107 additions and 35 deletions.
10 changes: 7 additions & 3 deletions docs/offline_first/offline_first_with_supabase_repository.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,14 @@ Field types of classes that `extends OfflineFirstWithSupabaseModel` will automat
```dart
class User extends OfflineFirstWithSupabaseModel {
// The foreign key is a relation to the `id` column of the Address table
@Supabase(name: 'address_id')
// Help the SQLite provider connect the association locally to the one provided from remote
@OfflineFirst(where: {'id': "data['address']['id']"})
@Supabase(foreignKey: 'address_id')
final Address address;
// If the association will be created by the app, specify
// a field that maps directly to the foreign key column
// so that Brick can notify Supabase of the association.
@Sqlite(ignore: true)
String get addressId => address.id;
}
class Address extends OfflineFirstWithSupabaseModel{
Expand Down
9 changes: 6 additions & 3 deletions docs/supabase/fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@ final String lastName;

?> By default, Brick renames fields to be snake case when translating to Supabase, but you can change this default in the `@SupabaseSerializable(fieldRename:)` annotation that [decorates models](models.md).

When the annotated field type extends the model's type, the Supabase column should be a foreign key.
!> **Do not use** `name` when annotating an association. Instead, use `foreignKey`.

### `@Supabase(foreignKey:)`

When the annotated field references a `OfflineFirstWithSupabaseModel`, a foreign key can be specified. Supabase's PostgREST API can usually determine the association without specifying the foreign key. However, [when multiple foreign keys exist](https://supabase.com/docs/guides/database/joins-and-nesting?queryGroups=language&language=dart#specifying-the-on-clause-for-joins-with-multiple-foreign-keys) to the same table, guiding Brick to use the right foreign key is required.

```dart
class User extends OfflineFirstWithSupabaseModel{
// The foreign key is a relation to the `id` column of the Address table
@Supabase(name: 'address_id')
@Supabase(foreignKey: 'address_id')
final Address address;
}
Expand Down
8 changes: 7 additions & 1 deletion example_supabase/lib/brick/adapters/pizza_adapter.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ Future<Map<String, dynamic>> _$PizzaToSupabase(Pizza instance,
'id': instance.id,
'frozen': instance.frozen,
'customer': await CustomerAdapter()
.toSupabase(instance.customer, provider: provider, repository: repository)
.toSupabase(instance.customer, provider: provider, repository: repository),
'customer_id': instance.customerId
};
}

Expand Down Expand Up @@ -65,6 +66,11 @@ class PizzaAdapter extends OfflineFirstWithSupabaseAdapter<Pizza> {
columnName: 'customer',
associationType: Customer,
associationIsNullable: false,
foreignKey: 'customer_id',
),
'customerId': const RuntimeSupabaseColumnDefinition(
association: false,
columnName: 'customer_id',
)
};
@override
Expand Down
4 changes: 1 addition & 3 deletions example_supabase/lib/brick/db/schema.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import 'package:brick_sqlite/db.dart';
part '20240920034504.migration.dart';

/// All intelligently-generated migrations from all `@Migratable` classes on disk
final migrations = <Migration>{
const Migration20240920034504(),
};
final migrations = <Migration>{const Migration20240920034504()};

/// A consumable database structure including the latest generated migration.
final schema = Schema(20240920034504, generatorVersion: 1, tables: <SchemaTable>{
Expand Down
7 changes: 7 additions & 0 deletions example_supabase/lib/brick/models/pizza.model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,15 @@ class Pizza extends OfflineFirstWithSupabaseModel {

final bool frozen;

@Supabase(foreignKey: 'customer_id')
final Customer customer;

// If the association will be created by the app, specify
// a field that maps directly to the foreign key column
// so that Brick can notify Supabase of the association.
@Sqlite(ignore: true)
String get customerId => customer.id;

Pizza({
required this.id,
required this.frozen,
Expand Down
5 changes: 2 additions & 3 deletions example_supabase/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import 'package:flutter/material.dart';
import 'package:pizza_shoppe/brick/models/pizza.model.dart';
import 'package:pizza_shoppe/brick/repository.dart';

const supabaseUrl = 'https://aqjyorrxelzrbkgjuyll.supabase.co';
const supabaseAnonKey =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFxanlvcnJ4ZWx6cmJrZ2p1eWxsIiwicm9sZSI6ImFub24iLCJpYXQiOjE2OTMxNzM3MzAsImV4cCI6MjAwODc0OTczMH0.MUsslxOyculwfEG5r_yY02pvz3-DN5j6TobL5CsR-mk';
const supabaseUrl = 'SUPABASE_URL';
const supabaseAnonKey = 'SUPABASE_ANON_KEY';

Future<void> main() async {
await Repository.initializeSupabaseAndConfigure(
Expand Down
2 changes: 2 additions & 0 deletions packages/brick_build/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Unreleased

- Add documentation to increase pub.dev score

## 3.2.1

- Revert `.getDisplayString()` change due to Flutter 3.22 being restricted to analyzer <6.4.1. `meta` is pinned to `1.12` in this version of Flutter, and `analyzer >=6.5.0`, where the change was made, requires `meta >= 1.15`. This change will eventually be re-reverted.
Expand Down
1 change: 1 addition & 0 deletions packages/brick_build/lib/src/serdes_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ abstract class SerdesGenerator<FieldAnnotation extends FieldSerializable,
return "'$name': $contents";
}

/// Generates a `repository.getAssociation` invocation
String getAssociationMethod(
DartType argType, {
bool forceNullable = false,
Expand Down
8 changes: 7 additions & 1 deletion packages/brick_offline_first_with_supabase/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,14 @@ Field types of classes that `extends OfflineFirstWithSupabaseModel` will automat
```dart
class User extends OfflineFirstWithSupabaseModel {
// The foreign key is a relation to the `id` column of the Address table
@Supabase(name: 'address_id')
@Supabase(foreignKey: 'address_id')
final Address address;
// If the association will be created by the app, specify
// a field that maps directly to the foreign key column
// so that Brick can notify Supabase of the association.
@Sqlite(ignore: true)
String get addressId => address.id;
}
class Address extends OfflineFirstWithSupabaseModel{
Expand Down
2 changes: 2 additions & 0 deletions packages/brick_offline_first_with_supabase_build/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Unreleased

- Add documentation to increase pub.dev score

## 1.0.0

- Stable release
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import 'package:build/build.dart';

final _schemaGenerator = OfflineFirstSchemaGenerator();

/// Generates migrations based off the [schemaGenerator]
class OfflineFirstMigrationBuilder extends NewMigrationBuilder<ConnectOfflineFirstWithSupabase> {
@override
final schemaGenerator = _schemaGenerator;
}

/// Generates a schema using the [schemaGenerator]
class OfflineFirstSchemaBuilder extends SchemaBuilder<ConnectOfflineFirstWithSupabase> {
@override
final schemaGenerator = _schemaGenerator;
Expand Down
9 changes: 9 additions & 0 deletions packages/brick_supabase/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
## Unreleased

## 1.0.2

- Only specify key lookup in query transformer if `RuntimeSupabaseColumnDefinition#foreignKey` is specified

## 1.0.1

- Add `@Supabase(foreignKey:)` to specify association querying
- Add `RuntimeSupabaseColumnDefinition#foreignKey` to track `@Supabase(foreignKey:)` values

## 1.0.0

- Stable release
Expand Down
6 changes: 5 additions & 1 deletion packages/brick_supabase/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,18 @@ Supabase keys can be renamed per field. This will override the default set by `S
final String lastName;
```

**Do not use** `name` when annotating an association. Instead, use `foreignKey`.

:bulb: By default, Brick renames fields to be snake case when translating to Supabase, but you can change this default in the `@SupabaseSerializable(fieldRename:)` annotation that [decorates models](models.md).

### `@Supabase(foreignKey:)`

When the annotated field type extends the model's type, the Supabase column should be a foreign key.

```dart
class User extends OfflineFirstWithSupabaseModel{
// The foreign key is a relation to the `id` column of the Address table
@Supabase(name: 'address_id')
@Supabase(foreignKey: 'address_id')
final Address address;
}
Expand Down
25 changes: 17 additions & 8 deletions packages/brick_supabase/lib/src/annotations/supabase.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@ import 'package:brick_core/field_serializable.dart';

/// An annotation used to specify how a field is serialized for a [SupabaseAdapter].
///
/// If this annotates a field type that extends this model's type, the Supabase column
/// should be a foreign key.
///
/// ```dart
/// class User extends OfflineFirstWithSupabaseModel{
/// // The foreign key is a relation to the `id` column of the Address table
/// @Supabase(name: 'address_id')
/// @Supabase(foreignKey: 'address_id')
/// final Address address;
/// }
///
Expand All @@ -33,6 +30,18 @@ class Supabase implements FieldSerializable {
@override
final bool enumAsString;

/// Specify a column for the ON clause in association queries.
/// For example, `'customer_id'` in `customer:customers!customer_id(...)`.
/// This is the model's column, not the association's column.
///
/// This field must be specified if the current model uses
/// [multiple foreign keys](https://supabase.com/docs/guides/database/joins-and-nesting?queryGroups=language&language=dart#specifying-the-on-clause-for-joins-with-multiple-foreign-keys).
///
/// The remote column type can be different than the local Dart type. For example,
/// `@Supabase(foreignKey: 'user_id')` that annotates `final User user` can be
/// a Postgres string type.
final String? foreignKey;

@override
final String? fromGenerator;

Expand All @@ -54,11 +63,10 @@ class Supabase implements FieldSerializable {
/// The key name to use when reading and writing values corresponding
/// to the annotated field.
///
/// The remote column type can be different than the local Dart type for associations.
/// For example, `@Supabase(name: 'user_id')` that annotates `final User user` can be
/// a Postgres string type.
/// **Do not use** `name` when annotating an association.
/// Instead, use [foreignKey].
///
/// If `null`, the snake case value of the field is used.
/// If `null`, the renamed value of the field is used.
@override
final String? name;

Expand All @@ -78,6 +86,7 @@ class Supabase implements FieldSerializable {
this.defaultValue,
bool? enumAsString,
this.fromGenerator,
this.foreignKey,
bool? ignore,
bool? ignoreFrom,
bool? ignoreTo,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ class QuerySupabaseTransformer<_Model extends SupabaseModel> {
final field = entry.value;
if (field.association && field.associationType != null) {
var associationOutput =
'${entry.key}:${modelDictionary.adapterFor[field.associationType!]?.supabaseTableName}!${field.columnName}';
'${entry.key}:${modelDictionary.adapterFor[field.associationType!]?.supabaseTableName}';
if (field.foreignKey != null) {
associationOutput += '!${field.foreignKey}';
}
associationOutput += '(';
final fields = destructureAssociationProperties(
modelDictionary.adapterFor[field.associationType!]?.fieldsToSupabaseColumns,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,18 @@ class RuntimeSupabaseColumnDefinition {
/// Then the [columnName] should reflect the Dart field name for deserialization.
final String columnName;

/// When specified, this value will be used in the ON clause of the association query.
/// For example, `'customer_id'` in `customer:customers!customer_id(...)`.
///
/// When left unspecified, the query will not use a foreign key.
/// (e.g. `customer:customers(...)`)
final String? foreignKey;

const RuntimeSupabaseColumnDefinition({
this.association = false,
this.associationIsNullable = false,
this.associationType,
required this.columnName,
this.foreignKey,
});
}
2 changes: 1 addition & 1 deletion packages/brick_supabase/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ homepage: https://github.com/GetDutchie/brick/tree/main/packages/brick_supabase
issue_tracker: https://github.com/GetDutchie/brick/issues
repository: https://github.com/GetDutchie/brick

version: 1.0.0
version: 1.0.2+1

environment:
sdk: ">=3.0.0 <4.0.0"
Expand Down
1 change: 1 addition & 0 deletions packages/brick_supabase/test/__mocks__.dart
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ class DemoAssociationModelAdapter extends SupabaseAdapter<DemoAssociationModel>
association: true,
associationIsNullable: false,
columnName: 'assoc_id',
foreignKey: 'assoc_id',
associationType: DemoModel,
),
'assocs': const RuntimeSupabaseColumnDefinition(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ void main() {
final transformer = _buildTransformer<DemoAssociationModel>();
expect(
transformer.selectFields,
'id,name,assoc:demos!assoc_id(id,name,age),assocs:demos!assocs_id(id,name,age)',
'id,name,assoc:demos!assoc_id(id,name,age),assocs:demos(id,name,age)',
);
});

test('association', () {
final transformer = _buildTransformer<DemoNestedAssociationModel>();
expect(
transformer.selectFields,
'id,name,nested:demo_associations!nested_id(id,name,assoc:demos!assoc_id(id,name,age),assocs:demos!assocs_id(id,name,age))',
'id,name,nested:demo_associations(id,name,assoc:demos!assoc_id(id,name,age),assocs:demos(id,name,age))',
);
});
});
Expand Down Expand Up @@ -90,7 +90,7 @@ void main() {

expect(
select.query,
'select=id,name,assoc:demos!assoc_id(id,name,age),assocs:demos!assocs_id(id,name,age)&demos.name=eq.Thomas&assoc=not.is.null',
'select=id,name,assoc:demos!assoc_id(id,name,age),assocs:demos(id,name,age)&demos.name=eq.Thomas&assoc=not.is.null',
);
});
});
Expand Down Expand Up @@ -256,7 +256,7 @@ void main() {
containsAll([
'id',
'name',
'nested:demo_associations!nested_id(id,name,assoc:demos!assoc_id(id,name,age),assocs:demos!assocs_id(id,name,age))',
'nested:demo_associations(id,name,assoc:demos!assoc_id(id,name,age),assocs:demos(id,name,age))',
]),
);
});
Expand Down
4 changes: 4 additions & 0 deletions packages/brick_supabase_generators/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## Unreleased

## 1.0.1

- Apply `@Supabase(foreignKey:)` to `RuntimeSupabaseColumnDefinition#foreignKey`

## 1.0.0

- Stable release
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:brick_supabase/brick_supabase.dart';
/// Find `@Supabase` given a field
class SupabaseAnnotationFinder extends AnnotationFinder<Supabase>
with AnnotationFinderWithFieldRename<Supabase> {
/// Model-level settings
final SupabaseSerializable? config;

SupabaseAnnotationFinder([this.config]);
Expand All @@ -33,6 +34,7 @@ class SupabaseAnnotationFinder extends AnnotationFinder<Supabase>
return Supabase(
defaultValue: obj.getField('defaultValue')!.toStringValue(),
enumAsString: obj.getField('enumAsString')!.toBoolValue() ?? Supabase.defaults.enumAsString,
foreignKey: obj.getField('foreignKey')!.toStringValue(),
fromGenerator: obj.getField('fromGenerator')!.toStringValue(),
ignore: obj.getField('ignore')!.toBoolValue() ?? Supabase.defaults.ignore,
ignoreFrom: obj.getField('ignoreFrom')!.toBoolValue() ?? Supabase.defaults.ignoreFrom,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class SupabaseSerialize extends SupabaseSerdesGenerator
''';
if (isAssociation) definition += 'associationType: ${checker.withoutNullResultType},';
if (isAssociation) definition += 'associationIsNullable: ${checker.isNullable},';
if (annotation.foreignKey != null) definition += "foreignKey: '${annotation.foreignKey}',";
definition += ')';
fieldsToColumns.add(definition);

Expand Down
4 changes: 2 additions & 2 deletions packages/brick_supabase_generators/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ homepage: https://github.com/GetDutchie/brick/tree/main/packages/brick_supabase_
issue_tracker: https://github.com/GetDutchie/brick/issues
repository: https://github.com/GetDutchie/brick

version: 1.0.0
version: 1.0.1+3

environment:
sdk: ">=3.0.0 <4.0.0"
Expand All @@ -14,7 +14,7 @@ dependencies:
brick_build: ">=3.2.0 <4.0.0"
brick_core: ">=1.2.1 <2.0.0"
brick_json_generators: ">=3.1.0 <4.0.0"
brick_supabase: ">=0.1.1+2 <2.0.0"
brick_supabase: ">=1.0.2+1 <2.0.0"
build: ">=2.0.0 <3.0.0"
dart_style: ">=2.0.0 <3.0.0"
source_gen: ">=1.2.2 <2.0.0"
Expand Down
Loading

0 comments on commit 747512e

Please sign in to comment.