Using ChatGPT with Delphi and DMVCFramework
The AI revolution is reshaping our technological landscape. In 2016, Google’s CEO Sundar Pichai predicted a shift to an ‘AI-first’ world, and now we are experiencing it.
While OpenAI has made notable strides with releases like DALL-E and GPT-3.5, the AI field is witnessing continuous evolution. In a recent interview, Pichai highlighted the vast potential of AI, emphasizing its status as a ‘profound platform shift, bigger than web or mobile.’ (Yes, bigger than web or mobile!)
When discussing AI breakthroughs, Pichai emphasizes the significance of multimodality, allowing models to comprehend text, audio, and code simultaneously. This capability, coupled with advancements in benchmarks, holds promise for deeper breakthroughs in the field.
As AI continues to evolve, the world anticipates transformative changes that will define the next decade and beyond.
Have you ever used AI for a coding task?
Now, let’s dive into a practical example of AI’s capabilities using Delphi and DelphiMVCFramework, the most popular Delphi framework on Github.
Imagine you have a database table, and you want to use the simple and performant MVCActiveRecord framework to handle it. The first step is to create a mapping class that inherits from TMVCActiveRecord
.
📣 If you’re unfamiliar with what
ActiveRecord
is and how it can reduce the effort to handle your data and business logic, check Chapter 7 of DelphiMVCFramework - the official guide. The guide is available 👉here in english, 👉here in Spanish and 👉here in Brazilian Portuguese.
There’s also a specialized tool for such tasks, but in this case, I want to use ChatGPT 3.5, which is freely available, to generate the mapping class.
These kinds of LLMs use ‘prompts.’ Let’s consider a prompt as a description, along with the relevant context, about what you need from the AI engine.
Navigate to https://chat.openai.com/ and use the following prompt.
ChatGPT Prompt 👇🏼
Act as a Delphi and DelphiMVCFramework expert.
I've a database table defined as follows:
CREATE TABLE public.customers (
id int8 generated by default as identity NOT NULL,
code varchar(20) NOT NULL,
description varchar(200) NULL,
city varchar(200) NULL,
note text NULL,
rating int4 NULL
);
Write a Delphi class which uses TMVCActiveRecord to map this table to an entity.
Remember to use MVCTable attribute on the class itself while MVCTableField attribute on the fields.
Where appropriate uses the nullable types from the unit MVCFramework.Nullables.pas.
This is what ChatGPT 3.5 generated using the prompt (I didn’t modified nothing).
unit CustomerEntity;
interface
uses
MVCFramework.Serializer.Nullables,
MVCFramework.ActiveRecord,
MVCFramework.Nullables;
type
[MVCTable('customers')]
TCustomer = class(TMVCActiveRecord)
private
[MVCTableField('id', [foPrimaryKey, foAutoGenerated])]
FID: NullableInt64;
[MVCTableField('code')]
FCode: NullableString;
[MVCTableField('description')]
FDescription: NullableString;
[MVCTableField('city')]
FCity: NullableString;
[MVCTableField('note')]
FNote: NullableString;
[MVCTableField('rating')]
FRating: NullableInteger;
public
property ID: NullableInt64 read FID write FID;
property Code: NullableString read FCode write FCode;
property Description: NullableString read FDescription write FDescription;
property City: NullableString read FCity write FCity;
property Note: NullableString read FNote write FNote;
property Rating: NullableInteger read FRating write FRating;
end;
implementation
end.
The code doesn’t compile but after fixing NullableInteger
to NullableInt32
and change some unit names, it works correctly. Let’s go on. The next thing we try to ask to ChatGPT is to generate a controller to handle the previously created entity.
ChatGPT Prompt 👇🏼
OK, good. Now generated a DelphiMVCFramework controller which uses this class and provides the common CRUD functionalities. Please use the following controller as example.
unit CustomersControllerU;
interface
uses
MVCFramework,
MVCFramework.ActiveRecord,
MVCFramework.Commons,
System.Generics.Collections,
EntitiesU;
type
[MVCPath('/api/customers')]
TCustomersController = class(TMVCController)
public
[MVCPath]
[MVCHTTPMethods([httpGET])]
procedure GetCustomers([MVCFromQueryString('rql','')] RQLFilter: String);
[MVCPath('/($ID)')]
[MVCHTTPMethods([httpGET])]
procedure GetCustomerByID(const ID: Integer);
[MVCPath('/($ID)')]
[MVCHTTPMethods([httpPUT])]
procedure UpdateCustomerByID(const [MVCFromBody] Customer: TCustomer; const ID: Integer);
[MVCPath]
[MVCHTTPMethods([httpPOST])]
procedure CreateCustomer([MVCFromBody] const Customer: TCustomer);
[MVCPath('/_bulk')]
[MVCHTTPMethods([httpPOST])]
procedure BulkCreateCustomers([MVCFromBody] const Customers: TObjectList<TCustomer>);
end;
implementation
uses
System.SysUtils,
FireDAC.Comp.Client,
FireDAC.Stan.Param,
MVCFramework.Logger,
MVCFramework.Serializer.Commons,
JsonDataObjects;
{ TCustomersController }
procedure TCustomersController.CreateCustomer(const Customer: TCustomer);
begin
Customer.Insert;
Render201Created('/api/customers/' + Customer.ID.Value.ToString);
end;
procedure TCustomersController.GetCustomerByID(const ID: Integer);
begin
Render(ObjectDict().Add('data', TMVCActiveRecord.GetByPK<TCustomer>(ID)));
end;
procedure TCustomersController.GetCustomers([MVCFromQueryString('rql','')] RQLFilter: String);
begin
if RQLFilter.IsEmpty then
Render(ObjectDict().Add('data', TMVCActiveRecord.All<TCustomer>))
else
Render(ObjectDict().Add('data', TMVCActiveRecord.SelectRQL<TCustomer>(RQLFilter, 1000)));
end;
procedure TCustomersController.UpdateCustomerByID(const Customer: TCustomer; const ID: Integer);
begin
Customer.ID := ID;
Customer.Update();
end;
procedure TCustomersController.BulkCreateCustomers(const Customers: TObjectList<TCustomer>);
begin
TMVCActiveRecord.CurrentConnection.StartTransaction;
try
for var lCustomer in Customers do
begin
lCustomer.Insert;
end;
TMVCActiveRecord.CurrentConnection.Commit;
Render201Created();
except
TMVCActiveRecord.CurrentConnection.Rollback;
raise;
end;
end;
end.
This is what we get.
unit CustomerController;
interface
uses
MVCFramework,
MVCFramework.ActiveRecord,
MVCFramework.Commons,
System.Generics.Collections,
CustomerEntity;
type
[MVCPath('/api/customers')]
TCustomerController = class(TMVCController)
public
[MVCPath]
[MVCHTTPMethods([httpGET])]
procedure GetCustomers([MVCFromQueryString('rql', '')] RQLFilter: string);
[MVCPath('/($ID)')]
[MVCHTTPMethods([httpGET])]
procedure GetCustomerByID(const ID: Integer);
[MVCPath('/($ID)')]
[MVCHTTPMethods([httpPUT])]
procedure UpdateCustomerByID(const [MVCFromBody] Customer: TCustomer; const ID: Integer);
[MVCPath]
[MVCHTTPMethods([httpPOST])]
procedure CreateCustomer(const [MVCFromBody] Customer: TCustomer);
[MVCPath('/_bulk')]
[MVCHTTPMethods([httpPOST])]
procedure BulkCreateCustomers(const [MVCFromBody] Customers: TObjectList<TCustomer>);
end;
implementation
uses
System.SysUtils,
MVCFramework.Logger,
MVCFramework.Serializer.Commons,
MVCFramework.RQL.AST2MVC,
MVCFramework.RQL.Parser,
MVCFramework.RQL.Parser.Exceptions;
{ TCustomerController }
procedure TCustomerController.CreateCustomer(const Customer: TCustomer);
begin
Customer.Insert;
Render201Created('/api/customers/' + Customer.ID.Value.ToString);
end;
procedure TCustomerController.GetCustomerByID(const ID: Integer);
begin
Render(ObjectDict().Add('data', TMVCActiveRecord.GetByPK<TCustomer>(ID)));
end;
procedure TCustomerController.GetCustomers([MVCFromQueryString('rql', '')] RQLFilter: string);
begin
try
if RQLFilter.IsEmpty then
Render(ObjectDict().Add('data', TMVCActiveRecord.All<TCustomer>))
else
Render(ObjectDict().Add('data', TMVCActiveRecord.SelectRQL<TCustomer>(RQLFilter, 1000)));
except
on E: ERQLException do
Render500(JsonObject
.T('error', 'RQL Error')
.A('details', E.Message));
end;
end;
procedure TCustomerController.UpdateCustomerByID(const Customer: TCustomer; const ID: Integer);
begin
Customer.ID := ID;
Customer.Update;
Render204NoContent;
end;
procedure TCustomerController.BulkCreateCustomers(const Customers: TObjectList<TCustomer>);
begin
TMVCActiveRecord.CurrentConnection.StartTransaction;
try
for var lCustomer in Customers do
begin
lCustomer.Insert;
end;
TMVCActiveRecord.CurrentConnection.Commit;
Render201Created;
except
TMVCActiveRecord.CurrentConnection.Rollback;
raise;
end;
end;
end.
In this case we still get not compilable code but all the main things are there. Just a small fix and we get a plain controller ready to be enhanced with our business logic.
That’s all for this blog post.
This is just the start of the journey guys…
DT
Comments
comments powered by Disqus