Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mixin DestructiveLocalSyncFromRemoteMixin<T extends OfflineFirstModel>
/// are destroyed. Further, when `true`, all values from other parameters except [query] are ignored.
@override
Future<List<TModel>> get<TModel extends T>({
OfflineFirstGetPolicy? associationPolicy,
bool forceLocalSyncFromRemote = false,
OfflineFirstGetPolicy policy = OfflineFirstGetPolicy.awaitRemoteWhenNoneExist,
Query? query,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,9 @@ abstract class OfflineFirstRepository<TRepositoryModel extends OfflineFirstModel
/// is ignorable (e.g. eager loading). Defaults to `false`.
@override
Future<List<TModel>> get<TModel extends TRepositoryModel>({
OfflineFirstGetPolicy policy = OfflineFirstGetPolicy.awaitRemoteWhenNoneExist,
OfflineFirstGetPolicy? associationPolicy,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new parameter requires doc comments on this get method

OfflineFirstGetPolicy policy =
OfflineFirstGetPolicy.awaitRemoteWhenNoneExist,
Query? query,
bool seedOnly = false,
}) async {
Expand All @@ -189,7 +191,7 @@ abstract class OfflineFirstRepository<TRepositoryModel extends OfflineFirstModel
final alwaysHydrate = policy == OfflineFirstGetPolicy.alwaysHydrate;

try {
_latestGetPolicy = policy;
_latestGetPolicy = associationPolicy ?? policy;

if (memoryCacheProvider.canFind<TModel>(query) && !requireRemote) {
final memoryCacheResults = memoryCacheProvider.get<TModel>(query: query, repository: this);
Expand Down Expand Up @@ -244,6 +246,7 @@ abstract class OfflineFirstRepository<TRepositoryModel extends OfflineFirstModel
/// can be expensive for large datasets, making deserialization a significant hit when the result
/// is ignorable (e.g. eager loading). Defaults to `false`.
Future<List<TModel>> getBatched<TModel extends TRepositoryModel>({
OfflineFirstGetPolicy? associationPolicy,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here - this will need documentation

int batchSize = 50,
OfflineFirstGetPolicy policy = OfflineFirstGetPolicy.awaitRemoteWhenNoneExist,
Query? query,
Expand All @@ -267,6 +270,7 @@ abstract class OfflineFirstRepository<TRepositoryModel extends OfflineFirstModel
final results = await get<TModel>(
query: recursiveQuery,
policy: policy,
associationPolicy: associationPolicy,
seedOnly: seedOnly,
);
total.addAll(results);
Expand Down Expand Up @@ -362,6 +366,7 @@ abstract class OfflineFirstRepository<TRepositoryModel extends OfflineFirstModel
/// with the assignment/subscription `.cancel()`'d as soon as the data is no longer needed.
/// The stream will not close naturally.
Stream<List<TModel>> subscribe<TModel extends TRepositoryModel>({
OfflineFirstGetPolicy? associationPolicy,
OfflineFirstGetPolicy policy = OfflineFirstGetPolicy.localOnly,
Query? query,
}) {
Expand All @@ -384,7 +389,10 @@ abstract class OfflineFirstRepository<TRepositoryModel extends OfflineFirstModel
subscriptions[TModel]?[query] = controller;

// ignore: discarded_futures
get<TModel>(query: query, policy: policy).then(
get<TModel>(
query: query,
policy: policy,
associationPolicy: associationPolicy,).then(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it's missing a formatting check

(results) {
if (!controller.isClosed) controller.add(results);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ part 'horse.dart';
part 'horse_adapter.dart';
part 'mounty.dart';
part 'mounty_adapter.dart';
part 'owner.dart';
part 'owner_adapter.dart';

/// The exact same as [Mounty], except this class is tracked by the Memory Cache Provider
/// while [Mounty] is not.
Expand Down Expand Up @@ -40,6 +42,13 @@ class DemoModelMigration extends Migration {
onDeleteCascade: true,
),
InsertColumn('name', Column.varchar, onTable: 'Horse'),
InsertTable('Owner'),
InsertColumn('name', Column.varchar, onTable: 'Owner'),
InsertForeignKey(
'Horse',
'Owner',
foreignKeyColumn: '_brick_owner_id',
),
],
down: const <MigrationCommand>[],
);
Expand Down Expand Up @@ -94,6 +103,7 @@ final Map<Type, TestAdapter<TestModel>> testMappings = {
Horse: HorseAdapter(),
MemoryDemoModel: MountyAdapter(),
Mounty: MountyAdapter(),
Owner: OwnerAdapter(),
};
final testModelDictionary = TestModelDictionary(testMappings);

Expand All @@ -102,5 +112,6 @@ final Map<Type, SqliteAdapter<SqliteModel>> sqliteMappings = {
Horse: HorseAdapter(),
MemoryDemoModel: MountyAdapter(),
Mounty: MountyAdapter(),
Owner: OwnerAdapter(),
};
final sqliteModelDictionary = SqliteModelDictionary(sqliteMappings);
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ class Horse extends OfflineFirstWithTestModel {

final List<Mounty> mounties;

Horse({
this.name,
this.mounties = const <Mounty>[],
});
final Owner? owner;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding another model, why can't the existing Mounty be used?


Horse({this.name, this.mounties = const <Mounty>[], this.owner});
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ Future<Horse> _$HorseFromTest(
}) async {
return Horse(
name: data['name'] as String?,
mounties: await Future.wait<Mounty>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this removed?

mounties: await Future.wait(
data['mounties']
?.map((d) => MountyAdapter().fromTest(d, provider: provider, repository: repository))
?.map<Future<Mounty>>((d) => MountyAdapter()
.fromTest(d, provider: provider, repository: repository),)
.toList() ??
[],
),
owner: data['owner'] == null
? null
: await OwnerAdapter().fromTest(data['owner'],
provider: provider, repository: repository,),
);
}

Expand All @@ -25,9 +30,14 @@ Future<Map<String, dynamic>> _$HorseToTest(
'name': instance.name,
'mounties': await Future.wait<Map<String, dynamic>>(
instance.mounties
.map((s) => MountyAdapter().toTest(s, provider: provider, repository: repository))
.map((s) => MountyAdapter()
.toTest(s, provider: provider, repository: repository),)
.toList(),
),
'owner': instance.owner == null
? null
: await OwnerAdapter().toTest(instance.owner!,
provider: provider, repository: repository,),
};
}

Expand All @@ -36,6 +46,13 @@ Future<Horse> _$HorseFromSqlite(
required SqliteProvider provider,
OfflineFirstWithTestRepository? repository,
}) async {
final ownerId = data['_brick_owner_id'];
final owner = ownerId == null
? null
: await repository!.getAssociation<Owner>(
Query.where('primaryKey', ownerId, limit1: true),
);

return Horse(
name: data['name'] == null ? null : data['name'] as String?,
mounties: (await provider.rawQuery(
Expand All @@ -54,6 +71,7 @@ Future<Horse> _$HorseFromSqlite(
);
}))
.toList(),
owner: owner?.firstOrNull,
)..primaryKey = data['_brick_id'] as int;
}

Expand All @@ -62,7 +80,16 @@ Future<Map<String, dynamic>> _$HorseToSqlite(
required SqliteProvider provider,
OfflineFirstWithTestRepository? repository,
}) async {
return {'name': instance.name};
return {
'name': instance.name,
'_brick_owner_id': (instance.owner == null)
? null
: instance.owner?.primaryKey ??
await provider.upsert<Owner>(
instance.owner!,
repository: repository,
),
};
}

/// Construct a [Horse]
Expand All @@ -85,6 +112,11 @@ class HorseAdapter extends OfflineFirstWithTestAdapter<Horse> {
iterable: true,
type: Mounty,
),
'owner': const RuntimeSqliteColumnDefinition(
association: true,
columnName: '_brick_owner_id',
type: Owner,
),
};
@override
Future<int?> primaryKeyByUniqueColumns(Horse instance, DatabaseExecutor executor) async =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
part of '__mocks__.dart';

class Owner extends OfflineFirstWithTestModel {
final String? name;

Owner({
this.name,
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
part of '__mocks__.dart';

Future<Owner> _$OwnerFromTest(
Map<String, dynamic> data, {
required TestProvider provider,
OfflineFirstWithTestRepository? repository,
}) async {
return Owner(
name: data['name'] as String?,
);
}

Future<Map<String, dynamic>> _$OwnerToTest(
Owner instance, {
required TestProvider provider,
OfflineFirstWithTestRepository? repository,
}) async {
return {
'name': instance.name,
};
}

Future<Owner> _$OwnerFromSqlite(
Map<String, dynamic> data, {
required SqliteProvider provider,
OfflineFirstWithTestRepository? repository,
}) async {
return Owner(
name: data['name'] == null ? null : data['name'] as String?,
)..primaryKey = data['_brick_id'] as int;
}

Future<Map<String, dynamic>> _$OwnerToSqlite(
Owner instance, {
required SqliteProvider provider,
OfflineFirstWithTestRepository? repository,
}) async {
return {'name': instance.name};
}

/// Construct a [Owner]
class OwnerAdapter extends OfflineFirstWithTestAdapter<Owner> {
OwnerAdapter();

@override
final fieldsToSqliteColumns = <String, RuntimeSqliteColumnDefinition>{
'primaryKey': const RuntimeSqliteColumnDefinition(
columnName: '_brick_id',
type: int,
),
'name': const RuntimeSqliteColumnDefinition(
columnName: 'name',
type: String,
),
};
@override
Future<int?> primaryKeyByUniqueColumns(
Owner instance, DatabaseExecutor executor,) async =>
instance.primaryKey;
@override
final tableName = 'Owner';

@override
Future<Owner> fromTest(
Map<String, dynamic> input, {
required TestProvider provider,
covariant OfflineFirstWithTestRepository? repository,
}) async =>
await _$OwnerFromTest(input, provider: provider, repository: repository);
@override
Future<Map<String, dynamic>> toTest(
Owner input, {
required TestProvider provider,
covariant OfflineFirstWithTestRepository? repository,
}) async =>
await _$OwnerToTest(input, provider: provider, repository: repository);
@override
Future<Owner> fromSqlite(
Map<String, dynamic> input, {
required SqliteProvider<SqliteModel> provider,
covariant OfflineFirstWithTestRepository? repository,
}) async =>
await _$OwnerFromSqlite(input,
provider: provider, repository: repository,);
@override
Future<Map<String, dynamic>> toSqlite(
Owner input, {
required SqliteProvider<SqliteModel> provider,
covariant OfflineFirstWithTestRepository? repository,
}) async =>
await _$OwnerToSqlite(input, provider: provider, repository: repository);
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ class TestProvider extends Provider<TestModel> {
methodsCalled.add('get');
final adapter = modelDictionary.adapterFor[T]!;
final data = [
{'name': 'SqliteName'},
{
'name': 'SqliteName',
'mounties': [
{'name': 'SqliteName'},
],
'owner': { 'name': 'SqliteName'},
}
];
final results = data
.map((e) => adapter.fromTest(e, provider: this, repository: repository))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ void main() {
// The TestProvider does not have unique keys, so Brick can't compare based on the name,
// giving the appearance of two distinct records
expect(fetchAgain, hasLength(2));
expect((TestRepository().remoteProvider as TestProvider).methodsCalled, hasLength(2));
expect((TestRepository().remoteProvider as TestProvider).methodsCalled, hasLength(5));
} finally {
TestRepository().memoryCacheProvider.managedModelTypes.remove(Horse);
}
Expand All @@ -121,6 +121,20 @@ void main() {
});
});

test('associationPolicy overrides policy for associations', () async {
await TestRepository().get<Horse>(policy: OfflineFirstGetPolicy.awaitRemote);

expect((TestRepository().remoteProvider as TestProvider).methodsCalled, hasLength(2));

await TestRepository().get<Horse>(
associationPolicy: OfflineFirstGetPolicy.localOnly,
policy: OfflineFirstGetPolicy.awaitRemote,
);

// Get is only invoked once if the associationPolicy is localOnly
expect((TestRepository().remoteProvider as TestProvider).methodsCalled, hasLength(3));
});

test(
'#hydrateSqlite / #get requireRest:true',
() async {
Expand Down
Loading
Loading