serverpod / serverpod

Serverpod is a next-generation app and web server, explicitly built for the Flutter and Dart ecosystem.
BSD 3-Clause "New" or "Revised" License
2.56k stars 240 forks source link

`equatable` option for protocol classes #1468

Open lukehutch opened 1 year ago

lukehutch commented 1 year ago

Is your feature request related to a problem? Please describe.

By default, Dart implements reference equality. For value equality, == and hashCode have to be overridden, and take into account all the object's fields/properties.

Describe the solution you'd like

It would be great if a Serverpod protocol yaml file could support an option equatable: true that would define the == and hashCode method for all the fields in the class.

Describe alternatives you've considered

Overriding == and hashCode in your own class requires you to override the class, and manually write these methods, which is brittle to changes to the yaml file.

There is the equatable package to simplify this, but you still have to list all the properties manually.

Isakdl commented 1 year ago

I think this feature makes the most sense in conjunction with immutability: https://github.com/serverpod/serverpod/issues/1359

vlidholt commented 1 year ago

Yes, this is planned for the future! :)

lukehutch commented 1 year ago

@Isakdl maybe -- it definitely makes sense to do this with immutable classes (data classes will have auto-defined == and hashCode classes -- https://github.com/dart-lang/language/issues/314 ), but it would still be useful to be able to auto-define these even for mutable classes (albeit with the understanding that if you put an object into a Map and then change a field that affects the hashCode, it breaks, and you get to keep both pieces).

N.B. there is an Object.hashCode that will hash a whole object, but not an Object.equals...

lukehutch commented 11 months ago

Along with protocol object equality, one more thing that I keep running into the need for is making deep copies of protocol objects, e.g. myProtocolObject.copy() and myProtocolObject.copyWith(field: ...)

Isakdl commented 11 months ago

Then I can make you happy, the current copyWith implementation makes a deep copy of the entire object, including lists and maps.

lukehutch commented 11 months ago

Face-palm! How did I not notice this before? I thought I tried this before, and it must not have been implemented yet.. Sorry for the comment spam, in that case! Nice feature, thanks :-)

jason-downs commented 9 months ago

Really disappointed that this does not appear to be in the 1.2 release as I was expecting it to be included. Lack of hashable and equatable is still a major roadblock to being able to convert any of my existing projects over to serverpod. I hope it is coming soon in a minor update and we don't have to wait several months again for it to be bundled with a major new release.

vlidholt commented 9 months ago

@jason-downs, we have laid the groundwork for this feature. We are planning on adding support for const models. Unless the model can be constant, the hashing and equality should really not be implemented for the classes.

jason-downs commented 9 months ago

Great to hear that, and sorry for my negativity in my previous post, that was right when 1.2.0 was released, and since then I've been pleased with the increased frequency of the incremental version releases.

lukehutch commented 8 months ago

There's this:

https://github.com/a14n/zengen#value

it generates the == and hashCode methods (and toString) based on just an annotation.

In my opinion, if a user adds == and hashCode to a class and then the modify the field values while an object is stored in a map, which breaks something, then they get to keep both pieces. I don't think it's worth it to enforce on a user that a class is sealed.

lukehutch commented 8 months ago

I tried extending a Serverpod model class, and I get:

The generative constructor [...] is expected, but a factory was found.
Try calling a different constructor of the superclass, or making the called constructor not be a factory 

So the model classes are not extendable -- you can't just add == and hashCode by extending one of these classes.

The only options are to use composition (wrap a model object in another object) or create a class with the same fields, and manually copy over every value to your new equatable version of the same class.

These are not good options, if you need to be able to use model objects in equatable ways.

wanGiB commented 6 months ago

I really need this feature as well. I am currently fighting duplicates in my frontend code because a set couldn't perform deduplication due to lack of operator== and hashCode implementations for a non-table entity class.

lukehutch commented 6 months ago

@Isakdl even if you don't want to conform to the ==/hashCode contract, can you please at least generate an .equals(other) method?

Right now I need this for multiple classes with many fields, and my code breaks if I add a field and forget to update the custom equality-checking method.

(For large classes, it's so much boilerplate to write equality-checking code that I offload it to GitHub Copilot... but even Copilot shouldn't have to write this code!)