Provided by: libur-perl_0.440-1_all bug

NAME

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

Overview

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

       Our database has the following basic entities and relationships:

       • One artist can have many CDs

       • One CD belongs to one artist

       • one CD can have many tracks

       • one track belongs to one CD

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.

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

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

         > 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 UR::Command for more details on writing tools like this.

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.

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.

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

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:

       1. Find all the defined datasources within the current namespace
       2. Query the data sources about what tables, columns, constraints and foreign keys are
       present
       3. Load up all the classes in the current namespace
       4. Figure out what the differences are between the database schema and the class structure
       5. Alter the class metadata to match the database schema
       6. Use the new class metadata to write headers on the Perl module files in the namespace

       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 "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 "$cd->title". To get back the
       artist object that is related to that CD, "$cd->artist".

CRUD (Create, Read, Update, Delete)

   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.

   Read
       Retrieving object instances from the database is done with the "get()" method.  A "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);

   get_or_create
       "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');

   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 "UR::Context->commit".

   Delete
       The "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.

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.

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'
                                       );