Skip to content

Commit

Permalink
Improvement is Nullable primary keys. Added method "Store"
Browse files Browse the repository at this point in the history
  • Loading branch information
danieleteti committed Feb 3, 2020
1 parent 42cf51b commit 4a78322
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 23 deletions.
107 changes: 88 additions & 19 deletions sources/MVCFramework.ActiveRecord.pas
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,10 @@ TMVCActiveRecord = class
constructor Create; overload; virtual;
destructor Destroy; override;
procedure EnsureConnection;
/// <summary>
/// Executes an Insert or an Update if primary key is defined or not
/// </summary>
procedure Store;
function CheckAction(const aEntityAction: TMVCEntityAction; const aRaiseException: Boolean = True): Boolean;
procedure Insert;
function GetMapping: TMVCFieldsMapping;
Expand All @@ -241,6 +245,7 @@ TMVCActiveRecord = class
procedure LoadByDataset(const aDataSet: TDataSet; const aOptions: TMVCActiveRecordLoadOptions = []);
procedure SetPK(const aValue: TValue);
function GetPK: TValue;
function TryGetPKValue(var Value: TValue; out IsNullableType: Boolean): Boolean;
[MVCDoNotSerialize]
property PrimaryKeyIsAutogenerated: Boolean read GetPrimaryKeyIsAutogenerated write SetPrimaryKeyIsAutogenerated;
class function GetByPK(const aClass: TMVCActiveRecordClass; const aValue: int64;
Expand Down Expand Up @@ -1100,26 +1105,15 @@ class function TMVCActiveRecordHelper.GetOneByWhere<T>(const SQLWhere: string; c
end;

function TMVCActiveRecord.GetPK: TValue;
var
lIsNullableType: Boolean;
begin
if fPrimaryKeyFieldName.IsEmpty then
raise Exception.Create('No primary key defined');
Result := fPrimaryKey.GetValue(Self);
if Result.Kind = tkRecord then
begin
if Result.IsType<NullableInt32>() then
Result := Result.AsType<NullableInt32>().Value
else if Result.IsType<NullableInt64>() then
Result := Result.AsType<NullableInt64>().Value
else if Result.IsType<NullableUInt32>() then
Result := Result.AsType<NullableUInt32>().Value
else if Result.IsType<NullableUInt64>() then
Result := Result.AsType<NullableUInt64>().Value
else if Result.IsType<NullableInt16>() then
Result := Result.AsType<NullableInt16>().Value
else if Result.IsType<NullableUInt16>() then
Result := Result.AsType<NullableUInt16>().Value
else
raise EMVCActiveRecord.Create('Invalid primary key type');
if not TryGetPKValue(Result, lIsNullableType) then
begin
if not lIsNullableType then
begin
raise EMVCActiveRecord.Create('Primary key not available');
end;
end;
end;

Expand Down Expand Up @@ -2280,6 +2274,27 @@ function TMVCActiveRecord.SQLGenerator: TMVCSQLGenerator;
Result := fSQLGenerator;
end;

procedure TMVCActiveRecord.Store;
var
lValue: TValue;
lRes: Boolean;
lIsNullableType: Boolean;
begin
lRes := TryGetPKValue(lValue, lIsNullableType);
if not lIsNullableType then
begin
raise EMVCActiveRecord.Create('Store can be used only with nullable PKs [HINT] Use NullableInt64 as PK');
end;
if lRes then
begin
Update;
end
else
begin
Insert;
end;
end;

function TMVCActiveRecord.TableInfo: string;
var
keyvalue: TPair<TRttiField, string>;
Expand All @@ -2289,6 +2304,60 @@ function TMVCActiveRecord.TableInfo: string;
Result := Result + sLineBreak + #9 + keyvalue.Key.Name + ' = ' + keyvalue.Value;
end;

function TMVCActiveRecord.TryGetPKValue(var Value: TValue; out IsNullableType: Boolean): Boolean;
begin
IsNullableType := false;
if fPrimaryKeyFieldName.IsEmpty then
raise Exception.Create('No primary key defined');
Value := fPrimaryKey.GetValue(Self);
if Value.Kind = tkRecord then
begin
if Value.IsType<NullableInt32>() then
begin
Result := Value.AsType<NullableInt32>().HasValue;
if Result then
Value := Value.AsType<NullableInt32>().Value;
end
else if Value.IsType<NullableInt64>() then
begin
Result := Value.AsType<NullableInt64>().HasValue;
if Result then
Value := Value.AsType<NullableInt64>().Value;
end
else if Value.IsType<NullableUInt32>() then
begin
Result := Value.AsType<NullableUInt32>().HasValue;
if Result then
Value := Value.AsType<NullableUInt32>().Value;
end
else if Value.IsType<NullableUInt64>() then
begin
Result := Value.AsType<NullableUInt64>().HasValue;
if Result then
Value := Value.AsType<NullableUInt64>().Value;
end
else if Value.IsType<NullableInt16>() then
begin
Result := Value.AsType<NullableInt16>().HasValue;
if Result then
Value := Value.AsType<NullableInt16>().Value;
end
else if Value.IsType<NullableUInt16>() then
begin
Result := Value.AsType<NullableUInt16>().HasValue;
if Result then
Value := Value.AsType<NullableUInt16>().Value;
end
else
raise EMVCActiveRecord.Create('Invalid primary key type [HINT: Use Int64 or NullableInt64, so that Store method is available too.]');
IsNullableType := True;
end
else
begin
Result := not Value.IsEmpty;
end;
end;

procedure TMVCActiveRecord.Update;
var
SQL: string;
Expand Down
35 changes: 35 additions & 0 deletions unittests/general/Several/ActiveRecordTestsU.pas
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ TTestActiveRecord = class(TObject)
[Test]
procedure TestCRUD;
[Test]
procedure TestStore;
[Test]
procedure TestLifeCycle;
[Test]
procedure TestRQL;
Expand Down Expand Up @@ -301,6 +303,39 @@ procedure TTestActiveRecord.TestRQL;
Assert.AreEqual(Int64(0), TMVCActiveRecord.Count<TCustomer>(RQL1));
end;

procedure TTestActiveRecord.TestStore;
var
lCustomer: TCustomerWithNullablePK;
lID: Integer;
begin
Assert.AreEqual(Int64(0), TMVCActiveRecord.Count<TCustomerWithNullablePK>());
lCustomer := TCustomerWithNullablePK.Create;
try
lCustomer.CompanyName := 'bit Time Professionals';
lCustomer.City := 'Rome, IT';
lCustomer.Note := 'note1';
lCustomer.Store; { pk is not set, so it should do an insert }
lID := lCustomer.ID;
Assert.AreEqual(1, lID);
finally
lCustomer.Free;
end;

lCustomer := TMVCActiveRecord.GetByPK<TCustomerWithNullablePK>(lID);
try
Assert.IsFalse(lCustomer.Code.HasValue);
Assert.IsFalse(lCustomer.Rating.HasValue);
lCustomer.Code := '1234';
lCustomer.Rating := 3;
lCustomer.Note := lCustomer.Note + 'noteupdated';
lCustomer.Store; { pk is set, so it should do an update }
Assert.AreEqual<Int64>(1, lCustomer.ID.Value);
finally
lCustomer.Free;
end;

end;

procedure TTestActiveRecord.LoadData;
var
lTasks: TArray<ITask>;
Expand Down
24 changes: 24 additions & 0 deletions unittests/general/Several/BOs.pas
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,30 @@ TCustomer = class(TMVCActiveRecord)
property Note: String read fNote write fNote;
end;

[MVCTable('customers')]
TCustomerWithNullablePK = class(TMVCActiveRecord)
private
[MVCTableField('id', [foPrimaryKey, foAutoGenerated])]
fID: NullableInt64;
[MVCTableField('code')]
fCode: NullableString;
[MVCTableField('description')]
fCompanyName: NullableString;
[MVCTableField('city')]
fCity: string;
[MVCTableField('rating')]
fRating: NullableInt32;
[MVCTableField('note')]
fNote: String;
public
property ID: NullableInt64 read fID write fID;
property Code: NullableString read fCode write fCode;
property CompanyName: NullableString read fCompanyName write fCompanyName;
property City: string read fCity write fCity;
property Rating: NullableInt32 read fRating write fRating;
property Note: String read fNote write fNote;
end;

[MVCTable('customers')]
TCustomerWithLF = class(TCustomer)
private
Expand Down
1 change: 0 additions & 1 deletion unittests/general/Several/DMVCFrameworkTests.dpr
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ uses
DUnitX.TestFramework,
FrameworkTestsU in 'FrameworkTestsU.pas',
LiveServerTestU in 'LiveServerTestU.pas',
MessagingExtensionsTestU in 'MessagingExtensionsTestU.pas',
BOs in 'BOs.pas',
TestServerControllerU in '..\TestServer\TestServerControllerU.pas',
RESTAdapterTestsU in 'RESTAdapterTestsU.pas',
Expand Down
1 change: 0 additions & 1 deletion unittests/general/Several/DMVCFrameworkTests.dproj
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@
</DelphiCompile>
<DCCReference Include="FrameworkTestsU.pas"/>
<DCCReference Include="LiveServerTestU.pas"/>
<DCCReference Include="MessagingExtensionsTestU.pas"/>
<DCCReference Include="BOs.pas"/>
<DCCReference Include="..\TestServer\TestServerControllerU.pas"/>
<DCCReference Include="RESTAdapterTestsU.pas"/>
Expand Down
44 changes: 42 additions & 2 deletions unittests/general/Several/LiveServerTestU.pas
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,9 @@ TServerTest = class(TBaseServerTest)

// test nullables
[Test]
procedure TestDeserializeNullables;
procedure TestDeserializeNullablesWithValue;
[Test]
procedure TestDeserializeNullablesWithNulls;
[Test]
procedure TestSerializeAndDeserializeNullables;

Expand Down Expand Up @@ -1240,12 +1242,50 @@ procedure TServerTest.TestResponseNoContent;
// end;
// end;

procedure TServerTest.TestDeserializeNullables;
procedure TServerTest.TestDeserializeNullablesWithNulls;
var
lRes: IRESTResponse;
lSer: TMVCJsonDataObjectsSerializer;
lNullableTest: TNullablesTest;
begin
/// nullables/getsinglewithnulls

lRes := RESTClient.doGET('/nullables/getsinglewithnulls', []);
lSer := TMVCJsonDataObjectsSerializer.Create;
try
lNullableTest := TNullablesTest.Create();
try
lSer.DeserializeObject(lRes.BodyAsString, lNullableTest);
Assert.isFalse(lNullableTest.f_int2.HasValue);
Assert.isFalse(lNullableTest.f_int4.HasValue);
Assert.isFalse(lNullableTest.f_int8.HasValue);
Assert.isFalse(lNullableTest.f_date.HasValue);
Assert.isFalse(lNullableTest.f_time.HasValue);
Assert.isFalse(lNullableTest.f_datetime.HasValue);
Assert.isFalse(lNullableTest.f_bool.HasValue);
Assert.isFalse(lNullableTest.f_float4.HasValue);
Assert.isFalse(lNullableTest.f_float8.HasValue);
Assert.isFalse(lNullableTest.f_string.HasValue);
Assert.isFalse(lNullableTest.f_currency.HasValue);
{ TODO -oDanieleT -cGeneral : Compare streams too }
// Assert.AreEqual('0123456789', lNullableTest.f_blob.Value, 0);
finally
lNullableTest.Free;
end;
finally
lSer.Free;
end;

end;

procedure TServerTest.TestDeserializeNullablesWithValue;
var
lRes: IRESTResponse;
lSer: TMVCJsonDataObjectsSerializer;
lNullableTest: TNullablesTest;
begin
/// nullables/getsinglewithnulls

lRes := RESTClient.doGET('/nullables/getsingle', []);
lSer := TMVCJsonDataObjectsSerializer.Create;
try
Expand Down
12 changes: 12 additions & 0 deletions unittests/general/TestServer/TestServerControllerU.pas
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,10 @@ TTestServerController = class(TMVCController)
[MVCPath('/nullables/getsingle')]
procedure TestSerializeNullables;

[MVCHTTPMethod([httpGET])]
[MVCPath('/nullables/getsinglewithnulls')]
procedure TestSerializeNullablesWithNulls;


// Response Objects Tests
[MVCHTTPMethod([httpPOST])]
Expand Down Expand Up @@ -608,6 +612,14 @@ procedure TTestServerController.TestSerializeNullables;
Render(lObj);
end;

procedure TTestServerController.TestSerializeNullablesWithNulls;
var
lObj: TNullablesTest;
begin
lObj := TNullablesTest.Create();
Render(lObj);
end;

procedure TTestServerController.TestStringDictionary;
var
lDict: TMVCStringDictionary;
Expand Down

0 comments on commit 4a78322

Please sign in to comment.