-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generated classes mark autogenerated fields as required - primary id keys and timestamps #107
Comments
Hi @pepie , thanks for opening an issue! So just to be clear the goal is to instantiate a Im thinking of adding a factory method called "New" to instantiate it with default values (it will be optional, configurable in the supadart.yaml) . So the real question is what are the default values, im thinking of using dartTypeDefaultNullValue() function that the from_json uses. But the thing about that function is its not "postgres default value" if you know what i mean. I think it'll be fine in the client runtime but when you try to push that object into the database it probably wont work until you change those values. I'd like your thoughts on this, ill work on it as soon as i get your feedback class Test {
final String name;
final int age;
Test({required this.name, required this.age});
factory Test.New({String name = 'John', int age = 30}) {
return Test(name: name, age: age);
}
}
void main() {
var test1 = Test.New(); // Uses default values
print('Name: ${test1.name}, Age: ${test1.age}');
var test2 = Test.New(name: 'Alice'); // Uses default age
print('Name: ${test2.name}, Age: ${test2.age}');
var test3 = Test.New(age: 25); // Uses default name
print('Name: ${test3.name}, Age: ${test3.age}');
}
// Output
// Name: John, Age: 30
// Name: Alice, Age: 30
// Name: John, Age: 25 |
Thanks @mmvergara , I haven’t saved anything with this package yet but plan to today. For a table defined as
I can instantiate the generated class using T.fromJson() and T.insert() to avoid having to initialize the id and and timestamp fields - which are supposed to be generated by the server:
It would be nice to have a simpler option like:
I think most of the work is already done in T.insert() or _generateMap(). The only change needed is to return an object instead of a map:
These two functions already have context on which fields are optional, it need only return the object instead of a json map.
To avoid guessing default values, I’d suggest leaving optional fields uninitialized and letting the database handle defaults on insert. Sending null or an empty value for UUID/timestamp fields should work, as the server will populate defaults on insert. So, to answer your question above, sending null or ' ' for the uuid / timestamp fields should be fine. Since _generateMap() and T.insert() already know which fields are optional, consider replicating _generateMap() as T.new() and return an object instead of a JSON map:
|
This is sample generation implementing the class StringTypes implements SupadartClass<StringTypes> {
final String id;
final String? colUuid;
final List<String>? colUuidArray;
final String? colCharacter;
final List<String>? colCharacterArray;
final String? colCharactervarying;
final List<String>? colCharactervaryingArray;
final String? colText;
final List<String>? colTextArray;
const StringTypes({
required this.id,
this.colUuid,
this.colUuidArray,
this.colCharacter,
this.colCharacterArray,
this.colCharactervarying,
this.colCharactervaryingArray,
this.colText,
this.colTextArray,
});
static String get table_name => 'string_types';
static String get c_id => 'id';
static String get c_colUuid => 'col_uuid';
static String get c_colUuidArray => 'col_uuid_array';
static String get c_colCharacter => 'col_character';
static String get c_colCharacterArray => 'col_character_array';
static String get c_colCharactervarying => 'col_charactervarying';
static String get c_colCharactervaryingArray => 'col_charactervarying_array';
static String get c_colText => 'col_text';
static String get c_colTextArray => 'col_text_array';
static List<StringTypes> converter(List<Map<String, dynamic>> data) {
return data.map(StringTypes.fromJson).toList();
}
static StringTypes converterSingle(Map<String, dynamic> data) {
return StringTypes.fromJson(data);
}
static Map<String, dynamic> _generateMap({
String? id,
String? colUuid,
List<String>? colUuidArray,
String? colCharacter,
List<String>? colCharacterArray,
String? colCharactervarying,
List<String>? colCharactervaryingArray,
String? colText,
List<String>? colTextArray,
}) {
return {
if (id != null) 'id': id,
if (colUuid != null) 'col_uuid': colUuid,
if (colUuidArray != null)
'col_uuid_array': colUuidArray.map((e) => e).toList(),
if (colCharacter != null) 'col_character': colCharacter,
if (colCharacterArray != null)
'col_character_array': colCharacterArray.map((e) => e).toList(),
if (colCharactervarying != null)
'col_charactervarying': colCharactervarying,
if (colCharactervaryingArray != null)
'col_charactervarying_array':
colCharactervaryingArray.map((e) => e).toList(),
if (colText != null) 'col_text': colText,
if (colTextArray != null)
'col_text_array': colTextArray.map((e) => e).toList(),
};
}
// New Method
// New Method
// New Method
static Object New({
String? id,
String? colUuid,
List<String>? colUuidArray,
String? colCharacter,
List<String>? colCharacterArray,
String? colCharactervarying,
List<String>? colCharactervaryingArray,
String? colText,
List<String>? colTextArray,
}) {
return {
if (id != null) 'id': id,
if (colUuid != null) 'col_uuid': colUuid,
if (colUuidArray != null) 'col_uuid_array': colUuidArray,
if (colCharacter != null) 'col_character': colCharacter,
if (colCharacterArray != null) 'col_character_array': colCharacterArray,
if (colCharactervarying != null)
'col_charactervarying': colCharactervarying,
if (colCharactervaryingArray != null)
'col_charactervarying_array': colCharactervaryingArray,
if (colText != null) 'col_text': colText,
if (colTextArray != null) 'col_text_array': colTextArray,
};
}
static Map<String, dynamic> insert({
String? id,
String? colUuid,
List<String>? colUuidArray,
String? colCharacter,
List<String>? colCharacterArray,
String? colCharactervarying,
List<String>? colCharactervaryingArray,
String? colText,
List<String>? colTextArray,
}) {
return _generateMap(
id: id,
colUuid: colUuid,
colUuidArray: colUuidArray,
colCharacter: colCharacter,
colCharacterArray: colCharacterArray,
colCharactervarying: colCharactervarying,
colCharactervaryingArray: colCharactervaryingArray,
colText: colText,
colTextArray: colTextArray,
);
}
static Map<String, dynamic> update({
String? id,
String? colUuid,
List<String>? colUuidArray,
String? colCharacter,
List<String>? colCharacterArray,
String? colCharactervarying,
List<String>? colCharactervaryingArray,
String? colText,
List<String>? colTextArray,
}) {
return _generateMap(
id: id,
colUuid: colUuid,
colUuidArray: colUuidArray,
colCharacter: colCharacter,
colCharacterArray: colCharacterArray,
colCharactervarying: colCharactervarying,
colCharactervaryingArray: colCharactervaryingArray,
colText: colText,
colTextArray: colTextArray,
);
}
factory StringTypes.fromJson(Map<String, dynamic> jsonn) {
return StringTypes(
id: jsonn['id'] != null ? jsonn['id'].toString() : '',
colUuid: jsonn['col_uuid'] != null ? jsonn['col_uuid'].toString() : '',
colUuidArray: jsonn['col_uuid_array'] != null
? (jsonn['col_uuid_array'] as List<dynamic>)
.map((v) => v.toString())
.toList()
: <String>[],
colCharacter: jsonn['col_character'] != null
? jsonn['col_character'].toString()
: '',
colCharacterArray: jsonn['col_character_array'] != null
? (jsonn['col_character_array'] as List<dynamic>)
.map((v) => v.toString())
.toList()
: <String>[],
colCharactervarying: jsonn['col_charactervarying'] != null
? jsonn['col_charactervarying'].toString()
: '',
colCharactervaryingArray: jsonn['col_charactervarying_array'] != null
? (jsonn['col_charactervarying_array'] as List<dynamic>)
.map((v) => v.toString())
.toList()
: <String>[],
colText: jsonn['col_text'] != null ? jsonn['col_text'].toString() : '',
colTextArray: jsonn['col_text_array'] != null
? (jsonn['col_text_array'] as List<dynamic>)
.map((v) => v.toString())
.toList()
: <String>[],
);
}
Map<String, dynamic> toJson() {
return _generateMap(
id: id,
colUuid: colUuid,
colUuidArray: colUuidArray,
colCharacter: colCharacter,
colCharacterArray: colCharacterArray,
colCharactervarying: colCharactervarying,
colCharactervaryingArray: colCharactervaryingArray,
colText: colText,
colTextArray: colTextArray,
);
}
StringTypes copyWith({
String? id,
String? colUuid,
List<String>? colUuidArray,
String? colCharacter,
List<String>? colCharacterArray,
String? colCharactervarying,
List<String>? colCharactervaryingArray,
String? colText,
List<String>? colTextArray,
}) {
return StringTypes(
id: id ?? this.id,
colUuid: colUuid ?? this.colUuid,
colUuidArray: colUuidArray ?? this.colUuidArray,
colCharacter: colCharacter ?? this.colCharacter,
colCharacterArray: colCharacterArray ?? this.colCharacterArray,
colCharactervarying: colCharactervarying ?? this.colCharactervarying,
colCharactervaryingArray:
colCharactervaryingArray ?? this.colCharactervaryingArray,
colText: colText ?? this.colText,
colTextArray: colTextArray ?? this.colTextArray,
);
}
}
does it seem good to you? |
That looks good. |
updated to 1.6.8 you can try it out now |
I did a quick test on v 1.6.8 and it looks good. It would be nice to have it return the class, similar the .fromJson() factory method. He's the updated usage example: OLD:
NEW:
|
The TypeScript code generated using the Supabase CLI wraps the existing types with an Insert type, allowing you to omit auto-generated values. Would it be possible to use that approach? |
do you mean like this? factory StringTypes.fromJson(Map<String, dynamic> jsonn) {
return StringTypes(
id: jsonn['id'] != null ? jsonn['id'].toString() : '',
...
) sorry i don't quite follow. |
@pepie I see, i should be able to change it easily. rn it returns it as an By the way, are you trying to create the class instance locally and send it to the server, letting the server populate those values? I'm not entirely sure about the use case for this new method, especially since we already have the insert method for that purpose. |
Yes. Here's a sample use case:
Output:
Create function:
Instantiating Genre locally helps me mocking or manipulating the object before sending it back to the server.
.insert() works great but it returns a JSON map. An object is easier to work with and provides better type safety. I am wondering if you need .insert() to return a JSON at all, since you can get that with .toJson().
I think .insert() is a nice wrapper, but it doesn't completely replace the need for a constructor/function that returns the object. |
I see, I didn't think about that.
it needs to return a JSON because assuming we do insert then only the required fields, then if we cast it to the Class it will fail because some So trying out the new method which is static Object New({
String? id,
String? colUuid,
List<String>? colUuidArray,
String? colCharacter,
List<String>? colCharacterArray,
String? colCharactervarying,
List<String>? colCharactervaryingArray,
String? colText,
List<String>? colTextArray,
}) {
return {
if (id != null) 'id': id,
if (colUuid != null) 'col_uuid': colUuid,
if (colUuidArray != null) 'col_uuid_array': colUuidArray,
if (colCharacter != null) 'col_character': colCharacter,
if (colCharacterArray != null) 'col_character_array': colCharacterArray,
if (colCharactervarying != null)
'col_charactervarying': colCharactervarying,
if (colCharactervaryingArray != null)
'col_charactervarying_array': colCharactervaryingArray,
if (colText != null) 'col_text': colText,
if (colTextArray != null) 'col_text_array': colTextArray,
};
} Then doing
Will throw this error
So i just realized the Best thing i could do is probably this static StringTypes New({
String? id,
String? colUuid,
List<String>? colUuidArray,
String? colCharacter,
List<String>? colCharacterArray,
String? colCharactervarying,
List<String>? colCharactervaryingArray,
String? colText,
List<String>? colTextArray,
}) {
return StringTypes.fromJson(StringTypes.insert(
id: id,
colUuid: colUuid,
colUuidArray: colUuidArray,
colCharacter: colCharacter,
colCharacterArray: colCharacterArray,
colCharactervarying: colCharactervarying,
colCharactervaryingArray: colCharactervaryingArray,
colText: colText,
));
} which is just basically what you described last time Genre g = Genre.fromJson( Genre.insert(
name: 'some name'
)); but this populates the properties with default values, which is against on what you want which is to instantiate the class, then have the server populate it. am i understanding it correctly? i maybe out of it again lol |
Yes, that's correct. I imaging populating the properties with default values would be difficult to maintain. This could be problematic in the case where we have a timestamp or an auto-incremented column, since the values would not be generated by the DB server. I'll try to complete a full insert today and let you know if what we have so far doesn't work. Some additional thoughts: Optional fields are null by default, so is it possible to simply not initialize them? And leave it to the developer to initialize them when needed? This would add the flexibility needed, I think. Imagine the following table, with auto-generated columns and one nullable column with no default value
This should produce a class like
Instantiating with
Should create an object with the following values
Now, I can work with Genre locally until I am ready to send it to the server with
The server would receive the following values
and would apply the relevant rules/constraints to the columns with null values, and return
The takeaway here, is we delegate optional field initialization to the developer. For example, I can initialize the option color field like:
|
so altering the class to be create table
public.genres (
name character varying(30) not null, <-- required & not generated => required in class
nickname character varying(30) null, <-- nullable & not generated => optional in class
-- required but auto-generated => optional in class
id uuid not null default extensions.uuid_generate_v4 (),
created_date timestamp with time zone not null default now(),
last_updated timestamp with time zone not null default now(),
) class Genre {
String name; <- only required field, everything else is optional
String? id;
String? color;
DateTime? createdDate;
DateTime? lastUpdated;
....
} But the rule of the class right now is
What you are proposing is changing this and do
am i following? I don't know how i feel about leaving database not nullable fields like I can do some tweaking for you and have an option to apply the rule you are proposing. so you can just instantiate the class directly from the constructor. just let me know.
|
Yeah, At the end of the day, I just want to instantiate the class without having to specify the timestamp field or anything that will be generated by the server. So far I've been able to do that with
The .new() addition works fine too, so far. I'll have more information by tonight. |
The types generated based on the Supabase CLI are typically defined as Tables<'table_name'> when retrieving objects from a table. However, when inserting new objects into a table, they are declared using TablesInsert<'table_name'>. Would it be challenging to distinguish between auto-generated values in this manner? I have some experience with TypeScript but understand that it differs from Dart classes. I think it might be difficult to find an alternative as well. |
The generated class
you can just do // Yes we know which one's are optional or required.
final data = Books.insert(
name: 'Learn Flutter',
);
await supabase.books.insert(data); and it will return { "name":"your_name" } which then you can use the supabase sdk to insert, await supabase.books.insert(data); are you talking about the |
Hey, I finally got a chance to test inserts and have few updates. To answer your question, I was referring to T.insert() in my previous post - the insert statement generated with the class, not .insert() from the sdk ( supabase..insert() ). I noticed today the object created with T.New() will mark ALL fields as optional. I missed that from your previous message , but I ended up going back to using T.fromJson( T.insert(...)) so I can leverage the null safety features. Ex: Given a Genre table with one a required 'name' column:
using
class Genre {
class {
create table
final event = Event.fromJson(Event.insert(...)); Future<Event?> create(Event event) async{
}
|
Things are a bit out of place Regarding the T.new()I think we should just give up this feature, it's too complicated and can even cause runtime issues in your app, If this pattern works well for you i think we should just keep it that way, unless there are more problems Genres.fromJson(Genres.insert(name: 'Test')); Regarding the converters
with no
|
Hello,
First, thank you for your work. It saves us a lot of time and effort.
Supadart will mark all required db fields as required in the generated class.
This means fields like the db primary id field and timestamps (which are required and automatically generated) will be required when instantiating an object for that table.
Would it be possible to have a constructor /factory that returns Object with id/date fields populated?
Ex:
Assume this simple Genre table:
The generated code is
This means Genre needs to be instantiated with an ID and a value for create/update date.
The expectation is to use
I can see from the docs that Object.fromJson() is available, but that means I would have to instantiate objects like
The name option is Object.insert() which returns a map. So we're looking at something like
Both of these approaches seems verbose and cumbersome.
Here is my supadart.yaml:
The text was updated successfully, but these errors were encountered: