=head1 NAME Tangram::Tour - Guided Tour =head1 INTRODUCTION In this tour, we add persistence to a simple Person design. A Person is either a NaturalPerson or a LegalPerson. Persons (in general) have a collection of addresses. An address consists in a type (a string) and a city (also a string). NaturalPerson - a subclass of Person - represents persons of flesh and blood. NaturalPersons have a name and a firstName (both strings) and an age (an integer). NaturalPersons sometimes have a partner (another NaturalPerson) and even children (a collection of NaturalPersons). LegalPerson - another subclass of Person - represents companies and other entities that the law regards as 'persons'. A LegalPerson has a name (a string) and a manager (a NaturalPerson). All this is expressed in the following UML diagram: +---------------------+ +--------------+ | Person | | Address | | { abstract } |1<>-->-*|--------------| |---------------------| | kind: string | +---------------------+ | city: string | | +--------------+ | +--------------A--------------+ | | +-------------------+ +---------------+ +--*| NaturalPerson | | LegalPerson | | |-------------------|manager |---------------| V | firstName: string |1---<-----1| name: string | | | name: string | +---------------+ +--*| age: integer | children +-------------------+ 1 1 | partner | | +--->---+ B create the corresponding Perl packages!>. That's up to the user. However, to facilitate experimentation, Tangram comes with a module that implements the necessary classes. For more information see L. Before we can actually store objects we must complete two steps: =over 4 =item 1 Create a Schema =item 2 Create a database =back =head2 Creating a Schema A Schema object contains information about the persistent aspects of a system of classes. It also gives a degree of control over the way Tangram performs the object-relational mapping, but in this tour we will use all the defaults. Here is the Schema for Springfield: $schema = Tangram::Relational->schema( { classes => [ Person => { abstract => 1, fields => { iarray => { addresses => { class => 'Address', aggreg => 1 } } } }, Address => { fields => { string => [ qw( kind city ) ], }, }, NaturalPerson => { bases => [ qw( Person ) ], fields => { string => [ qw( firstName name ) ], int => [ qw( age ) ], ref => [ qw( partner ) ], array => { children => 'NaturalPerson' }, } }, LegalPerson => { bases => [ qw( Person ) ], fields => { string => [ qw( name ) ], ref => [ qw( manager ) ], } }, ] } ); The Schema lists all the classes that need persistence, along with their attributes and the inheritance relationships. We must provide type information for the attributes, because SQL is more typed than Perl. We also tell Tangram that C is an abstract class, so it wastes no time attempting to retrieve objects of that exact class. Note that Tangram cannot deduce this information by itself. While Perl makes it possible to extract the list of all the classes in an application, in general not all classes will need to persist. A class may have both persistent and non-persistent bases. As for attributes, Perl's most typical representation for objects - a hash - even allows two objects of the same class to have a different set of attributes. For more information on creating Schemas, see L and L. =head2 Setting up a database Now we create a database. The simplest way is to create an empty database and let Tangram initialize it: use Tangram; $dbh = DBI->connect( @cp ); Tangram::Relational->deploy($schema, $dbh ); $dbh->disconnect(); Tangram::Relational is the vanilla object-relational backend. It assumes that the database understands standard SQL, and that both the database and the related DBI driver fully implements the DBI specification. Tangram also comes with vendor-specific backends for Mysql and Sybase. When a vendor-specific backend exists, it should be used in place of the vanilla backend. For more information, see L, L and L. =head2 Connecting to a database We are now ready to store objects. First we connect to the database, using the class method Tangram::Relational::connect (or Tangram::mysql::connect for Mysql). The first argument of connect() the schema object; the others are passed directly to DBI::connect. The method returns a Tangram::Storage object that will be used to communicate with the database. For example: $storage = Tangram::Relational->connect( $schema, @cp ); connects to a database named Springfield via the vanilla Relational backend, using a specific account and password. For more information on connecting to databases, see L and L. =head2 Inserting objects Now we can populate the database: $storage->insert( NaturalPerson->new( firstName => 'Montgomery', name => 'Burns' ) ); This inserts a single NaturalPerson object into the database. We can insert several objects in one call: $storage->insert( NaturalPerson->new( firstName => 'Patty', name => 'Bouvier' ), NaturalPerson->new( firstName => 'Selma', name => 'Bouvier' ) ); Sometimes Tangram saves objects implicitly: @kids = ( NaturalPerson->new( firstName => 'Bart', name => 'Simpson' ), NaturalPerson->new( firstName => 'Lisa', name => 'Simpson' ) ); $marge = NaturalPerson->new( firstName => 'Marge', name => 'Simpson', addresses => [ Address->new( kind => 'residence', city => 'Springfield' ) ], children => [ @kids ] ); $homer = NaturalPerson->new( firstName => 'Homer', name => 'Simpson', addresses => [ Address->new( kind => 'residence', city => 'Springfield' ), Address->new( kind => 'work', city => 'Springfield' ) ], children => [ @kids ] ); $homer->{partner} = $marge; $marge->{partner} = $homer; $homer_id = $storage->insert( $homer ); In the process of saving Homer, Tangram detects that it contains references to objects that are not persistent yet (Marge, the addresses and the kids), and inserts them automatically. Note that Tangram can handle cycles: Homer and Marge refer to each other. insert() returns an object id, or a list of object ids, that uniquely identify the object(s) that have been inserted. For more information on inserting objects, see L. =head2 Updating objects Updating works pretty much the same as inserting: my $maggie = NaturalPerson->new( firstName => 'Maggie', name => 'Simpson' ); push @{ $homer->{children} }, $maggie; push @{ $marge->{children} }, $maggie; $storage->update( $homer, $marge ); Here again Tangram detects that Maggie is not already persistent in $storage and automatically inserts it. Note that we need to update Marge explicitly because she was already persistent. For more information on updating objects, see L. =head2 Memory management ...is still up to you. Tangram won't break in-memory cycles, it's a persistence tool, not a memory management tool. Let's make sure we don't leak objects: $homer->{partner} = undef; # do this before $homer goes out of scope Also, when we're finished with a storage, we can explicitly disconnect it: $storage->disconnect(); Whether it's important or not to disconnect the Storage depends on what version of Perl you use. If it's prior to 5.6, you I disconnect the storage explicitly (or at least call unload()) otherwise the Storage will prevent the objects it controls from being reclaimed by Perl. For more information see see L. =head2 Finding objects After reconnecting to Springfield, we now want to retrieve some objects. But how do we find them? Basically there are three options =over 4 =item * We know their IDs. =item * We obtain them from another object. =item * We use a query. =back =head2 Loading by ID When an object is inserted, Tangram assigns an identifier to it. IDs are numbers that uniquely identify objects in the database. C returns the ID(s) of the object(s) it was passed: $storage = Tangram::Relational->connect( $schema, @cp ); $ned_id = $storage->insert( NaturalPerson->new( firstNname => 'Ned', name => 'Flanders' ) ); @sisters_id = $storage->insert( NaturalPerson->new( firstName => 'Patty', name => 'Bouvier' ), NaturalPerson->new( firstName => 'Selma', name => 'Bouvier' ) ); This enables us to retrieve the objects: $ned = $storage->load( $ned_id ); @sisters = $storage->load( @sisters_id ); For more information on loading objects by id, see L. =head2 Obtaining objects from other objects Once Homer has been restored to his previous state, including his relations with his family. Thus we can say: $storage = Tangram::Relational->connect( $schema, @cp ); $homer = $storage->load( $homer_id ); # load by id $marge = $homer->{partner}; @kids = @{ $homer->{children} }; Actually, when Tangram loads an object that contains references to other persistent objects, it doesn't retrieve the referenced objects immediately. Marge is retrieved only when Homer's 'partner' field is accessed. This mechanism is almost totally transparent, we'd have to use C to observe a non-present collection or reference. For more information on relationships, see L, L, L, L, L and L. =head2 select To retrieve all the objects of a given class, we use C