Rose::DB::Object::Manager(3pm) | User Contributed Perl Documentation | Rose::DB::Object::Manager(3pm) |
Rose::DB::Object::Manager - Fetch multiple Rose::DB::Object-derived objects from the database using complex queries.
## ## Given the following Rose::DB::Object-derived classes... ## package Category; use base 'Rose::DB::Object'; __PACKAGE__->meta->setup ( table => 'categories', columns => [ id => { type => 'int', primary_key => 1 }, name => { type => 'varchar', length => 255 }, description => { type => 'text' }, ], unique_key => 'name', ); ... package CodeName; use base 'Rose::DB::Object'; __PACKAGE__->meta->setup ( table => 'code_names', columns => [ id => { type => 'int', primary_key => 1 }, product_id => { type => 'int' }, name => { type => 'varchar', length => 255 }, applied => { type => 'date', not_null => 1 }, ], foreign_keys => [ product => { class => 'Product', key_columns => { product_id => 'id' }, }, ], ); ... package Product; use base 'Rose::DB::Object'; __PACKAGE__->meta->setup ( table => 'products', columns => [ id => { type => 'int', primary_key => 1 }, name => { type => 'varchar', length => 255 }, description => { type => 'text' }, category_id => { type => 'int' }, region_num => { 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' }, ], unique_key => 'name', foreign_keys => [ category => { class => 'Category', key_columns => { category_id => 'id', } }, ], relationships => [ code_names => { type => 'one to many', class => 'CodeName', column_map => { id => 'product_id' }, manager_args => { sort_by => CodeName->meta->table . '.applied DESC', }, }, ], ); ... ## ## Create a manager class ## package Product::Manager; use base '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'); # } # # sub update_products # { # shift->update_objects(@_, object_class => 'Product'); # } # # sub delete_products # { # shift->delete_objects(@_, 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', 'code_names' ], query => [ category_id => [ 5, 7, 22 ], status => 'active', start_date => { lt => '15/12/2005 6:30 p.m.' }, # We need to disambiguate the "name" column below since it # appears in more than one table referenced by this query. # When more than one table is queried, the tables have numbered # aliases starting from the "main" table ("products"). The # "products" table is t1, "categories" is t2, and "code_names" # is t3. You can read more about automatic table aliasing in # the documentation for the get_objects() method below. # # "category.name" and "categories.name" would work too, since # table and relationship names are also valid prefixes. 't2.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"; # The call to $product->code_names does not hit the database foreach my $code_name ($product->code_names) { # This call doesn't hit the database either print $code_name->name, "\n"; } } # # Update objects # $num_rows_updated = Product::Manager->update_products( set => { end_date => DateTime->now, region_num => { sql => 'region_num * -1' } status => 'defunct', }, where => [ start_date => { lt => '1/1/1980' }, status => [ 'active', 'pending' ], ]); # # Delete objects # $num_rows_deleted = Product::Manager->delete_products( where => [ status => [ 'stale', 'old' ], name => { like => 'Wax%' }, or => [ start_date => { gt => '2008-12-30' }, end_date => { gt => 'now' }, ], ]);
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, either manually or with the make_manager_methods method. A very minimal example is shown in the synopsis above.
If the first argument is a reference to a hash or array, it is converted to a reference to an array (if necessary) and taken as the value of the "where" parameter.
Valid parameters are:
For the complete list of valid parameter names and values, see the documentation for the "query" parameter of the build_select function in the Rose::DB::Object::QueryBuilder module.
If this parameter is omitted, this method will refuse to delete all rows from the table and a fatal error will occur. To delete all rows from a table, you must pass the "all" parameter with a true value. If both the "all" and the "where" parameters are passed, a fatal error will occur.
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:
In all cases, the class's "error" attribute will also contain the error message.
If the first argument is a reference to a hash or array, it is converted to a reference to an array (if necessary) and taken as the value of the "query" parameter.
Each table that participates in the query will be aliased. Each alias is in the form "tN" where "N" is an ascending number starting with 1. The tables are numbered as follows.
"Many to many" relationships have two corresponding tables, and therefore will use two "tN" numbers. All other supported of relationship types only have just one table and will therefore use a single "tN" number.
For example, imagine that the "Product" class shown in the synopsis also has a "many to many" relationship named "colors." Now consider this call:
$products = Product::Manager->get_products( require_objects => [ 'category' ], with_objects => [ 'code_names', 'colors' ], multi_many_ok => 1, query => [ status => 'defunct' ], sort_by => 't1.name');
The "products" table is "t1" since it's the primary table--the table behind the "Product" class that "Product::Manager" manages. Next, the "with_objects" tables are aliased. The "code_names" table is "t2". Since "colors" is a "many to many" relationship, it gets two numbers: "t3" and "t4". Finally, the "require_objects" tables are numbered: the table behind the foreign key "category" is "t5". Here's an annotated version of the example above:
# Table aliases in the comments $products = Product::Manager->get_products( # t5 require_objects => [ 'category' ], # t2 t3, t4 with_objects => [ 'code_names', 'colors' ], multi_many_ok => 1, query => [ status => 'defunct' ], sort_by => 't1.name'); # "products" is "t1"
Also note that the "multi_many_ok" parameter was used in order to suppress the warning that occurs when more than one "... to many" relationship is included in the combination of "require_objects" and "with_objects" ("code_names" (one to many) and "colors" (many to many) in this case). See the documentation for "multi_many_ok" below.
The "tN" table aliases are for convenience, and to isolate end-user code from the actual table names. Ideally, the actual table names should only exist in one place in the entire code base: in the class definitions for each Rose::DB::OBject-derived class.
That said, when using Rose::DB::Object::Manager, the actual table names can be used as well. But be aware that some databases don't like a mix of table aliases and real table names in some kinds of queries.
Valid parameters to get_objects are:
@ids = (); # empty list Product::Manager->get_products( query => [ id => \@ids, ... ]);
By default, passing an empty list as a value will cause a fatal error.
If set to a simple scalar value that is true, then only the columns in the primary table ("t1") are fetched from the database.
If set to a reference to an array of table names, "tN" table aliases, or relationship or foreign key names, then only the columns from the corresponding tables will be fetched. In the case of relationships that involve more than one table, only the "most distant" table is considered. (e.g., The map table is ignored in a "many to many" relationship.) Columns from the primary table ("t1") are always selected, regardless of whether or not it appears in the list.
This parameter conflicts with the "fetch_only" parameter in the case where both provide a list of table names or aliases. In this case, if the value of the "distinct" parameter is also reference to an array table names or aliases, then a fatal error will occur.
This parameter conflicts with the "distinct" parameter in the case where both provide a list of table names or aliases. In this case, then a fatal error will occur.
for_update => 1
is equivalent to this:
lock => { type => 'for update' }
See the lock parameter below for more information.
<http://dev.mysql.com/doc/refman/5.0/en/select.html>
The hints hash is keyed by tN table aliases or relationship names. The value of each key is a reference to a hash of hint directives. In the absence of any key for "t1" or the name of the primary table, the entire hints hash is considered applicable to the primary table.
Valid hint directives are:
This parameter is useful for situations where the performance of get_objects is limited by the speed at which Rose::DB::Object-derived objects can be created. It's safe to set this parameter to true only if the constructor and column mutator methods for all of the classes involved do not have any side-effects (or if it's is okay to bypass any side-effects).
The default Rose::DB::Object constructor and the column mutator methods created by the column classes included in the Rose::DB::Object module distribution do not have any side-effects and should therefore be safe to use with this parameter.
The default value is determined by the default_limit_with_subselect class method.
The value should be a reference to a hash or a TYPE string, which is equivalent to setting the value of the "type" key in the hash reference form. For example, these are both equivalent:
lock => 'for update' lock => { type => 'for update' }
Valid hash keys are:
For example, "vendor.region.name" would lock the "name" column in the table arrived at by traversing the "vendor" and then the "region" relationships, starting from the primary table ("t1"). Lone column names may also be used, provided they're not ambiguous.
For locking whole tables, "vendor.region" would lock the table arrived at by traversing the "vendor" and then the "region" relationships. (See the require_objects parameter for more information on relationship traversal.)
Finally, references to scalars will be de-referenced and used as-is, included literally in the SQL locking clause.
You may pass only one of the parameters that specifies "what to lock" (i.e., "columns", "on", or "tables").
The default value is determined by the default_nested_joins class method.
If the value is a true boolean value (typically "1"), then all columns will be fetched for all participating classes (i.e., the main object class as well as any sub-object classes).
The value can also be a reference to an array of relationship names. The sub-objects corresponding to each relationship name will have all their columns selected. To refer to the main class (the "t1" table), use the special name "self".
This parameter can only be used along with the "limit" parameter, otherwise a fatal error will occur.
The number of objects per page can be set by the "per_page" parameter. If the "per_page" parameter is supplied and this parameter is omitted, it defaults to 1 (the first page).
If this parameter is included along with either of the "limit" or <offset> parameters, a fatal error will occur.
If this parameter is included along with either of the "limit" or <offset> parameters, a fatal error will occur.
For the complete list of valid parameter names and values, see the documentation for the "query" parameter of the build_select function in the Rose::DB::Object::QueryBuilder module.
This class also supports an extension to the query syntax supported by Rose::DB::Object::QueryBuilder. In addition to table names and aliases, column (or column method) names may be prefixed with foreign key or relationship names. These names may be chained, with dots (".") separating the components.
For example, imagine three tables, "products", "vendors", and "regions", fronted by three Rose::DB::Object-derived classes, "Product", "Vendor", and "Region", respectively. Each "Product" has a "Vendor", and each "Vendor" has a "Region".
To select only products whose vendors are in the United States, use a query argument like this:
query => [ 'vendor.region.name' => 'US' ],
This assumes that the "Product" class has a relationship or foreign key named "vendor" that points to the product's "Vendor", and that the "Vendor" class has a foreign key or relationship named "region" that points to the vendor's "Region", and that 'vendor.region' (or any foreign key or relationship name chain that begins with 'vendor.region.') is an argument to the "with_objects" or "require_objects" parameters.
Please note that the "tN" table aliases are not allowed in front of these kinds of chained relationship parameters. (The chain of relationship names specifies the target table, so any "tN" alias would be redundant at best, or present a conflict at worst.)
For each foreign key or relationship name listed in ARRAYREF, another table will be added to the query via an implicit inner join. The join conditions will be constructed automatically based on the foreign key or relationship definitions. Note that each related table must have a Rose::DB::Object-derived class fronting it.
Foreign key and relationship names may be chained, with dots (".") separating each name. For example, imagine three tables, "products", "vendors", and "regions", fronted by three Rose::DB::Object-derived classes, "Product", "Vendor", and "Region", respectively. Each "Product" has a "Vendor", and each "Vendor" has a "Region".
To fetch "Product"s along with their "Vendor"s, and their vendors' "Region"s, provide a "with_objects" argument like this:
require_objects => [ 'vendor.region' ],
This assumes that the "Product" class has a relationship or foreign key named "vendor" that points to the product's "Vendor", and that the "Vendor" class has a foreign key or relationship named "region" that points to the vendor's "Region".
This chaining syntax can be used to traverse relationships of any kind, including "one to many" and "many to many" relationships, to an arbitrary depth.
The following optional suffixes may be added after any name in the chain in order to override the join type used:
Suffix Join Type ------ ---------- ! Inner join ? Left outer join
Each link in a "require_objects" chain uses an inner join by default. In other words, the following "require_objects" parameters are all equivalent:
# These all mean the same thing require_objects => [ 'vendor.region' ] require_objects => [ 'vendor!.region!' ] require_objects => [ 'vendor.region!' ] require_objects => [ 'vendor!.region' ]
Thus, it is only really useful to use the "?" suffix in "require_objects" parameters (though the "!" suffixes don't do any harm). Here's a useful example of a call with hybrid join chain:
$products = Product::Manager->get_products( require_objects => [ 'vendor.region?' ]);
All product objects returned would have associated vendor objects, but those vendor objects may or may not have associated region objects.
Note that inner joins may be implicit and nested_joins may or may not be used. When in doubt, use the debug parameter to see the generated SQL.
Warning: there may be a geometric explosion of redundant data returned by the database if you include more than one "... to many" relationship in ARRAYREF. Sometimes this may still be more efficient than making additional queries to fetch these sub-objects, but that all depends on the actual data. A warning will be emitted (via Carp::cluck) if you include more than one "... to many" relationship in ARRAYREF. If you're sure you know what you're doing, you can silence this warning by passing the "multi_many_ok" parameter with a true value.
Note: the "require_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).
Column names should be prefixed by the appropriate "tN" table alias, the table name, or the foreign key or relationship name. The prefix should be joined to the column name with a dot ("."). Examples: "t2.name", "vendors.age".
Unprefixed columns are assumed to belong to the primary table ("t1") and are explicitly prefixed as such when selecting from more than one table. If a column name matches "/ AS \w+$/" then no prefix is applied.
If the column name is "*" (e.g., "t1.*") then all columns from that table are selected.
If an item in the referenced array is itself a reference to a scalar, then that item will be dereferenced and passed through unmodified.
If selecting sub-objects via the "with_objects" or "require_objects" parameters, you must select the primary key columns from each sub-object table. Failure to do so will cause those sub-objects not to be created.
Be warned that you should provide some way to determine which column or method and which class an item belongs to: a tN prefix, a column name, or at the very least an "... AS ..." alias clause.
If any "with_objects" or "require_objects" arguments are included in this call, the "select" list must include at least the primary key column(s) from each table that contributes to the named relationships.
This parameter conflicts with the "fetch_only" parameter. A fatal error will occur if both are used in the same call.
If this parameter is omitted, then all columns from all participating tables are selected (optionally modified by the "nonlazy" parameter).
If an argument is a reference to a scalar, then it is passed through to the ORDER BY clause unmodified.
Otherwise, within each string, any instance of "NAME." will be replaced with the appropriate "tN." table alias, where NAME is a table, foreign key, or relationship name. All unprefixed simple column names are assumed to belong to the primary table ("t1").
If selecting sub-objects (via "require_objects" or "with_objects") that are related through "one to many" or "many to many" relationships, the first condition in the sort order clause must be a column in the primary table (t1). If this condition is not met, the list of primary key columns will be added to the beginning of the sort order clause automatically.
SELECT t1.id AS t1_id, t1.name AS t1_name, t2.id AS t2_id, t2.name AS t2_name FROM foo AS t1, bar AS t2 WHERE ...
These unique aliases provide a technique of last resort for unambiguously addressing a column in a query clause.
If the value is "1", then each object fetched through a mapping table will have its associated map record available through a "map_record()" attribute.
If a method name is provided instead, then each object fetched through a mapping table will have its associated map record available through a method of that name.
If the value is a reference to a hash, then the keys of the hash should be "many to many" relationship names, and the values should be the method names through which the maps records will be available for each relationship.
For each foreign key or relationship name listed in ARRAYREF, another table will be added to the query via an explicit LEFT OUTER JOIN. (Foreign keys whose columns are all NOT NULL are the exception, however. They are always fetched via inner joins.) The join conditions will be constructed automatically based on the foreign key or relationship definitions. Note that each related table must have a Rose::DB::Object-derived class fronting it. See the synopsis for an example.
"Many to many" relationships are a special case. They will add two tables to the query (the "map" table plus the table with the actual data), which will offset the "tN" table numbering by one extra table.
Foreign key and relationship names may be chained, with dots (".") separating each name. For example, imagine three tables, "products", "vendors", and "regions", fronted by three Rose::DB::Object-derived classes, "Product", "Vendor", and "Region", respectively. Each "Product" has a "Vendor", and each "Vendor" has a "Region".
To fetch "Product"s along with their "Vendor"s, and their vendors' "Region"s, provide a "with_objects" argument like this:
with_objects => [ 'vendor.region' ],
This assumes that the "Product" class has a relationship or foreign key named "vendor" that points to the product's "Vendor", and that the "Vendor" class has a foreign key or relationship named "region" that points to the vendor's "Region".
This chaining syntax can be used to traverse relationships of any kind, including "one to many" and "many to many" relationships, to an arbitrary depth.
The following optional suffixes may be added after any name in the chain in order to override the join type used:
Suffix Join Type ------ ---------- ! Inner join ? Left outer join
Each link in a "with_objects" chain uses a left outer join by default. In other words, the following "with_objects" parameters are all equivalent:
# These all mean the same thing with_objects => [ 'vendor.region' ] with_objects => [ 'vendor?.region?' ] with_objects => [ 'vendor.region?' ] with_objects => [ 'vendor?.region' ]
Thus, it is only really useful to use the "!" suffix in "with_objects" parameters (though the "?" suffixes don't do any harm). Here's a useful example of a call with hybrid join chain:
$products = Product::Manager->get_products( with_objects => [ 'vendor!.region' ]);
All product objects returned would have associated vendor objects, but those vendor object may or may not have associated region objects.
Note that inner joins may be implicit and nested_joins may or may not be used. When in doubt, use the debug parameter to see the generated SQL.
Warning: there may be a geometric explosion of redundant data returned by the database if you include more than one "... to many" relationship in ARRAYREF. Sometimes this may still be more efficient than making additional queries to fetch these sub-objects, but that all depends on the actual data. A warning will be emitted (via Carp::cluck) if you include more than one "... to many" relationship in ARRAYREF. If you're sure you know what you're doing, you can silence this warning by passing the "multi_many_ok" parameter with a true value.
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).
Each column returned by the SQL query must be either a column or method name in "object_class". Column names take precedence in the case of a conflict.
Returns a reference to an array of "object_class" objects.
Examples:
package Product::Manager; use Product; use base 'Rose::DB::Object::Manager'; sub object_class { 'Product' } ... $products = Product::Manager->get_objects_from_sql(<<"EOF"); SELECT * FROM products WHERE sku % 2 != 0 ORDER BY status, type EOF $products = Product::Manager->get_objects_from_sql( args => [ '2005-01-01' ], sql => 'SELECT * FROM products WHERE release_date > ?');
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:
The class of the Rose::DB::Object-derived objects to be fetched or counted.
The base name is a string used as the basis of the method names. For example, the base name "products" might be used to create methods named "get_products", "get_products_count", "get_products_iterator", "delete_products", and "update_products".
In the absence of a base name, an explicit method name may be provided instead. The method name will be used as is.
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 defaults to the following:
Type Method -------- ---------------------- objects get_objects() iterator get_objects_iterator() count get_objects_count() delete delete_objects() update update_objects()
You may override the auto_manager_method_name method in the object_class's convention manager class to customize one or more of these names.
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.
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 an "object_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.
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 the auto_manager_base_name method in the object_class's convention manager is called on to supply a base name.
If an explicit list of method types is not passed to the method, then all of the default_manager_method_types are created. Example:
# Base name is determined by convention manager auto_manager_base_name() # method, all default method types created $class->make_manager_methods(); # Base name is "products", all default method types created $class->make_manager_methods('products'); # Base name is "products", all default 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 the "methods" and "base_name" parameters are passed, then a fatal error will occur.
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', @_); } sub delete_products { shift->delete_objects(object_class => 'Product', @_); } sub update_products { shift->update_objects(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 the "get_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 the "with_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.
Pass either a method name and an SQL query string or name/value parameters as arguments. Valid parameters are:
Each column returned by the SQL query must be either a column or method name in "object_class". Column names take precedence in the case of a conflict.
Arguments passed to the created method will be passed to DBI's execute method when the query is run. The number of arguments must exactly match the number of placeholders in the SQL query. Positional parameters are required unless the "params" parameter is used. (See description above.)
Returns a code reference to the method created.
Examples:
package Product::Manager; use base 'Rose::DB::Object::Manager'; ... # Make method that takes no arguments __PACKAGE__->make_manager_method_from_sql(get_odd_products =><<"EOF"); SELECT * FROM products WHERE sku % 2 != 0 EOF # Make method that takes one positional parameter __PACKAGE__->make_manager_method_from_sql(get_new_products =><<"EOF"); SELECT * FROM products WHERE release_date > ? EOF # Make method that takes named parameters __PACKAGE__->make_manager_method_from_sql( method => 'get_named_products', params => [ qw(type name) ], sql => <<"EOF"); SELECT * FROM products WHERE type = ? AND name LIKE ? EOF ... $products = Product::Manager->get_odd_products(); $products = Product::Manager->get_new_products('2005-01-01'); $products = Product::Manager->get_named_products( name => 'Kite%', type => 'toy'); # Make method that takes named parameters and returns an iterator __PACKAGE__->make_manager_method_from_sql( method => 'get_named_products_iterator', iterator => 1, params => [ qw(type name) ], sql => <<"EOF"); SELECT * FROM products WHERE type = ? AND name LIKE ? EOF $iterator = Product::Manager->get_named_products_iterator( name => 'Kite%', type => 'toy'); while(my $product = $iterator->next) { ... # do something with $product $iterator->finish if(...); # finish early? }
package Product::Manager; use base 'Rose::DB::Object::Manager'; use Product; sub object_class { 'Product' } ... sub get_products { my($class, %args) = shift->normalize_get_objects_args(@_); # Detect, extract, and handle custom argument if(delete $args{'active_only'}) { push(@{$args{'query'}}, status => 'active'); } return $class->get_objects(%args); # call through to normal method }
Now all of the following calls will work:
$products = Product::Manager->get_products([ type => 'boat' ], sort_by => 'name'); $products = Product::Manager->get_products({ name => { like => '%Dog%' } }); $products = Product::Manager->get_products([ id => { gt => 123 } ], active_only => 1);
The Perl code is returned as a string. Here's an example:
package My::Product::Manager; use My::Product; use Rose::DB::Object::Manager; our @ISA = qw(Rose::DB::Object::Manager); sub object_class { 'My::Product' } __PACKAGE__->make_manager_methods('products'); 1;
Valid parameters are:
If a value is a reference to a scalar, then it is dereferenced and incorporated into the SQL query as-is.
If a value is a reference to a hash, then it must contain a single key named "sql" and a corresponding value that will be incorporated into the SQL query as-is.
Example:
$num_rows_updated = Product::Manager->update_products( set => { end_date => DateTime->now, region_num => { sql => 'region_num * -1' } count => \q(count + 1), status => 'defunct', }, where => [ status => [ 'stale', 'old' ], name => { like => 'Wax%' } or => [ start_date => { gt => '2008-12-30' }, end_date => { gt => 'now' }, ], ]);
The call above would execute an SQL statement something like the one shown below (depending on the database vendor, and assuming the current date was September 20th, 2005):
UPDATE products SET end_date = '2005-09-20', region_num = region_num * -1, count = count + 1, status = 'defunct' WHERE status IN ('stale', 'old') AND name LIKE 'Wax%' AND ( start_date > '2008-12-30' OR end_date > '2005-09-20' )
For the complete list of valid parameter names and values, see the documentation for the "query" parameter of the build_select function in the Rose::DB::Object::QueryBuilder module.
If this parameter is omitted, this method will refuse to update all rows in the table and a fatal error will occur. To update all rows in a table, you must pass the "all" parameter with a true value. If both the "all" and the "where" parameters are passed, a fatal error will occur.
For an informal overview of Rose::DB::Object, including Rose::DB::Object::Manager, consult the Rose::DB::Object::Tutorial.
perldoc Rose::DB::Object::Tutorial
Any Rose::DB::Object::Manager questions or problems can be posted to the Rose::DB::Object mailing list. To subscribe to the list or view the archives, go here:
<http://groups.google.com/group/rose-db-object>
Although the mailing list is the preferred support mechanism, you can also email the author (see below) or file bugs using the CPAN bug tracking system:
<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Rose-DB-Object>
There's also a wiki and other resources linked from the Rose project home page:
<http://rosecode.org>
John C. Siracusa (siracusa@gmail.com)
Copyright (c) 2010 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.
2022-10-14 | perl v5.34.0 |