Skip to content

Commit

Permalink
Override first, last and single on RealmResults (#1267)
Browse files Browse the repository at this point in the history
* Override first, last and single on RealmResults

* Update CHANGELOG

* Add tests

* Address PR feedback
  • Loading branch information
nielsenko authored Jun 22, 2023
1 parent a6e7319 commit e389ffd
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Added support binary data type. ([#1320](https://github.com/realm/realm-dart/pull/1320))
* Extended `ClientResetError` to return the `backupFilePath` where the backup copy of the realm will be placed once the client reset process has completed. ([#1291](https://github.com/realm/realm-dart/pull/1291))
* Added `CompensatingWriteError` containing detailed error information about the writes that have been reverted by the server due to permissions or subscription view restrictions. The `Configuration.flexibleSync.syncErrorHandler` will be invoked with this error type when this error occurs ([#1291](https://github.com/realm/realm-dart/pull/1291)).
* Improve performance of elementAt, first, single and last on RealmResults ([#1261](https://github.com/realm/realm-dart/issues/1261), [#1262](https://github.com/realm/realm-dart/pull/1262), [#1267](https://github.com/realm/realm-dart/pull/1267)).

### Fixed
* The constructors of all `SyncError` types are deprecated. The sync errors will be created only internally ([#1291](https://github.com/realm/realm-dart/pull/1291)).
Expand Down
31 changes: 29 additions & 2 deletions lib/src/results.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
//
////////////////////////////////////////////////////////////////////////////////
import 'dart:async';
import 'dart:collection' as collection;
import 'dart:ffi';

import 'collections.dart';
Expand All @@ -28,7 +27,7 @@ import 'realm_object.dart';
/// added to or deleted from the Realm that match the underlying query.
///
/// {@category Realm}
class RealmResults<T extends Object?> extends collection.IterableBase<T> with RealmEntity implements Finalizable {
class RealmResults<T extends Object?> extends Iterable<T> with RealmEntity implements Finalizable {
final RealmObjectMetadata? _metadata;
final RealmResultsHandle _handle;

Expand Down Expand Up @@ -75,6 +74,34 @@ class RealmResults<T extends Object?> extends collection.IterableBase<T> with Re
@override
int get length => realmCore.getResultsCount(this);

@override
T get first {
if (length == 0) {
throw RealmStateError('No element');
}
return this[0];
}

@override
T get last {
if (length == 0) {
throw RealmStateError('No element');
}
return this[length - 1];
}

@override
T get single {
final l = length;
if (l > 1) {
throw RealmStateError('Too many elements');
}
if (l == 0) {
throw RealmStateError('No element');
}
return this[0];
}

/// Creates a frozen snapshot of this query.
RealmResults<T> freeze() {
if (isFrozen) {
Expand Down
49 changes: 49 additions & 0 deletions test/results_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -741,4 +741,53 @@ Future<void> main([List<String>? args]) async {
final items = realm.query<Product>(ids);
expect(items.length, 0);
});

test('RealmResults.first throws on empty', () async {
final config = Configuration.local([Dog.schema, Person.schema]);
final realm = getRealm(config);

final empty = Iterable<int>.empty();
// To illustrate the behavior of Iterable.single on a regular iterable
expect(() => empty.first, throws<StateError>('No element'));

expect(() => realm.all<Dog>().first, throws<StateError>('No element'));
expect(() => realm.all<Dog>().first, throws<RealmStateError>('No element'));
});

test('RealmResults.last throws on empty', () async {
final config = Configuration.local([Dog.schema, Person.schema]);
final realm = getRealm(config);

final empty = Iterable<int>.empty();
// To illustrate the behavior of Iterable.single on a regular iterable
expect(() => empty.last, throws<StateError>('No element'));

expect(() => realm.all<Dog>().last, throws<StateError>('No element'));
expect(() => realm.all<Dog>().last, throws<RealmStateError>('No element'));
});

test('RealmResult.single throws on empty', () {
final config = Configuration.local([Dog.schema, Person.schema]);
final realm = getRealm(config);

final empty = Iterable<int>.empty();
// To illustrate the behavior of Iterable.single on a regular iterable
expect(() => empty.single, throws<StateError>('No element'));

expect(() => realm.all<Dog>().single, throws<StateError>('No element'));
expect(() => realm.all<Dog>().single, throws<RealmStateError>('No element'));
});

test('RealmResult.single throws on two items', () {
final config = Configuration.local([Dog.schema, Person.schema]);
final realm = getRealm(config);

final twoItems = [1, 2];
// To illustrate the behavior of Iterable.single on a regular list
expect(() => twoItems.single, throws<StateError>('Too many elements'));

realm.write(() => realm.addAll([Dog('Fido'), Dog('Spot')]));
expect(() => realm.all<Dog>().single, throws<StateError>('Too many elements'));
expect(() => realm.all<Dog>().single, throws<RealmStateError>('Too many elements'));
});
}

0 comments on commit e389ffd

Please sign in to comment.