=pod

=head1 NAME

UR::Manual::Tutorial - Step-by-step guide to building a set of classes for a simple database schema

=head1 Overview

We'll use the familiar "Music Database" example used in many ORM tutorials:

Our database has the following basic entities and relationships:

=over 2 

=item *
One artist can have many CDs

=item *
One CD belongs to one artist

=item *
one CD can have many tracks

=item *
one track belongs to one CD 

=back

=head1 The "ur" command-line program

The tool for working with UR from the command line is 'ur' . It is installed
with the UR module suite.

Just type "ur" and hit enter, to see a list of valid ur commands: 
 > ur 
 Sub-commands for ur:
 init      NAMESPACE [DB]      initialize a new UR app in one command            
 define    ...                 define namespaces, data sources and classes       
 describe  CLASSES-OR-MODULES  show class properties, relationships, meta-data   
 update    ...                 update parts of the source tree of a UR namespace 
 list      ...                 list objects, classes, modules                    
 sys       ...                 service launchers                                 
 test      ...                 tools for testing and debugging   

The "ur" command works a lot like the "svn" command: it is the entry point
for a list of other subordinate commands.

=over 2

=item *

Typing something like "ur browser" will run the browser tool.

=item *

Typing something like "ur define" will give another list, of even more
granular commands which are under "ur define": 

=back

  > ur define
  Sub-commands for ur define:
   namespace  NSNAME               create a new namespace tree and top-level module 
   db         URI NAME             add a data source to the current namespace       
   class      --extends=? [NAMES]  Add one or more classes to the current namespace 

At any point, you can put '--help' as a command line argument and get some
(hopefully) helpful documentation.

In many cases, the output also resembles svn's output where the first column
is a character like 'A' to represent something being added, 'D' for deleted,
etc.

(NOTE: The "ur" command, uses the Command API, an API for objects which
follow the command-pattern. See L<UR::Command> for more details on writing
tools like this.

=head1 Define a UR Namespace

A UR namespace is the top-level object that represents your data's class
structure in the most general way. For this new project, we'll need to
create a new namespace, perhaps within a testing directory.

  ur define namespace Music

And you should see output like this:

  A   Music (UR::Namespace)
  A   Music::Vocabulary (UR::Vocabulary)
  A   Music::DataSource::Meta (UR::DataSource::Meta)
  A   Music/DataSource/Meta.sqlite3-dump (Metadata DB skeleton)

showing that it created 3 classes for you, Music, Music::Vocabulary
and Music::DataSource::Meta, and shows what classes those inherit from.
In addition, it has also created a file to hold your metadata. Other parts
of the documentation give a more thorough description of Vocabulary and
Metadata classes. 

=head1 Define a Data Source

A UR DataSource is an object representing the location of your data. It's
roughly analogous to a Schema class in DBIx::Class, or the "Base class" in
Class::DBI.

Note: Because UR can be used with objects which do NOT live in a database,
using a data source is optional, but is the most common case.

Most ur commands operate in the context of a Namespace, including the one to
create a datasource, so you need to be within the Music's Namespace's directory:

  cd Music

and then define the datasource. We specify the data source's type as a
sub-command, and the name with the --dsname argument.  For this example,
we'll use a brand new SQLite database. For some other, perhaps already
existing database, give its connect string instead.

  ur define db dbi:SQLite:/var/lib/music.sqlite3 Example

which generates this output:

  A   Music::DataSource::Example (UR::DataSource::SQLite,UR::Singleton)
     ...connecting...
     ....ok

and creates a symlink to the database at:
  Music/DataSource/Example.sqlite3

and shows that it created a class for your data source called
Music::DataSource::Example, which inherits from UR::DataSource::SQLite.
It also created an empty database file and connected to it to confirm that
everything is OK. 

=head1 Create the database tables

Here are the table creation statements for our example database. Put them
into a file with your favorite editor and call it example-db.schema.txt:

  CREATE TABLE artist (
     artist_id INTEGER NOT NULL PRIMARY KEY,
     name TEXT NOT NULL
  );
  CREATE TABLE cd (
     cd_id INTEGER NOT NULL PRIMARY KEY,
     artist_id INTEGER NOT NULL CONSTRAINT CD_ARTIST_FK REFERENCES artist(artist_id),
     title TEXT NOT NULL,
     year INTEGER
  );
  CREATE TABLE track (
     track_id INTEGER NOT NULL PRIMARY KEY,
     cd_id INTEGER NOT NULL CONSTRAINT TRACK_CD_FK REFERENCES cd(cd_id),
     title TEXT NOT NULL
  );

This new SQLite data source assumes the database file will have the pathname
Music/DataSource/Example.sqlite3. You can populate the database schema
like this:

  sqlite3 DataSource/Example.sqlite3 < example-db.schema.txt

=head1 Create your data classes

Now we're ready to create the classes that will store your data in the
database.

You could write those classes by hand, but it's easiest to start with an
autogenerated group built from the database schema:

  ur update classes-from-db

is the command that performs all the magic. You'll see it go through several
steps:

=over 2

=item 1.
Find all the defined datasources within the current namespace

=item 2.
Query the data sources about what tables, columns, constraints and foreign
keys are present

=item 3.
Load up all the classes in the current namespace

=item 4.
Figure out what the differences are between the database schema and the
class structure

=item 5.
Alter the class metadata to match the database schema

=item 6.
Use the new class metadata to write headers on the Perl module files in the
namespace 

=back

There will now be a Perl module for each database table. For example, in
Cd.pm:

  package Music::Cd;
  
  use strict;
  use warnings;
  
  use Music;
  class Music::Cd {
      table_name => 'CD',
      id_by => [
          cd_id => { is => 'INTEGER' },
      ],
      has => [
          artist    => { is => 'Music::Artist', id_by => 'artist_id', constraint_name => 'CD_ARTIST_FK' },
          artist_id => { is => 'INTEGER' },
          title     => { is => 'TEXT' },
          year      => { is => 'INTEGER', is_optional => 1 },
      ],
      schema_name => 'Example',
      data_source => 'Music::DataSource::Example',
  };
  
  1;

The first few lines are what you would see in any Perl module. The keyword
C<class> tells the UR system to define a new class, and lists the properties
of the new class. Some of the important parts are that instances of this
class come from the Music::DataSource::Example datasource, in the table
'CD'. This class has 4 direct properties (cd_id, artist_id, title and year),
and one indirect property (artist). Instances are identified by the cd_id
property.

Methods are automatically created to match the property names. If you have
an instance of a CD, say $cd, you can get the value of the title with
C<$cd-E<gt>title>. To get back the artist object that is related to that CD,
C<$cd-E<gt>artist>. 

=head1 CRUD (Create, Read, Update, Delete) 

=head2 Create

Creating new object instances is done with the create method; its arguments
are key-value pairs of properties and their values.

  #!/usr/bin/perl

  use strict;
  use Music;

  my $obj1 = Music::Artist->create(name => 'Elvis');

  my $obj2 = Music::Artist->create(name => 'The Beatles');

  UR::Context->commit();

And that's it. After this script runs, there will be 2 rows in the Artist table.

Just a short aside about that last line... All the changes to your objects
while the program runs (creates, updates, deletes) exist only in memory. The
current "Context" manages that knowledge. Those changes are finally pushed
out to the underlying data sources with that last line. 

=head2 Read

Retrieving object instances from the database is done with the C<get()>
method.  A C<get()> with no arguments will return a list of all the objects
in the table.

  @all_cds = Music::Cd->get();

If you know the "id" (primary key) value of the objects you're interested in,
you can pass that "id" value as a single argument to get:

  $cd = Music::Cd->get(3);

An arrayref of identity values can be passed-in as well. Note that if you
query is going to return more than one item, and it is called in scalar
context, it will generate an exception.

  @some_cds = Music::Cd->get([1, 2, 4]);

To filter the return list by a property other than the ID property, give a
list of key-value pairs:

  @some_cds = Music::Cd->get(artist_id => 3);

This will return all the CDs with the artist ID 5, 6 or 10.

  @some_cds = Music::Cd->get(artist_id => [5, 6, 10]);

get() filters support operators other than strict equality. This will return
a list of CDs with artist ID 2 and have the word 'Ticket' somewhere in the
title.

  @some_cds = Music::Cd->get(artist_id=> 2, title => { operator => 'like', value => '%Ticket%'} );

To search for NULL fields, use undef as the value:

  @cds_with_no_year = Music::Cd->get(year => undef);

=head2 get_or_create

C<get_or_create()> is used to retrieve an instance from the database if it
exists, or create a new one if it does not.

  $possibly_new = Music::Artist->get_or_create(name => 'The Band');

=head2 Update

All the properties of an object are also mutators. To change the object's
property, just call the method for that property with the new value.

  $cd->year(1990);

Remember that any changes made while the program runs are not saved in the
database until you commit the changes with C<UR::Context-E<gt>commit>. 

=head2 Delete

The C<delete()> method does just what it says.

  @all_tracks = Music::Track->get();
  foreach my $track ( @all_tracks ) {
     $track->delete();
  }

Again, the corresponding database rows will not be removed until you commit.

=head1 Relationships

After running ur update classes, it will automatically create indirect
properties for all the foreign keys defined in the schema, but not for the
reverse relationships. You can add other relationships in yourself and they
will persist even after you run ur update classes again. For example, there
is a foreign key that forces a track to be related to one CD. If you edit
the file Cd.pm, you can define a relationship so that CDs can have many
tracks:

  class Music::Cd {
     table_name => 'CD',
     id_by => [
         cd_id => { is => 'INTEGER' },
     ],
     has => [
         artist   => { is => 'Music::Artist', id_by => 'artist_id', constraint_name => 'CD_ARTIST_FK' },
         artist_id => { is => 'INTEGER' },
         title    => { is => 'TEXT' },
         year     => { is => 'INTEGER' },
         tracks   => { is => 'Music::Track', reverse_as => 'cd', is_many => 1 },  # This is the new line
     ],
     schema_name => 'Example',
     data_source => 'Music::DataSource::Example',
  };

This tells the system that there is a new property called 'tracks' which
returns items of the class Music::Track. It links them to the acting CD
object through the Track's cd property.

After that is in place, you can ask for a list of all the tracks belonging
to a CD with the line 

  @tracks = $cd->tracks()

You can also define indirect relationships through other indirect
relationships. For example, if you edit Artist.pm to add a couple of lines:

  class Music::Artist {
      table_name => 'ARTIST',
      id_by => [
          artist_id => { is => 'INTEGER' },
      ],
      has => [
          name     => { is => 'TEXT' },
          cds      => { is => 'Music::Cd', reverse_as => 'artist', is_many => 1 },
          tracks   => { is => 'Music::Track', via => 'cds', to => 'tracks', is_many => 1},
      ],
      schema_name => 'Example',
      data_source => 'Music::DataSource::Example',
  };

This defines a relationship 'cds' to return all the CDs from the acting
artist. It also defines a relationship called 'tracks' that will, behind the
scenes, first look up all the CDs from the acting artist, and then find and
return all the tracks from those CDs.

Additional arguments can be passed to these indirect accessors to get a
subset of the data

  @cds_in_1990s = $artist->cds(year => { operator => 'between',
                                         value => [1990,1999] } );

would get all the CDs from that artist where the year is between 1990 and
1999, inclusive.

Note that is_many relationships should always be named with plural words.
The system will auto-create other accessors based on the singular name for
adding and removing items in the relationship. For example:

  $artist->add_cd(year => 1998, title => 'Cool Jams' );

would create a new Music::Cd object with the given year and title. The
cd_id will be autogenerated by the system, and the artist_id will be
automatically set to the artist_id of $artist. 

=head1 Custom SQL

It's possible to use get() with custom SQL to retrieve objects, as long as
the select clause includes all the ID properties of the class. To find
Artist objects that have no CDs, you might do this:

  my @artists_with_no_cds =
         Music::Artist->get(sql => 'select artist.artist_id,
                                               count(cd.artist_id)
                                        from artist
                                        left join cd on cd.artist_id = artist.artist_id
                                        group by artist.artist_id
                                        having count(cd.artist_id) = 0'
                                );

