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