Example Business Object (Customer)

Marcelo Via Giglio
Posts: 1033
Joined: Fri Oct 07, 2005 3:33 pm
Location: Cochabamba - Bolivia

Re: Example Business Object (Customer)

Post by Marcelo Via Giglio »

Hola,

maybe we can see to far and think in ORM https://en.wikipedia.org/wiki/Object-relational_mapping

an idea only

regards

Marcelo Vía
User avatar
Silvio.Falconi
Posts: 4956
Joined: Thu Oct 18, 2012 7:17 pm

Re: Example Business Object (Customer)

Post by Silvio.Falconi »

No I wish See how Make the same sample of Jeff Bott ( made with tdata and trecord)

but with Tdatabase and tdataRow of Fivetech
I use : FiveWin for Harbour August 2020 (Revision) - Harbour 3.2.0dev (r1712141320) - Bcc7.30 - xMate ver. 1.15.3 - PellesC
User avatar
nageswaragunupudi
Posts: 8017
Joined: Sun Nov 19, 2006 5:22 am
Location: India
Contact:

Re: Example Business Object (Customer)

Post by nageswaragunupudi »

This is a similar sample of TEmployee class derived from TDatarow class. This class works with not only TDatabase, but also directly with DBF, FWMariaDB, TDolphin and ADO ( with any RDMS ) also. To save the time of creation of base table on all these platforms, I have used "customer" table which is already present in all sample dbs of FWH.

Copy this program to \fwh\samples folder and use buildh.bat or buildx.bat to build and run the test program. You may try with DBF, TDatabase, FWMariaDB, ADO and Dolphin.

Code: Select all

#include "fivewin.ch"

REQUEST DBFCDX

#ifndef __XHARBOUR__
EXTERNAL TDOLPHINQRY
#endif

static oCn

//----------------------------------------------------------------------------//

function Main()

   local aSrc  := { "DBF", "TDATABASE", "FWMARIADB", "ADO" }
   local nSrc
   local oEmp

#ifndef __XHARBOUR__
   AADD( aSrc, "DOLPHIN" )
#endif

   SET DATE ITALIAN
   SET CENTURY ON
   RDDSETDEFAULT( "DBFCDX" )

   nSrc  := Alert( "Select Data Source Type", aSrc )
   if nSrc == 0
      return nil
   endif

   if aSrc[ nSrc ] == "FWMARIADB"
      oCn   := FW_DemoDB()
   elseif aSrc[ nSrc ] == "ADO"
      oCn   := FW_OpenAdoConnection( "xbrtest.mdb", .t. )
   elseif aSrc[ nSrc ] == "DOLPHIN"
      oCn   := FW_DemoDB( "DLP" )
   endif

   TEmployee():cDbType := Upper( aSrc[ nSrc ] )

   oEmp  := TEmployee():New( 101 )

   ? oEmp:cDbType
   ? oEmp:id, oEmp:FullName, oEmp:City
   ? oEmp:YearsWorked, oEmp:Gratuity
   ? oEmp:Salary
   oEmp:PayIncrease( 2000 )
   ? oEmp:Salary

   oEmp:Edit()
   oEmp:Close()

   if !Empty( oCn )
      if aSrc[ nSrc ] $ "FWMARIADB,DOLPHIN"
         oCn:End()
      endif
   endif

   ? "Done"

return nil

//----------------------------------------------------------------------------//

CLASS TEmployee FROM TDataRow

   CLASSDATA cDbType   AS CHARACTER INIT "DBF"
   CLASSDATA employees
   CLASSDATA nCount     INIT 0

   DATA oRs

   METHOD New( nID ) CONSTRUCTOR

   ACCESS FullName   INLINE Trim( ::First ) + ", " + Trim( ::Last )
   ACCESS YearsWorked INLINE YEAR( Date() ) - YEAR( ::HireDate ) - ;
                      If( SUBSTR( DTOS( DATE() ), 5 ) < SUBSTR( DTOS( ::HireDate ), 5 ), 1, 0 )

   METHOD PayIncrease( n ) INLINE ( ::Salary += n, ::Save(), ::Salary )

   ACCESS Gratuity INLINE ROUND( ::Salary * ::YearsWorked / 2, 0 )

   METHOD OpenTable() //used only by DBF and TDatabase

   METHOD Close()

ENDCLASS

//----------------------------------------------------------------------------//

METHOD New( nID ) CLASS TEmployee

   local cSql

   if ::cDbType $ "DBF,TDATABASE"
      ::OpenTable()
      if ::cDbType == "DBF"
         ( ::employees )->( DBSEEK( nID ) )
      else
         ::employees:Seek( nID )
      endif
   else
      cSql  := "SELECT * FROM customer WHERE ID = " + cValToChar( nID )
      ::oRs := If( ::cDbType == "ADO", FW_OpenRecordSet( oCn, cSql ), oCn:Query( cSql ) )
   endif

   ::Super:New( IfNil( ::oRs, ::employees ) )

   ::lNavigate := .f.

return Self

//----------------------------------------------------------------------------//

METHOD OpenTable() CLASS TEmployee

   if !File( "CUSTOMER.CDX" )
      USE CUSTOMER EXCLUSIVE
      FW_CdxCreate()
      CLOSE DATA
   endif

   if ::employees == nil
      if ::cDbType == "DBF"
         USE CUSTOMER NEW SHARED ALIAS ( ::employees := cGetNewAlias( "CUST" ) )
         SET ORDER TO TAG ID
         GO TOP
      elseif ::cDbType == "TDATABASE"
         ::employees := TDataBase():Open( nil, "CUSTOMER" )
         WITH OBJECT ::employees
            :SetOrder( "ID" )
            :GoTop()
         END
      endif
      ::nCount := 0
   endif

return nil

//----------------------------------------------------------------------------//

METHOD Close() CLASS TEmployee

   if ::cDbType $ "DBF,TDATABASE" .and. !Empty( ::employees )
      ::nCount--
      if ::nCount < 1
         if ::cDbType == "DBF"
            ( ::employees )->( DBCLOSEAREA() )
         else
            ::employees:End()
         endif
         ::employees := nil
         ::nCount    := 0
      endif
   elseif !Empty( ::oRs )
      if ::cDbType $ "FWMARIADB,ADO"
         ::oRs:Close()
      elseif ::cDbType == "DOLPHIN"
         ::oRs:End()
      endif
      ::oRs := nil
   endif

return nil

//----------------------------------------------------------------------------//
 
Regards

G. N. Rao.
Hyderabad, India
User avatar
nageswaragunupudi
Posts: 8017
Joined: Sun Nov 19, 2006 5:22 am
Location: India
Contact:

Re: Example Business Object (Customer)

Post by nageswaragunupudi »

Silvio.Falconi wrote:No I wish See how Make the same sample of Jeff Bott ( made with tdata and trecord)

but with Tdatabase and tdataRow of Fivetech
Same sample:
First please create arcust.dbf using the program already provided by Mr. James Bott to create the dbf.

This is the class posted in the first post with little modification to match TDataRow and TDataBase, retaining the same functionality.

Code: Select all

#include "fivewin.ch"

Function Main()

   Local oCustomer, oCustomers
   Local cCustNo
   Field custno, company

   REQUEST DBFCDX
   rddsetdefault( "DBFCDX" )
   set deleted on

   ferase("arcust.cdx")

   // Just for testing
   // Must use TAG clause with CDXs
   use arcust exclusive
   index on CUSTNO tag "CUSTNO" to arcust
   index on upper(COMPANY) tag "COMPANY" to arcust
   use

   // Instantiate the object
   cCustNo:="10007"
   oCustomer:= TCustomer():new(cCustNo)
   oCustomer:display()

   /* Testing
   msgInfo( oCustomer:Company,"oCustomer:Company")
   msgInfo( oCustomer:getBalance(), "Customer Balance"  )
   msgInfo( oCustomer:paymentStatus(), "Payment Status" )
   msgInfo( oCustomer:PaymentTerms(), "Payment Terms" )
   msgInfo( oCustomer:YearToDateSales(),"Year To Date Sales"  )
   */

   oCustomer:end()

Return nil


//--- Customer class
CLASS TCustomer from TDataRow

   DATA oTable

   METHOD New( cCustNo )
   METHOD ApplyPayment( nAmount, dDate)
   Method Display()
    METHOD End()
   Method GetBalance()
   Method PaymentStatus()
   Method PaymentTerms()
   Method PastDueAmount()
   Method PastDueDays()
   Method PTDSales() inline ::ptdsls
   Method YearToDateSales() inline ::ytdsls
   Method SalesHistory()
ENDCLASS

METHOD New( cCustNo ) CLASS TCustomer
   ::oTable:= TCustomers():New()
   ::oTable:setOrder(1)
   ::oTable:seek( cCustNo )
//   ::Load()
   ::Super:New( ::oTable )

RETURN Self

METHOD End() Class TCustomer
    ::oTable:End()
Return nil

METHOD ApplyPayment( nAmount, dDate ) CLASS TCustomer
   default dDate:= date()
   ::balance:= ::balance - nAmount
   ::lastPay:= dDate
   ::lpymt  := nAmount
   ::save()
RETURN nil

Method GetBalance() Class TCustomer
Return ::balance

Method PaymentStatus() Class TCustomer
Return "Paid Up"

Method PaymentTerms() Class TCustomer
Return "Net "+alltrim(str(::PDays))+" days"

Method PastDueAmount() Class TCustomer
Return 0

Method SalesHistory() Class TCustomer
   //Local oSalesHistory:= TSalesHistory():New(::custNo)
   //oSalesHistory:Display()
   //oSalesHistory:end()
Return nil

Method PastDueDays() Class TCustomer
Return 0

Method Display() Class TCustomer
   Local cString:=""

   cString := cString + "Customer No: "+ ::custno + CRLF
   cString := cString + "Payment Status: "+ ::paymentStatus + CRLF
   cString := cString + "Payment Terms: "+ ::paymentTerms() + CRLF
   cString := cString + "Balance: " + TRANSFORM(::GetBalance(), "$999,999.99") + CRLF
   cString := cString + "Year-To-Date Sales: " + transform(::yeartodateSales(), "$999,999.99") + CRLF

   msgInfo(cString, ::Company)

Return nil




//---------------------------------------------------------------------------//
// Customer table class
Class TCustomers From TDataBase
   Method New()
   Method End() inline ::close()
Endclass

Method New() Class TCustomers
   ::super:new(,"arcust")
   ::use()
   ::setOrder(1)
   ::gotop()
Return self

// EOF
Regards

G. N. Rao.
Hyderabad, India
User avatar
Silvio.Falconi
Posts: 4956
Joined: Thu Oct 18, 2012 7:17 pm

Re: Example Business Object (Customer)

Post by Silvio.Falconi »

Mr Rao,
To better understand how to create a management with Tdatabase and Tdatarow it is possible to make a small program from A to Z, inserting the management of an archive even simple with the following possibilities: insertion, modification, search, printing also with the possibility to insert data from other archives for example through a selection
For us beginners it could be very useful
I use : FiveWin for Harbour August 2020 (Revision) - Harbour 3.2.0dev (r1712141320) - Bcc7.30 - xMate ver. 1.15.3 - PellesC
User avatar
nageswaragunupudi
Posts: 8017
Joined: Sun Nov 19, 2006 5:22 am
Location: India
Contact:

Re: Example Business Object (Customer)

Post by nageswaragunupudi »

Mr. Silvio

What you are asking is totally different from what Mr. James Bott was discussing. (i.e., the subject of this post)

A Customer object is for ONE customer only. The question of adding customer, deleting customer, etc. does not arise.

For that, you need a class to handle Tables. That is what TData or TDatabase does.
These classes already have methods to append, modify, seek and all those.
Regards

G. N. Rao.
Hyderabad, India
User avatar
José Luis Sánchez
Posts: 484
Joined: Thu Oct 13, 2005 9:23 am
Location: Novelda - Alicante - España
Contact:

Re: Example Business Object (Customer)

Post by José Luis Sánchez »

Marcelo Via Giglio wrote:Hola,

maybe we can see to far and think in ORM https://en.wikipedia.org/wiki/Object-relational_mapping

an idea only

regards

Marcelo Vía
Hello Marcelo,
I'm using your TRecord class and works fine for me. Do you have any other classes like a simple ORM and want to share them ?

Regards,
Carlos Mora
Posts: 988
Joined: Thu Nov 24, 2005 3:01 pm
Location: Madrid, España

Re: Example Business Object (Customer)

Post by Carlos Mora »

Hi Marcelo
Marcelo Via Giglio wrote: maybe we can see to far and think in ORM https://en.wikipedia.org/wiki/Object-relational_mapping
ORM are a very elegant solution followed widely in almost every modern programming language. The most important concept behind is that it separates the persistency layer from the data layer, that is it's hidden to the app how the data is stored in permanent memory. For those of us who have being using dbfs for so long, it could be a little bit challenging to forget about that. We are so used to the navigational ISAM model, GOing TOP, SKIPing until EOF that all that sounds a little bit weird in the begining.
Back to ORM, the most complete ones have 2 types of objects: the Entity object and the Repository object. The Entity represents one and only one individual of the collection: a customer, a product, an account, etc. The Repository is the full set of objects on a given Entity, like ClientsRepository, ProductRepository, etc. The Repository is usually the responsible for Find()ing a given or a subset of entities, and for the aggregational functions like Count(), Sum(), etc.
Entities are also able to represent complex objects throw the knowledge of relations between entities: an Invoice:hasMany(InvoiceItems), an Invoice:belongsTo(Customer) and so on, so in your code, given an instance of Invoice oInvoice, you can get the corresponding oCustomer just calling oInvoice:Customer.
Usually the reciprocal relation is true: an Invoice:belongsTo(Customer) so a Customer:hasMany(Invoice) .
With such ORM the code will reduce the need of direct data access by our code significantly.
This is a sample of an Entity written in PHP and Laravel's Eloquent ORM

Code: Select all

<?php

namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class Cliente extends Model
{
    protected $fillable = [
        'agrupacion_id',
        'nombre',
        'apellido1',
        'apellido2',
        'cif',
        'tipo_persona',
        'tipo_via',
        'nombre_via',
        'num_via',
        'otros_via',
        'poblacion',
        'codigo_postal',
        'telefono1',
        'telefono2',
        'email',
        'fax',
    ];
    
    static function rules(){
        return [
            'nombre' => 'required|max:64',
            'apellido1' => 'max:45',
            'apellido2' => 'max:45',
            'cif' => 'required|cif_nif_nie',
            'tipo_persona' => 'required|in:F,J',
            'tipo_via' => 'required',
            'nombre_via' => 'required',
            'num_via' => 'required',
            'otros_via' => '',
            'poblacion' => '',
            'codigo_postal' => 'required',
            'email' => 'email|required|max:64',
            ];
    }

    public function bancos(){
        return $this->hasMany('App\Models\BancoCliente', 'cliente_id', 'id');
    }

    public function solicitudes(){
        return $this->hasMany('App\Models\Solicitud', 'cliente_id', 'id');
    }

    public function agrupacion(){
        return $this->belongsTo('App\Models\Agrupacion', 'id', 'agrupacion_id');
    }

    /**
    * List of values for field Tipo de Persona
    *
    * @return array
    */
    static public function tipo_personaSelectData(){
        return [
            'J' => 'Jurídica',
            'F' => 'Física',
        ];
    }
} 
$Fillable are the fields the app is able to change, rules are the validation rules to apply in creating or editing, and then bancos, solicitudes and agrupacion are the relations among entities, so given a Client $client, invoking $client->bancos will return a collection of bank accounts without the need of writing a single line of code or SQL. Beauty! Thanks to the static methods, the same Model class acts as a Repository, so Cliente::find(32) will retrieve the cliente whose id is 32.
All of this can be done independent of the database used: it can be MySQL/MariadB, Postgress, SQLServer, Oracle, Sqlite, etc.

It would be really nice to have an ORM like that in Harbour, and it will have to be really good to make us to forget about the simplicity of having the dbf data as part of the very same language. I think there is no equivalent of that in any other language in the world.
Saludos
Carlos Mora
http://harbouradvisor.blogspot.com/
StackOverflow http://stackoverflow.com/users/549761/carlos-mora
“If you think education is expensive, try ignorance"
Marcelo Via Giglio
Posts: 1033
Joined: Fri Oct 07, 2005 3:33 pm
Location: Cochabamba - Bolivia

Re: Example Business Object (Customer)

Post by Marcelo Via Giglio »

Dear José Luis,

sorry, I don't have any close to ORM

Dear Carlos,

good explanation about ORM, but will be a best sample a Java ORM like Hibernate, EclipseLink, etc. where with a simple line of code, you can persist entire sophisticates relation entities.

I think [x]Harbour is flexible, maybe can be possible to develop a basic ORM

Many thanks for your comments

Regards and sorry for my good English

Marcelo Vía
User avatar
José Luis Sánchez
Posts: 484
Joined: Thu Oct 13, 2005 9:23 am
Location: Novelda - Alicante - España
Contact:

Re: Example Business Object (Customer)

Post by José Luis Sánchez »

As far as I know there is an ORM for Harbour at https://github.com/tfonrouge/oordb
You can know more about Teo Fonrouge at https://medium.com/harbour-magazine/int ... 9a1a167f7b

Regards,
User avatar
James Bott
Posts: 4654
Joined: Fri Nov 18, 2005 4:52 pm
Location: San Diego, California, USA
Contact:

Re: Example Business Object (Customer)

Post by James Bott »

According to Wikipedia, object oriented programming is a type of ORM. So we have had this capability since the Clipper days.

After you have defined an entity object, you can create an object in one line.

oCustomer := TCustomer:New( cCustNo )

Using TDatabase you can switch from DBFs to SQL without changing any other code.
FWH 18.05/xHarbour 1.2.3/BCC7/Windows 10
Carlos Mora
Posts: 988
Joined: Thu Nov 24, 2005 3:01 pm
Location: Madrid, España

Re: Example Business Object (Customer)

Post by Carlos Mora »

James Bott wrote:According to Wikipedia, object oriented programming is a type of ORM.
After you have defined an entity object, you can create an object in one line.

oCustomer := TCustomer:New( cCustNo )

Using TDatabase you can switch from DBFs to SQL without changing any other code.
OOP is NOT an ORM. They are absolutely different concepts. An ORM is able to hide the persistence layer of an implementation and expose entities abstracting their attributes and relations. OOP is a much wider concept, applied to everything.
Inside oCustomers you are dealing with DML, things that usually you don't need to using an ORM.
How do you get your customer's invoices? In your sample, it is required to write the code to make the select or tretrieve the records. In an ORM you just declare the relation among customer and invoice, sth like

Code: Select all

METHOD Invoices()
RETURN ::hasMany( TInvoices() )
 
Note that you are not writing a single line of code, that should be made by the ORM.
Saludos
Carlos Mora
http://harbouradvisor.blogspot.com/
StackOverflow http://stackoverflow.com/users/549761/carlos-mora
“If you think education is expensive, try ignorance"
Carlos Mora
Posts: 988
Joined: Thu Nov 24, 2005 3:01 pm
Location: Madrid, España

Re: Example Business Object (Customer)

Post by Carlos Mora »

José Luis Sánchez wrote:As far as I know there is an ORM for Harbour at https://github.com/tfonrouge/oordb
No, it is not an ORM, it is a Object Oriented Relational Database. Its model is closer to an Active Record pattern than an ORM.

Regards
Saludos
Carlos Mora
http://harbouradvisor.blogspot.com/
StackOverflow http://stackoverflow.com/users/549761/carlos-mora
“If you think education is expensive, try ignorance"
User avatar
jose_murugosa
Posts: 943
Joined: Mon Feb 06, 2006 4:28 pm
Location: Uruguay
Contact:

Re: Example Business Object (Customer)

Post by jose_murugosa »

This topic looks very atractive.
I feel very enthusiastic about it, I would like to see more information in this forum about how to use this model with FWH and Harbour.
Thanks a lot for all this knowledge you freely shear with us, this is what I love of this forum. :D
Saludos/Regards,
José Murugosa
FWH + Harbour + Bcc7. Una seda!
User avatar
James Bott
Posts: 4654
Joined: Fri Nov 18, 2005 4:52 pm
Location: San Diego, California, USA
Contact:

Re: Example Business Object (Customer)

Post by James Bott »

Carlos Mora wrote:
OOP is NOT an ORM. They are absolutely different concepts. An ORM is able to hide the persistence layer of an implementation and expose entities abstracting their attributes and relations. OOP is a much wider concept, applied to everything.
From Wikipedia:

Object-relational mapping https://en.wikipedia.org/wiki/Object-relational_mapping

Object-relational mapping (ORM, O/RM, and O/R mapping tool) in computer science is a programming technique for converting data between incompatible type systems using object-oriented programming languages.
However there do seem to be varying definitions. See this discussion:

https://stackoverflow.com/questions/115 ... -framework

The issue for us FiveWin programmers is that we do not have the type of ORM that you are describing. However we do have OOP. Even without the lower level ORM that you refer to, we can accomplish the same objectives with our OOP (even if it may require slightly more code). And this does isolate our code from the relational database so it does accomplish abstraction.

So we can either continue to write procedural code, or we can use the OOP language we do have to achieve the same end as using your type of ORM.
FWH 18.05/xHarbour 1.2.3/BCC7/Windows 10
Post Reply