The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Rose::DB::Object::Manager - Fetch multiple Rose::DB::Object-derived objects from the database using complex queries.

SYNOPSIS

  ##
  ## Given the following 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 CodeName;

  use Product;

  use Rose::DB::Object;
  our @ISA = qw(Rose::DB::Object);

  __PACKAGE__->meta->table('code_names');

  __PACKAGE__->meta->columns
  (
    id          => { type => 'int', primary_key => 1 },
    product_id  => { type => 'int' },
    name        => { type => 'varchar', length => 255 },
    applied     => { type => 'date', not_null => 1 },
  );

  __PACKAGE__->foreign_keys
  (
    product =>
    {
      class       => 'Product',
      key_columns => { product_id => 'id' },
    },
  );

  __PACKAGE__->meta->initialize;

  ...

  package Product;

  use Category;
  use CodeName;

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

  __PACKAGE__->meta->add_unique_key('name');

  __PACKAGE__->meta->foreign_keys
  (
    category =>
    {
      class       => 'Category',
      key_columns =>
      {
        category_id => 'id',
      }
    },
  );

  __PACKAGE__->meta->relationships
  (
    code_names =>
    {
      type  => 'one to many',
      class => 'CodeName',
      column_map   => { id => 'product_id' },
      manager_args => 
      {
        sort_by => CodeName->meta->table . '.applied DESC',
      },
    }
  );

  __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');
  # }
  #
  # 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' },
        ],
      ]);

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, either manually or with the make_manager_methods method. A very minimal example is shown in the synopsis above.

CLASS METHODS

default_objects_per_page [NUM]

Get or set the default number of items per page, as returned by the get_objects method when used with the page and/or per_page parameters. The default value is 20.

delete_objects [PARAMS]

Delete rows from a table fronted by a Rose::DB::Object-derived class based on PARAMS, where PARAMS are name/value pairs. Returns the number of rows deleted, or undef if there was an error.

Valid parameters are:

all BOOL

If set to a true value, this parameter indicates an explicit request to delete all rows from the table. If both the all and the where parameters are passed, a fatal error will occur.

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 the object_class.

object_class CLASS

The name of the Rose::DB::Object-derived class that fronts the table from which rows are to be deleted. This parameter is required; a fatal error will occur if it is omitted.

where 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 is used to delete the rows from the table. Arbitrarily nested boolean logic is supported.

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.

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).

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.

  • The primary table is always "t1"

  • The table(s) that correspond to each relationship or foreign key named in the with_objects parameter are numbered in order, starting with "t2"

  • The table(s) that correspond to each relationship or foreign key named in the require_objects parameter are numbered in order, starting where the with_objects table aliases left off.

"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 supress 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 codebase: 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:

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 the object_class.

distinct [BOOL|ARRAYREF]

If set to any kind of true value, then the "DISTINCT" SQL keyword will be added to the "SELECT" statement. Specific values trigger the behaviors described below.

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.

fetch_only [ARRAYREF]

ARRAYREF should be a reference to an array of table names or "tN" table aliases. 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 distinct parameter in the case where both provide a list of table names or aliases. In this case, then a fatal error will occur.

limit NUM

Return a maximum of NUM objects.

multi_many_ok BOOL

If true, do not print a warning when attempting to do multiple LEFT OUTER JOINs against tables related by "... to many" relationships. See the documentation for the with_objects parameter for more information.

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 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.

page NUM

Show page number NUM of objects. Pages are numbered starting from 1. A page number less than or equal to zero causes the page number to default to 1.

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.

per_page NUM

The number of objects per page. Defaults to the value returned by the default_objects_per_page class method (20, by default).

If this parameter is included along with either of the limit or <offset> parameters, a fatal error will occur.

require_objects OBJECTS

Only fetch rows from the primary table that have all of the associated sub-objects listed in OBJECTS, where OBJECTS is a reference to an array of foreign key or relationship names defined for object_class. The supported relationship types are "one to one," "one to many," and "many to many".

For each foreign key or relationship listed in OBJECTS, 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.

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).

share_db BOOL

If true, db will be passed to each Rose::DB::Object-derived object when it is constructed. Defaults to true.

sort_by CLAUSE | ARRAYREF

A fully formed SQL "ORDER BY ..." clause, sans the words "ORDER BY", or a reference to an array of strings to be joined with a comma and appended to the "ORDER BY" clause.

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").

with_objects OBJECTS

Also fetch sub-objects (if any) associated with rows in the primary table, where OBJECTS is a reference to an array of foreign key or relationship names defined for object_class. The supported relationship types are "one to one," "one to many," and "many to many".

For each foreign key or relationship listed in OBJECTS, 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

Warning: there may be a geometric explosion of redundant data returned by the database if you include more than one "... to many" relationship in OBJECTS. 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 you include more than one "... to many" relationship in OBJECTS. 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).

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 documentation for the query parameter of the build_select function in the Rose::DB::Object::QueryBuilder module.

This class also supports a useful extension to the query syntax supported by Rose::DB::Object::QueryBuilder. In addition to table names and aliases, column names may be prefixed with foreign key or relationship names as well.

get_objects_count [PARAMS]

Accepts the same arguments as get_objects(), but just returns the number of objects that would have been fetched, or undef if there was an error.

Note that the with_objects parameter is ignored by this method, since it counts the number of primary objects, irrespective of how many sub-objects exist for each primary object. If you want to count the number of primary objects that have sub-objects matching certain criteria, use the require_objects parameter instead.

get_objects_iterator [PARAMS]

Accepts any valid get_objects() argument, but return a Rose::DB::Object::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", "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.

  • 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()
        delete      delete_objects()
        update      update_objects()
  • 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 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.

  • 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, and count. 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) the methods and base_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', @_);
  }

  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.

update_objects [PARAMS]

Update rows in a table fronted by a Rose::DB::Object-derived class based on PARAMS, where PARAMS are name/value pairs. Returns the number of rows updated, or undef if there was an error.

Valid parameters are:

all BOOL

If set to a true value, this parameter indicates an explicit request to update all rows in the table. If both the all and the where parameters are passed, a fatal error will occur.

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 the object_class.

object_class CLASS

The class name of the Rose::DB::Object-derived class that fronts the table whose rows will to be updated. This parameter is required; a fatal error will occur if it is omitted.

set PARAMS

The names and values of the columns to be updated. PARAMS should be a reference to a hash. Each key of the hash should be a column name or column get/set method name. If a value is a simple scalar, then it is passed through the get/set method that services the column before being incorporated into the SQL query.

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. For example, this method call:

  $num_rows_updated =
    Product::Manager->update_products(
      set =>
      {
        end_date   => DateTime->now,
        region_num => { sql => 'region_num * -1' }
        status     => 'defunct',
      },
      where =>
      [
        status  => [ 'stale', 'old' ],
        name    => { like => 'Wax%' }
        or =>
        [
          start_date => { gt => '2008-12-30' },
          end_date   => { gt => 'now' },
        ],
      ]);

would produce the an SQL statement something like this (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,
      status     = 'defunct'
    WHERE
      status IN ('stale', 'old') AND
      name LIKE 'Wax%' AND
      (
        start_date > '2008-12-30' OR
        end_date   > '2005-09-20'
      )
where 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 is used to update the rows in the table. Arbitrarily nested boolean logic is supported.

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.

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.