NAME
Rose::DB::Object::Manager - Fetch multiple Rose::DB::Object-derived objects from the database.
SYNOPSIS
##
## Given the following example Rose::DB::Object-derived classes...
##
package Category;
use Rose::DB::Object;
our @ISA = qw(Rose::DB::Object);
__PACKAGE__->meta->table('categories');
__PACKAGE__->meta->columns
(
id => { type => 'int', primary_key => 1 },
name => { type => 'varchar', length => 255 },
description => { type => 'text' },
);
__PACKAGE__->meta->add_unique_key('name');
__PACKAGE__->meta->initialize;
...
package Product;
use Rose::DB::Object;
our @ISA = qw(Rose::DB::Object);
__PACKAGE__->meta->table('products');
__PACKAGE__->meta->columns
(
id => { type => 'int', primary_key => 1 },
name => { type => 'varchar', length => 255 },
description => { type => 'text' },
category_id => { type => 'int' },
status =>
{
type => 'varchar',
check_in => [ 'active', 'inactive' ],
default => 'inactive',
},
start_date => { type => 'datetime' },
end_date => { type => 'datetime' },
date_created => { type => 'timestamp', default => 'now' },
last_modified => { type => 'timestamp', default => 'now' },
);
__PACKAGE__->meta->add_unique_key('name');
__PACKAGE__->meta->foreign_keys
(
category =>
{
class => 'Category',
key_columns =>
{
category_id => 'id',
}
},
);
__PACKAGE__->meta->initialize;
...
##
## Create a manager class
##
package Product::Manager;
use Rose::DB::Object::Manager;
our @ISA = qw(Rose::DB::Object::Manager);
sub object_class { 'Product' }
__PACKAGE__->make_manager_methods('products');
# The call above creates the methods shown below. (The actual
# method bodies vary slightly, but this is the gist of it...)
#
# sub get_products
# {
# shift->get_objects(@_, object_class => 'Product');
# }
#
# sub get_products_iterator
# {
# shift->get_objects_iterator(@_, object_class => 'Product');
# }
#
# sub get_products_count
# {
# shift->get_objects_count(@_, object_class => 'Product');
# }
...
##
## Use the manager class
##
#
# Get a reference to an array of objects
#
$products =
Product::Manager->get_products
(
query =>
[
category_id => [ 5, 7, 22 ],
status => 'active',
start_date => { lt => '15/12/2005 6:30 p.m.' },
name => { like => [ '%foo%', '%bar%' ] },
],
sort_by => 'category_id, start_date DESC',
limit => 100,
offset => 80,
);
foreach my $product (@$products)
{
print $product->id, ' ', $product->name, "\n";
}
#
# Get objects iterator
#
$iterator =
Product::Manager->get_products_iterator
(
query =>
[
category_id => [ 5, 7, 22 ],
status => 'active',
start_date => { lt => '15/12/2005 6:30 p.m.' },
name => { like => [ '%foo%', '%bar%' ] },
],
sort_by => 'category_id, start_date DESC',
limit => 100,
offset => 80,
);
while($product = $iterator->next)
{
print $product->id, ' ', $product->name, "\n";
}
print $iterator->total;
#
# Get objects count
#
$count =
Product::Manager->get_products_count
(
query =>
[
category_id => [ 5, 7, 22 ],
status => 'active',
start_date => { lt => '15/12/2005 6:30 p.m.' },
name => { like => [ '%foo%', '%bar%' ] },
],
);
die Product::Manager->error unless(defined $count);
print $count; # or Product::Manager->total()
#
# Get objects and sub-objects in a single query
#
$products =
Product::Manager->get_products
(
with_objects => [ 'category' ],
query =>
[
category_id => [ 5, 7, 22 ],
status => 'active',
start_date => { lt => '15/12/2005 6:30 p.m.' },
name => { like => [ '%foo%', '%bar%' ] },
],
sort_by => 'category_id, start_date DESC',
limit => 100,
offset => 80,
);
foreach my $product (@$products)
{
# The call to $product->category does not hit the database
print $product->name, ': ', $product->category->name, "\n";
}
DESCRIPTION
Rose::DB::Object::Manager
is a base class for classes that select rows from tables fronted by Rose::DB::Object-derived classes. Each row in the table(s) queried is converted into the equivalent Rose::DB::Object-derived object.
Class methods are provided for fetching objects all at once, one at a time through the use of an iterator, or just getting the object count. Subclasses are expected to create syntactically pleasing wrappers for Rose::DB::Object::Manager
class methods. A very minimal example is shown in the synopsis above.
CLASS METHODS
- error
-
Returns the text message associated with the last error, or false if there was no error.
- error_mode [MODE]
-
Get or set the error mode for this class. The error mode determines what happens when a method of this class encounters an error. The default setting is "fatal", which means that methods will croak if they encounter an error.
PLEASE NOTE: The error return values described in the method documentation in the rest of this document are only relevant when the error mode is set to something "non-fatal." In other words, if an error occurs, you'll never see any of those return values if the selected error mode dies or croaks or otherwise throws an exception when an error occurs.
Valid values of MODE are:
- carp
-
Call Carp::carp with the value of the object error as an argument.
- cluck
-
Call Carp::cluck with the value of the object error as an argument.
- confess
-
Call Carp::confess with the value of the object error as an argument.
- croak
-
Call Carp::croak with the value of the object error as an argument.
- fatal
-
An alias for the "croak" mode.
- return
-
Return a value that indicates that an error has occurred, as described in the documentation for each method.
In all cases, the class's
error
attribute will also contain the error message. - get_objects [PARAMS]
-
Get Rose::DB::Object-derived objects based on PARAMS, where PARAMS are name/value pairs. Returns a reference to a (possibly empty) array in scalar context, a list of objects in list context, or undef if there was an error.
Note that naively calling this method in list context may result in a list containing a single undef element if there was an error. Example:
# If there is an error, you'll get: @objects = (undef) @objects = Rose::DB::Object::Manager->get_objects(...);
If you want to avoid this, feel free to change the behavior in your wrapper method, or just call it in scalar context (which is more efficient anyway for long lists of objects).
Valid parameters are:
db DB
-
A Rose::DB-derived object used to access the database. If omitted, one will be created by calling the
init_db()
object method of theobject_class
. limit NUM
-
Return a maximum of NUM objects.
object_args HASHREF
-
A reference to a hash of name/value pairs to be passed to the constructor of each
object_class
object fetched, in addition to the values from the database. object_class CLASS
-
The class name of the Rose::DB::Object-derived objects to be fetched. This parameter is required; a fatal error will occur if it is omitted.
offset NUM
-
Skip the first NUM rows. If the database supports some sort of "limit with offset" syntax (e.g., "LIMIT 10 OFFSET 20") then it will be used. Otherwise, the first NUM rows will be fetched and then discarded.
This parameter can only be used along with the
limit
parameter, otherwise a fatal error will occur. -
If true,
db
will be passed to each Rose::DB::Object-derived object when it is constructed. Defaults to true. with_object OBJECTS
-
Also fetch sub-objects associated with foreign keys in the primary table, where OBJECTS is a reference to an array of foreign key names, as defined by the Rose::DB::Object::Metadata object for
object_class
.Another table will be added to the query for each foreign key listed. The "join" clauses will be added automatically based on the foreign key definitions. Note that (obviously) each foreign key table has to have a Rose::DB::Object-derived class fronting it. See the synopsis for a simple example.
Note: the
with_objects
list currently cannot be used to simultaneously fetch two objects that both front the same database table, but are of different classes. One workaround is to make one class use a synonym or alias for one of the tables. Another option is to make one table a trivial view of the other. The objective is to get the table names to be different for each different class (even if it's just a matter of letter case, if your database is not case-sensitive when it comes to table names). query PARAMS
-
The query parameters, passed as a reference to an array of name/value pairs. These PARAMS are used to formulate the "where" clause of the SQL query that, in turn, is used to fetch the objects from the database. Arbitrarily nested boolean logic is supported.
For the complete list of valid parameter names and values, see the build_select() function of the Rose::DB::Object::QueryBuilder module.
- get_objects_count [PARAMS]
-
Accepts the same arguments as
get_objects()
, but just returns the number of rows that would have been fetched, or undef if there was an error. - get_objects_iterator [PARAMS]
-
Accepts any valid
get_objects()
argument, but return a Rose::DB::Objects::Iterator object which can be used to fetch the objects one at a time, or undef if there was an error. - get_objects_sql [PARAMS]
-
Accepts the same arguments as
get_objects()
, but return the SQL query string that would have been used to fetch the objects (in scalar context), or the SQL query string and a reference to an array of bind values (in list context). - make_manager_methods PARAMS
-
Create convenience wrappers for Rose::DB::Object::Manager's get_objects(), get_objects_iterator(), and get_objects_count() class methods in the target class. These wrapper methods will not overwrite any existing methods in the target class. If there is an existing method with the same name, a fatal error will occur.
PARAMS can take several forms, depending on the calling context. For a call to make_manager_methods() to succeed, the following information must be determined:
object class
The class of the Rose::DB::Object-derived objects to be fetched or counted.
base name or method name
The base name is a string used as the basis of the method names. For example, the base name "products" would be used to create methods named "get_products", "get_products_count", and "get_products_iterator"
In the absence of a base name, an explicit method name may be provided instead. The method name will be used as is.
method types
The types of methods that should be generated. Each method type is a wrapper for a Rose::DB::Object::Manager class method. The mapping of method type names to actual Rose::DB::Object::Manager class methods is as follows:
Type Method -------- ---------------------- objects get_objects() iterator get_objects_iterator() count get_objects_count()
target class
The class that the methods should be installed in.
Here are all of the different ways that each of those pieces of information can be provided, either implicitly or explicitly as part of PARAMS.
object class
If an
object_class
parameter is passed in PARAMS, then its value is used as the object class. Example:$class->make_manager_methods(object_class => 'Product', ...);
If the
object_class
parameter is not passed, and if the target class inherits from Rose::DB::Object::Manager and has also defined anobject_class
method, then the return value of that method is used as the object class. Example:package Product::Manager; use Rose::DB::Object::Manager; our @ISA = qw(Rose::DB::Object::Manager); sub object_class { 'Product' } # Assume object_class parameter is not part of the ... below __PACKAGE__->make_manager_methods(...);
In this case, the object class would be
Product
.Finally, if none of the above conditions are met, one final option is considered. If the target class inherits from Rose::DB::Object, then the object class is set to the target class.
If the object class cannot be determined in one of the ways described above, then a fatal error will occur.
base name or method name
If a
base_name
parameter is passed in PARAMS, then its value is used as the base name for the generated methods. Example:$class->make_manager_methods(base_name => 'products', ...);
If the
base_name
parameter is not passed, and if there is only one argument passed to the method, then the lone argument is used as the base name. Example:$class->make_manager_methods('products');
(Note that, since the object class must be derived somehow, this will only work in one of the situations (described above) where the object class can be derived from the calling context or class.)
If a
methods
parameter is passed with a hash ref value, then each key of the hash is used as the base name for the method types listed in the corresponding value. (See method types below for more information.)If a key of the
methods
hash ends in "()", then it is taken as the method name and is used as is. For example, the key "foo" will be used as a base name, but the key "foo()" will be used as a method name.If the base name cannot be determined in one of the ways described above, then a fatal error will occur.
method types
If a base name is passed to the method, either as the value of the
base_name
parameter or as the sole argument to the method call, then all of the method types are created:objects
,iterator
, andcount
. Example:# Base name is "products", all method types created $class->make_manager_methods('products'); # Base name is "products", all method types created $class->make_manager_methods(base_name => products', ...);
(Again, note that the object class must be derived somehow.)
If a
methods
parameter is passed, then its value must be a reference to a hash whose keys are base names or method names, and whose values are method types or references to arrays of method types.If a key ends in "()", then it is taken as a method name and is used as is. Otherwise, it is used as a base name. For example, the key "foo" will be used as a base name, but the key "foo()" will be used as a method name.
If a key is a method name and its value specifies more than one method type, then a fatal error will occur. (It's impossible to have more than one method with the same name.)
Example:
# Make the following methods: # # * Base name: products; method types: objects, iterators # # get_products() # get_products_iterator() # # * Method name: product_count; method type: count # # product_count() # $class->make_manager_methods(..., methods => { 'products' => [ qw(objects iterator) ], 'product_count()' => 'count' });
If the value of the
methods
parameter is not a reference to a hash, or if both (or neither of) themethods
andbase_name
parameters are passed, then a fatal error will occur.target class
If a
target_class
parameter is passed in PARAMS, then its value is used as the target class. Example:$class->make_manager_methods(target_class => 'Product', ...);
If a
target_class
parameter is not passed, and if the calling class is not Rose::DB::Object::Manager, then the calling class is used as the target class. Otherwise, the class from which the method was called is used as the target class. Examples:# Target class is Product, regardless of the calling # context or the value of $class $class->make_manager_methods(target_class => 'Product', ...); package Foo; # Target class is Foo: no target_class parameter is passed # and the calling class is Rose::DB::Object::Manager, so # the class from which the method was called (Foo) is used. Rose::DB::Object::Manager->make_manager_methods( object_class => 'Bar', base_name => 'Baz'); package Bar; # Target class is Foo: no target_class parameter is passed # and the calling class is not Rose::DB::Object::Manager, # so the calling class (Foo) is used. Foo->make_manager_methods(object_class => 'Bar', base_name => 'Baz');
There's a lot of flexibility in this method's arguments (although some might use the word "confusion" instead), but the examples can be pared down to a few common usage scenarios.
The first is the recommended technique, as seen in the synopsis. Create a separate manager class that inherits from Rose::DB::Object::Manager, override the
object_class
method to specify the class of the objects being fetched, and then pass a lone base name argument to the call to make_manager_methods().package Product::Manager; use Rose::DB::Object::Manager; our @ISA = qw(Rose::DB::Object::Manager); sub object_class { 'Product' } __PACKAGE__->make_manager_methods('products');
The second example is used to install object manager methods directly into a Rose::DB::Object-derived class. I do not recommend this practice; I consider it "semantically impure" for the class that represents a single object to also be the class that's used to fetch multiple objects. Inevitably, classes grow, and I'd like the "object manager" class to be separate from the object class itself so they can grow happily in isolation, with no potential clashes.
Also, keep in mind that Rose::DB::Object and Rose::DB::Object::Manager have separate error_mode settings which must be synchronized or otherwise dealt with. Another advantage of using a separate Rose::DB::Object::Manager subclass (as described earlier) is that you can override the error_mode in your Rose::DB::Object::Manager subclass only, rather than overriding the base class Rose::DB::Object::Manager error_mode, which may affect other classes.
If none of that dissuades you, here's how to do it:
package Product; use Rose::DB::Object:; our @ISA = qw(Rose::DB::Object); __PACKAGE__->make_manager_methods('products');
Finally, sometimes you don't want or need to use make_manager_methods() at all. In fact, this method did not exist in earlier versions of this module. The formerly recommended way to use this class is still perfectly valid: subclass it and then call through to the base class methods.
package Product::Manager; use Rose::DB::Object::Manager; our @ISA = qw(Rose::DB::Object::Manager); sub get_products { shift->get_objects(object_class => 'Product', @_); } sub get_products_iterator { shift->get_objects_iterator(object_class => 'Product', @_); } sub get_products_count { shift->get_objects_count(object_class => 'Product', @_); }
Of course, these methods will all look very similar in each Rose::DB::Object::Manager-derived class. Creating these identically structured methods is exactly what make_manager_methods() automates for you.
But sometimes you want to customize these methods, in which case the "longhand" technique above becomes essential. For example, imagine that we want to extend the code in the synopsis, adding support for a
with_categories
parameter to theget_products()
method.Product::Manager->get_products(date_created => '10/21/2001', with_categories => 1); ... sub get_products { my($class, %args) @_; if(delete $args{'with_categories'}) # boolean flag { push(@{$args{'with_objects'}}, 'category'); } Rose::DB::Object::Manager->get_objects( %args, object_class => 'Product') }
Here we've coerced the caller-friendly
with_categories
boolean flag parameter into thewith_objects => [ 'category' ]
pair that Rose::DB::Object::Manager's get_objects() method can understand.This is the typical evolution of an object manager method. It starts out as being auto-generated by make_manager_methods(), then becomes customized as new arguments are added.
AUTHOR
John C. Siracusa (siracusa@mindspring.com)
COPYRIGHT
Copyright (c) 2005 by John C. Siracusa. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.