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

NAME

       UR::Manual::Overview - UR from Ten Thousand Feet

Perspective on Objects

       Standard software languages provide a facility for making objects. Those objects have
       certain characteristics which are different with UR objects.

       A standard object in most languages:

       •   exists only as long as the program which created it has a reference to it

       •   requires that the developer manage organizing the object(s) into a structure to
           support any searching required

       •   handles persistence between processes explicitly, by saving or loading the object to
           external storage

       •   references other objects only if explicitly linked to those objects

       •   acts as a functional software device, but any meaning associated with the object is
           implied by how it is used

       Regular objects like those described above are the building blocks of most software.

       In many cases, however, they are often used for a second, higher-level purpose: defining
       entities in the domain model of the problem area the software addresses. UR objects are
       tailored to represent domain model entities well. In some sense, UR objects follow many of
       the design principles present in relational databases, and as such mapping to a database
       for UR objects is trivial, and can be done in complex ways.

       UR objects differ from a standard object in the following key ways:

       •   the object exists after creation until explicitly deleted, or the transaction it is in
           rolled-back

       •   managing loaded objects is done automatically by a Context object, which handles
           queries, saving, lazy-loading and caching

       •   it is possible to query for an object by specifying the class and the matching
           characteristics

       •   the object can reference other objects which are not loaded in the current process,
           and be referenced by objects not in the current process

       •   the object is a particular truth-assertion in the context in which it exists

Object-Relational Mapping

       UR's primary reason for existing is to function as an ORM. That is, managing how to store
       instances of objects in memory of a running program with more persistent storage in a
       relational database, and retrieve them later.  It handles the common cases where each
       table is implemented by a class their columns are properties of the classes; retrieving
       objects by arbitrary properties; creating, updating and deleting objects with enforced
       database constraints; and named relationships between classes.

       It can also handle more complicated things like:

       •   classes for things which are not database entities at all

       •   derived classes where the data spans multiple tables between the parent and child
           classes

       •   loading an object through a parent class and having it automatically reblessed into
           the appropriate subclass

       •   properties with no DB column behind them

       •   calculated properties with a formula behind them

       •   inheritance hierarchies that may have tables missing at some or all stages

       •   meta-data about Properties, Classes and the relationships between them

Object Context

       With UR, every object you create is made a part of the current "Context".  Conceptually,
       the Context is the lens by which your application views the data that exists in the world.
       At one level, you can think of the current context as an in-memory transaction. All
       changes to the object are tracked by the context. The Context knows how to map objects to
       their storage locations, called Data Sources. Saving your changes is simply a matter of
       asking the current context to commit.

       The Context can also reverse the saving process, and map a request for an object to a
       query of external storage. Requests for objects go through the Context, are loaded from
       outside as needed, and are returned to the caller after being made part of the current
       context's transaction.

       Objects never reference each other by actual Perl reference internally, instead they use
       the referent's ID. Accessors on an object which return another object send the ID through
       the context to get the object back, allowing the context to load the referenced object
       only when it is actually needed. This means that your objects can hook together until
       references span an entire database schema, and pulling one object from the database will
       not load the entire database into memory.

       The context handles caching, and by default will cache everything it touches. This means
       that you can ask for the same thing multiple times, and only the first request will
       actually hit the underlying database.  It also means that requests for objects which map
       to the same ID will return the exact same instance of the object.

       The net effect is that each process's context is an in-memory database. All object
       creation, deletion, and change is occurring directly to that database.  For objects
       configured to have external persistence, this database manages itself as a "diff" vs. the
       external database, allowing it to simulate representing all UR data everywhere, while only
       actually tracking what is needed.

   Benefits
       •   database queries don't repeat themselves again and again

       •   you never write insert/update/delete statements, or work out constraint order yourself

       •   allows you to write methods which address an object individually, with ways to avoid
           many individual database queries

       •   explicitly clearing the cache is less complex than explicitly managing the caching of
           data

   Issues
       •   the cache grows until you explicitly clear it

       •   there is CPU overhead checking the cache if you really are always going directly to
           the database

       Unloading objects from the cache can be done in several ways

       •   Calling unload() as a class or instance method to unload all objects of a class, or
           one particular object.

       •   Setting object count limits explicitly with object_cache_size_lowwater() and
           object_cache_size_highwater() in UR::Context

       •   Creating a UR::Context::AutoUnloadPool instance.  When it goes out of scope, all UR
           Objects loaded during its lifetime will be unloaded.

Class Definitions

       At the top of every module implementing a UR class is a block of code that defines the
       class to explicitly spell out its inheritance, properties and types, constraints,
       relationships to other classes and where the persistent storage is located. It's meant to
       be easy to read and edit, if necessary. If the class is backed by a database table, then
       it can also maintain itself.

Metadata

       Besides the object instances representing data used by the program, the UR system has
       other objects representing metadata about the classes (class information, properties,
       relationships, etc), database entities (databases, tables, columns, constraints, etc),
       transactions, data sources, etc. All the metadata is accessible through the same API as
       any of the database-backed data.

       For classes backed by the database, after a schema change (like adding tables or columns,
       altering types or constraints), a command-line tool can automatically detect the change
       and alter the class definition in the Perl module to keep the metadata in sync with the
       database.

Documentation System

       At the simplest level, most entities have a 'doc' metadata attribute to attach some kind
       of documentation to. There's also a set of tools that can be run from the command line or
       a web browser to view the documentation. It can also be used to browse through the class
       and database metadata, and generate diagrams about the metadata.

Iterators

       If a retrieval from the database is likely to result in the generation of tons of objects,
       you can choose to get them back in a list and keep them all in memory, or get back a
       special Iterator object that the program can use to get back objects in batches.

Command Line Tools

       UR has a central command-line tool that cam be used to manipulate the metadata in
       different ways. Setting up namespaces, creating data sources, syncing classes with
       schemas, accessing documentation, etc.

       There is also a framework for creating classes that represent command line tools, their
       parameters and results, and makes it easy to create tools through the Command Pattern.

Example

       Given these classes:

       PathThing/Path.pm
             use strict;
             use warnings;

             use PathThing;  # The application's UR::Namespace module

             class PathThing::Path {
                 id_by => 'path_id',
                 has => [
                     desc   => { is => 'String' },
                     length => { is => 'Integer' },
                 ],
                 data_source => 'PathThing::DataSource::TheDB',
                 table_name => 'PATHS',
             };

       PathThing/Node.pm
             class PathThing::Node {
                 id_by => 'node_id',
                 has => [
                     left_path => { is => 'PathThing::Path', id_by => 'left_path_id' },
                     left_path_desc => { via => 'left_path', to => 'desc' },
                     left_path_length => { via => 'left_path', to => 'length' },

                     right_path => { is => 'PathThing::Path', id_by => 'right_path_id' },
                     right_path_desc => { via => 'right_path', to => 'desc' },
                     right_path_length => { via => 'right_path', to => 'length' },

                     map_coord_x => { is => 'Integer' },
                     map_coord_y => { is => 'String' },
                 ],
                 data_source => 'PathThing::DataSource::TheDB',
                 table_name => 'NODES',
             };

       For a script like this one:

         use PathThing::Node;
         my @results = PathThing::Node->get(
                           right_path_desc => 'over the river',
                           left_path_desc => 'through the woods',
                           right_path_length => 10,
                       );

       It will generate SQL like this:

         select NODES.NODE_ID, NODES.LEFT_PATH_ID, NODES.RIGHT_PATH_ID,
                NODES.MAP_COORD_X, NODES.MAP_COORD_Y,
                left_path_1.PATH_ID, left_path_1.DESC, left_path_1.LENGTH
                right_path_1.PATH_ID, right_path_1.DESC, right_path_1.LENGTH
         from NODES
         join PATHS left_path_1 on NODES.LEFT_PATH_ID = left_path_1.PATH_ID
         join PATHS right_path_1 on NODES.RIGHT_PATH_ID = right_path1.PATH_ID
         where left_path_1.DESC = 'through the woods'
           and right_path_1.DESC = 'over the river',
           and right_path_1.LENGTH = 10

       And for every row returned by the query, a PathThing::Node and two PathThing::Path objects
       will be instantiated and stored in the Context's cache.  @results will contain a list of
       matching PathThing::Node objects.