NAME
UR::Object - transactional, queryable, process-independent entities
SYNOPSIS
Create a new object in the current context, and return it:
$elmo = Acme::Puppet->create(
name => 'Elmo',
father => $ernie,
mother => $bigbird,
jobs => [$dance, $sing],
favorite_color => 'red',
);
Plain accessors work in the typial fashion:
$color = $elmo->favorite_color();
Changes occur in a transaction in the current context:
$elmo->favorite_color('blue');
Non-scalar (has_many) properties have a variety of accessors:
@jobs = $elmo->jobs();
$jobs = $elmo->job_arrayref();
$set = $elmo->job_set();
$iter = $elmo->job_iterator();
$job = $elmo->add_job($snore);
$success = $elmo->remove_job($sing);
Query the current context to find objects:
$existing_obj = Acme::Puppet->get(name => 'Elmo');
# same reference as $existing_obj
@existing_objs = Acme::Puppet->get(
favorite_color => ['red','yellow'],
);
# this will not get elmo because his favorite color is now blue
@existing_objs = Acme::Puppet->get(job => $snore);
# this will return $elmo along with other puppets that snore,
# though we haven't saved the change yet..
Save our changes:
UR::Context->current->commit;
Too many puppets...:
$elmo->delete;
$elmo->play; # this will throw an exception now
$elmo = Acme::Puppet->get(name => 'Elmo'); # this returns nothing now
Just kidding:
UR::Context->current->rollback; # not a database rollback, an in-memory undo
All is well:
$elmo = Acme::Puppet->get(name => 'Elmo'); # back again!
DESCRIPTION
UR::Objects are transactional, queryable, representations of entities, built to maintain separation between the physical reference in a program, and the logical entity the reference represents, using a well-defined interface.
UR uses that separation to automatically handle I/O. It provides a query API, and manages the difference between the state of entities in the application, and their state in external persistence systems. It aims to do so transparently, keeping I/O logic orthogonally to "business logic", and hopefully making code around I/O unnecessary to write at all for most programs.
Rather than explicitly constructing and serializing/deserializing objects, the application layer just requests objects from the current "context", according to their characteristics. The context manages database connections, object state changes, references, relationships, in-memory transactions, queries and caching in tunable ways.
Accessors dynamically fabricate references lazily, as needed through the same query API, so objects work as the developer would traditionally expect in most cases. The goal of UR::Object is that your application doesn't have to do data management. Just ask for what you want, use it, and let it go.
UR::Objects support full reflection and meta-programming. Its meta-object layer is fully self-bootstrapping (most classes of which UR is composed are themselves UR::Objects), so the class data can introspect itself, such that even classes can be created within transactions and discarded.
INHERITANCE
UR::ModuleBase Basic error, warning, and status messages for modules in UR.
UR::Object This class - general OO transactional OO features
WRITING CLASSES
See UR::Manual::Tutorial for a narrative explanation of how to write clases.
For a complete reference see UR::Manual::WritingClasses.
For the meta-object API see UR::Object::Type.
A simple example, declaring the class used above:
class Acme::Puppet {
id_by => 'name',
has_optional => [
father => { is => 'Acme::Puppet' },
mother => { is => 'Acme::Puppet' },
jobs => { is => 'Acme::Job', is_many => 1 },
]
};
You can also declare the same API, but specifying additional internal details to make database mapping occur the way you'd like:
class Acme::Puppet {
id_by => 'name',
has_optional => [
father => { is => 'Acme::Puppet', id_by => 'father_id' },
mother => { is => 'Acme::Puppet', id_by => 'mother_id' },
},
has_many_optional => [
job_assignments => { is => 'Acme::PuppetJob', im_its => 'puppet' },
jobs => { is => 'Acme::Job', via => 'job_assignments', to => 'job' },
]
};
CONSTRUCTING OBJECTS
New objects are returned by create() and get(), which delegate to the current context for all object construction.
The create() method will always create something new or will return undef if the identity is already known to be in use.
The get() method lets the context internally decide whether to return a cached reference for the specified logical entities or to construct new objects by loading data from the outside.
METHODS
The examples below use $obj where an actual object reference is required, and SomeClass where the class name can be used. In some cases the example in the synopsisis is continued for deeper illustration.
Base API
- get
-
$obj = SomeClass->get($id); $obj = SomeClass->get(property1 => value1, ...); @obj = SomeClass->get(property1 => value1, ...); @obj = SomeClass->get('property1 operator1' => value1, ...);
Query the current context for objects.
It turns the passed-in parameters into a UR::BoolExpr and returns all objects of the given class which match. The current context determines whether the request can be fulfilled without external queries. Data is loaded from underlying database(s) lazliy as needed to fulfuill the request.
In the simplest case of requesting an object by id which is cached, the call to get() is an immediate hash lookup, and is very fast.
See UR::Manual::Queries, or look at UR::Object::Set, UR::BoolExpr, and UR::Context for details.
If called in scalar context and more than one object matches the given parameters, get() will raise an exception through
die
. - create
-
$obj = SomeClass->create( property1 => $value1, properties2 => \@values2, );
Create a new entity in the current context, and return a reference to it.
The only required property to create an object is the "id", and that is only required for objects which do not autogenerate their own ids. This requirement may be overridden in subclasses to be more restrictive.
If entities of this type persist in an underlying context, the entity will not appear there until commit. (i.e. no insert is done until just before a real database commit) The object in question does not need to pass its own constraints when initially created, but must be fully valid before the transaction which created it commits.
- delete
-
$obj->delete
Deletes an object in the current context.
The $obj reference will be garbage collected at the discretion of the Perl interpreter as soon as possible. Any attempt to use the reference after delete() is called will result in an exception.
If the represented entity was loaded from the parent context (i.e. persistent database objects), it will not be deleted from that context (the database) until commit is called. The commit call will do both the delete and the commit, presuming the complete save works across all involved data sources.
Should the transaction roll-back, the deleted object will be re-created in the current context, and a fresh reference will later be returnable by get(). See the documentation on UR::Context for details on how deleted objects are rememberd and removed later from the database, and how deleted objects are re-constructed on STM rollback.
- copy
-
$obj->copy(%overrides)
Copies the existing
$obj
by copying the values of all direct properties, except for ID properties, to a newly created object of the same type. A list of params and values may be provided as overrides to the existing values or to specify an ID. - class
-
$class_name = $obj->class; $class_name = SomeClass->class;
Returns the name of the class of the object in question. See __meta__ below for the class meta-object.
- id
-
$id = $obj->id;
The unique identifier of the object within its class.
For database-tracked entities this is the primary key value, or a composite blob containing the primary key values for multi-column primary keys.
For regular objects private to the process, the default id embeds the hostname, process ID, and a timestamp to uniquely identify the UR::Context::Process object which is its final home.
When inheritance is involved beneath UR::Object, the 'id' may identify the object within the super-class as well. It is also possible for an object to have a different id upon sub-classification.
Accessors
Every relationship declared in the class definition results in at least one accesor being generated for the class in question.
Identity properties are read-only, while non-identity properties are read-write unless is_mutable is explicitly set to false.
Assigning an invalid value is allowed temporarily, but the current transaction will be in an invalid state until corrected, and will not be commitable.
The return value of an the accessor when it mutates the object is the value of the property after the mutation has occurred.
Single-value property accessors:
By default, properties are expected to return a single value.
- NAME
-
Regular accessors have the same name as the property, as declared, and also work as mutators as is commonly expected:
$value = $obj->property_name; $obj->property_name($new_value);
When the property is declared with id_by instead of recording the refereince, it records the id of the object automatically, such that both will return different values after either changes.
Muli-value property accessors:
When a property is declared with the "is_many" flag, a variety of accessors are made available on the object. See UR::Manual::WritingClasses
for more details on the ways to declare relationships between objects when writing classes.
Using the example from the synopsis:
- NAMEs (the property name pluralized)
-
A "has_many" relationship is declared using the plural form of the relationship name. An accessor returning the list of property values is generated for the class. It is usable with or without additional filters:
@jobs = $elmo->jobs(); @fun_jobs = $elmo->jobs(is_fun => 1);
The singular name is used for the remainder of the accessors...
- NAME (the property name in singular form)
-
Returns one item from the group, which must be specified in parameters. If more than one item is matched, an exception is thrown via die():
$job = $elmo->job(name => 'Sing'); $job = $elmo->job(is_fun => 1); # die: too many things are fun for Elmo
- NAME_list
-
The default accessor is available as *_list. Usable with or without additional filters:
@jobs = $elmo->job_list(); @fun_jobs = $elmo_>job_list(is_fun => 1);
- NAME_set
-
Return a UR::Object::Set value representing the values with *_set:
$set = $elmo->job_set(); $set = $elmo->job_set(is_hard => 1);
- NAME_iterator
-
Create a new iterator for the set of property values with *_iterator:
$iter = $elmo->job_iterator(); $iter = $elmo->job_iterator(is_fun => 1, -order_by => ['name]); while($obj = $iter->next()) { ... }
- add_NAME
-
Add an item to the set of values with add_*:
$added = $elmo->add_job($snore);
A variation of the above will construt the item and add it at once. This second form of add_* automatically would identify that the line items also reference the order, and establish the correct converse relationship automatically.
@lines = $order->lines; # 2 lines, for instance $line = $order->add_line( product => $p, quantity => $q, ); print $line->num; # 3, if the line item has a multi-column primary key with auto_increment on the 2nd column called num
- remove_NAME
-
Items can be removed from the assigned group in a way symetrical with how they are added:
$removed = $elmo->remove_job($sing);
Extended API
These methods are available on any class defined by UR. They are convenience methods around UR::Context, UR::Object::Set, UR::BoolExpr, UR::Object::View, UR::Observer and Mock::Object.
- create_iterator
-
$iter = SomeClass->create_iterator( property1 => $explicit_value, property2 => \@my_in_clause, 'property3 like' => 'some_pattern_with_%_as_wildcard', 'property4 between' => [$low,$high], ); while (my $obj = $iter->next) { ... }
Takes the same sort of parameters as get(), but returns a UR::Object::Iterator for the matching objects.
The next() method will return one object from the resulting set each time it is called, and undef when the results have been exhausted.
UR::Object::Iterator
instances are normal object references in the current process, not context-oriented UR::Objects. They vanish upon dereference, and cannot be retrieved by querying the context.When using an iterator, the system attempts to return objects matching the params at the time the iterator is created, even if those objects do not match the params at the time they are returned from next(). Consider this case:
# many objects in the DB match this my $iter = SomeClass->create_iterator(job => 'cleaner'); my $an_obj = SomeClass->get(job => 'cleaner', id => 1); $an_obj->job('messer-upper'); # This no longer matches the iterator's params my @iter_objs; while (my $o = $iter->next) { push @iter_objs, $o; }
At the end, @iter_objs will contain several objects, including the object with id 1, even though its job is no longer 'cleaner'. However, if an object matching the iterator's params is deleted between the time the iterator is created and the time next() would return that object, then next() will throw an exception.
- define_set
-
$set = SomeClass->define_set( property1 => $explicit_value, property2 => \@my_in_clause, 'property3 like' => 'some_pattern_with_%_as_wildcard', 'property4 between' => [$low,$high], ); @subsets = $set->group_by('property3','property4'); @some_members = $subsets[0]->members;
Takes the same sort of parameters as get(), but returns a set object.
Sets are lazy, and only query underlying databases as much as necessary. At any point in time the members() method returns all matches to the specified parameters.
See UR::Object::Set for details.
- define_boolexpr
-
$bx = SomeClass->define_boolexpr( property1 => $explicit_value, property2 => \@my_in_clause, 'property3 like' => 'some_pattern_with_%_as_wildcard', 'property4 between' => [$low,$high], ); $bx->evaluate($obj1); # true or false?
Takes the same sort of parameters as get(), but returns a UR::BoolExpr object.
The boolean expression can be used to evaluate other objects to see if they match the given condition. The "id" of the object embeds the complete "where clause", and as a semi-human-readable blob, such is reconstitutable from it.
See UR::BoolExpr for details on how to use this to do advanced work on defining sets, comparing objects, creating query templates, adding object constraints, etc.
- add_observer
-
$o = $obj1->add_observer( aspect => 'someproperty' callback => sub { print "change!\n" }, ); $obj1->property1('new value'); # observer callback fires.... $o->delete;
Adds an observer to an object, monitoring one or more of its properties for changes.
The specified callback is fired upon property changes which match the observation request.
See UR::Observer for details.
- create_mock
-
$mock = SomeClass->create_mock( property1 => $value, method1 => $return_value, );
Creates a mock object using using the class meta-data for "SomeClass" via Mock::Object.
Useful for test cases.
Meta API
The following methods allow the application to interrogate UR for information about the object in question.
- __meta__
-
$class_obj = $obj->__meta__();
Returns the class metadata object for the given object's class. Class objects are from the class UR::Object::Type, and hold information about the class' properties, data source, relationships to other classes, etc.
- __extend_namespace__
-
package Foo::Bar; class Foo::Bar { has => ['stuff','things'] }; sub __extend_namespace__ { my $class = shift; my $ext = shift; return class {$class . '::' . $ext} { has => ['more'] }; }
Dynamically generate new classes under a given namespace. This is called automatically by UR::ModuleLoader when an unidentified class name is used.
If Foo::Bar::Baz is not a UR class, and this occurs:
Foo::Bar::Baz->some_method()
This is called:
Foo::Bar->__extend_namespace__("Baz")
If it returns a new class meta, the code will proceed on as though the class had always existed.
If Foo::Bar does not exist, the above will be called recursively:
Foo->__extend_namespace__("Bar")
If Foo::Bar, whether loaded or generated, cannot extend itself for "Baz", the loader will go up the tree before giving up. This means a top-level module could dynamically define classes for any given class name used under it:
Foo->__extend_namespace__("Bar::Baz")
- __errors__
-
@tags = $obj->__errors__()
Return a list of UR::Object::Tag values describing the issues which would prevent a commit in the current transaction.
The base implementation check the validity of an object by applying any constraints layed out in the class such as making sure any non-optional properties contain values, numeric properties contain numeric data, and properties with enumerated values only contain valid values.
Sub-classes can override this method to add additional validity checking.
- __display_name__
-
$text = $obj->__display_name__; # the class and id of $obj, by default $text = $line_item->__display_name__($order);
Stringifies an object. Some classes may choose to actually overload the stringification operator with this method. Even if they do not, this method will still attempt to identify this object in text form. The default returns the class name and id value of the object within a string.
It can be overridden to do a more nuanced job. The class might also choose to overload the stringification operator itself with this method, but even if it doesn not the system will presume this method can be called directly on an object for reasonable stringificaiton.
- __context__
-
$c = $self->__context__;
Return the UR::Context for the object reference in question.
In UR, a "context" handles connextions between objects, instead of relying on having objects directly reference each other. This allows an object to have a relationship with a large number of other logical entities, without having a "physical" reference present within the process in question.
All attempts to resolve non-primitive attribute access go through the context.
Extension API
These methods are primarily of interest for debugging, for test cases, and internal UR development.
They are likely to change before the 1.0 release.
- __signal_change__
-
Called by all mutators to tell the current context about a state change.
- __changes__
-
@tags = $obj->__changes__() @tags = $obj->__changes__('prop1', 'prop2', ...)
Return a list of changes present on the object _directly_. This is really only useful internally because the boundary of the object is internal/subjective. Callers may also request only changes to particular properties.
Changes to objects' properties are tracked by the system. If an object has been changed since it was defined or loaded from its external data source, then changed() will return a list of UR::Object::Tag objects describing which properties have been changed.
Work is in-progress on an API to request the portion of the changes in effect in the current transaction which would impact the return value of a given list of properties. This would be directly usable by a view/observer.
- __define__
-
This is used internally to "virtually load" things. Simply assert they already existed externally, and act as though they were just loaded... It is used for classes defined in the source code (which is the default) by the "class {}" magic instead of in some database, as we'd do for regular objects.
- __strengthen__
-
$obj->__strengthen__();
Mark this object as unloadable by the object cache pruner.
UR objects are normally tracked by the current Context for the life of the application, but the programmer can specify a limit to cache size, in which case old, unchanged objects are periodically pruned from the cache. If strengthen() is called on an object, it will effectively be locked in the cache, and will not be considered for pruning.
See UR::Context for more information about the pruning mechanism.
- is_strengthened
-
Check if an object has been stengthened,
__stengthen__
. - __weaken__
-
$obj->__weaken__();
Give a hint to the object cache pruner that this instance is not going to be used in the application in the future, and should be removed with preference when pruning the cache.
- is_weakened
-
Check if an object has been weakened,
__weaken__
. - DESTROY
-
Perl calls this method on any object before garbage collecting it. It should never by called by your application explicitly.
The DESTROY handler is overridden in UR::Object. If you override it in a subclass, be sure to call $self->SUPER::DESTROY() before exiting your override, or errors will occur.
ERRORS, WARNINGS and STATUS MESSAGES
When an error occurs which is "exceptional" the API will throw an exception via die().
In some cases, when the possibility of failure is "not-exceptional", the method will simply return false. In scalar context this will be undef. In list context an empty list.
When there is ambiguity as to whether this is an error or not (get() for instance, might simply match zero items, ...or fail to understand your parameters), an exception is used.
- error_message
-
The standard way to convey the error which has occurred is to set ->error_message() on the object. This will propagate to the class, and through its inheritance. This is much like DBI's errstr method, which affects the handle on which it was called, its source handle, and the DBI package itself.
- warning_message
-
Calls to warning_message also record themselves on the object in question, and its class(es).
They also emit a standard Perl warn(), which will invoke $SIG{__WARN__};
- status_message
-
Calls to status_message are also recorded on the object in question. They can be monitored through hooks, as can the other messages.
See UR::ModuleBase for more information.
SEE ALSO
UR, UR::Object::Type, UR::Context
UR::Maual::Tutorial, UR::Manual::WritingClasses, UR::Manual::Queries, UR::Manual::Transactions
UR::ObjectDeprecated contains additional methods which are deprecated in the API.