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.

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

share_db BOOL

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.

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

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.

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.