realm / realm-dart

Realm is a mobile database: a replacement for SQLite & ORMs.
Apache License 2.0
761 stars 87 forks source link

feat: equality comparison between objects #1460

Closed tomassasovsky closed 9 months ago

tomassasovsky commented 9 months ago

Problem

I want to identify whether two RealmObjects contain the same data but don't necessarily care about them being the same instance.

How important is this improvement for you?

Dealbreaker

Feature would mainly be used with

Local Database only

nirinchev commented 9 months ago

Do you want to supply your own equals implemented or do you just want to have a built-in function that compares the contents of an object?

nielsenko commented 9 months ago

To elaborate a bit further. We do support equality comparison of managed realm objects. As long as two instances refer to the same realm object, as in they have the same primary key (implicit or explicit) and stem from the same version of the realm, then they will compare equal.

The implementation is:

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    if (other is! RealmObjectBase) return false;
    if (!isManaged || !other.isManaged) return false;

    return realmCore.objectEquals(this, other);
  }

So when you say you need equality comparison, I suspect you mean either:

  1. You want to compare different instances of unmanaged objects. Today two different instances of unmanaged objects never compares equal.
  2. You want to compare a realm object across versions. Say you freeze an object in version 1, and read the same primary key in version 2, you want to see if the object changed. Today objects from different realm versions never compare equal.
  3. Or you wan't to define you own equality concept.

@tomassasovsky Could you clarify more what your intent is?

tomassasovsky commented 9 months ago

Ah, I'm sorry I wasn't clear enough. I want to compare different instances of unmanaged objects. This is super important for unit tests, since right now I can't make sure my parsing functions are generating what they're supposed to.

nielsenko commented 9 months ago

Would comparing the unmanaged object by primary key be enough for your use-case?

Also, have you considered storing the object in a realm (ie. make it managed)? For a unit test it might be preferable to use an in-memory realm (Realm(Configuration.inMemory(...)))

tomassasovsky commented 9 months ago

No, that won't be enough because I want to make sure my models are parsing the data correctly, and that everything will work with the backend. So I do need the class fields to be considered in the comparison.

nirinchev commented 9 months ago

What's unclear to me is if it's a requirement that this works through the equals implementation or if you can use another function. Because if it's the latter, you can just write a function yourself like:

bool areFoosEqual(Foo left, Foo right) {
  return left.prop1 == right.prop2 &&
    left.prop2 == right.prop2 &&
    ...
}

Unless you really need to be the implementation of the generated equals method, you can use it in unit tests or when validating the contents of unmanaged objects.

You can also write it as an extension on Foo, making it look like an instance method:

extension FooEquality on Foo {
  bool deepEquals(Foo other) { ... }
}
tomassasovsky commented 9 months ago

How would that work with a field that's a List<$RealmObject>?

nirinchev commented 9 months ago

The same way deep equality works for any object - you'd recursively invoke their deep equals:

if (this.list.length != other.list.length) {
  return false;
}

for (var i = 0; i < this.list.length; i++) {
  final thisElement = this.list[i];
  final otherElement = other.list[i];
  if (!thisElement.deepEquals(otherElement)) {
    return false;
  }
}
tomassasovsky commented 9 months ago

I'd love to have this as a built-in feature so that this ends up being generated equals function inside the class, and deepEquals as an extension DeepEqualsT on T.

Would that be possible?

nirinchev commented 9 months ago

This is not something that we intend to do in the short/medium-term. While I agree it's a generally useful feature, it is orthogonal to the database/synchronization product we're developing. Our goal is to be compatible with 3rd party packages, but it's not feasible to integrate every functionality in a database package.