NAME
SPOPS::Iterator - Class to cycle through results and return SPOPS objects
SYNOPSIS
my $iter = $spops_class->fetch_iterator({ where => 'last_name like ?',
value => [ 'smi%' ] });
while ( $iter->has_next ) {
my $object = $iter->get_next;
print "Object ID: ", $object->id, " at position: ",
$iter->position, "\n";
}
DESCRIPTION
One of the problems with current SPOPS implementations is that retrieving groups of objects is an all-or-nothing affair -- you get a list with all instantiated objects or you do not retrive them in the first place. This can be quite memory intensive, particularly when a user executes a query that can return thousands of objects back at one time.
This class -- or, more specifically, implementations of the interface in this class -- aims to change that. Instead of returning a list of objects from a group retrieval, you get back an SPOPS::Iterator
object. This object has a simple interface to cycle forward through objects and let you deal with them one at a time.
It does not keep track of these for you -- once you request the SPOPS object through the get_next()
call, the iterator loses track of it. The iterator does keep track of the current count (on a 1-based scheme) and whether you are currently 'on' the first or last element.
It is important to state that this works within the realm of other SPOPS capabilities -- just like the fetch_group()
method, all objects returned will get checked for security, and if a user cannot see a certain object it does not get returned and the iterator moves onto the next object.
As a result, users will never create an SPOPS::Iterator
object themselves. Instead, the object is returned from a method in a SPOPS implementation class, such as SPOPS::DBI
.
The initial module documentation is for the interface; there is also a section of creating a subclass of this module for SPOPS authors.
PUBLIC METHODS
has_next()
Returns boolean value: true if there are further values to retrieve, false if not.
Example:
my $iter = $spops_class->fetch_iterator({ where => "active = 'yes'" });
while ( $iter->has_next ) {
...
}
Note that calling has_next()
on an iterator does not advance it through the list of SPOPS objects. To advance the iterator you must call get_next()
. A common error might be something like:
my $iter = $spops_class->fetch_iterator({ where => "active = 'yes'" });
while ( $iter->has_next ) {
print "On record number: ", $iter->position, "\n";
}
Which will simply print:
On record number: 1
On record number: 1
On record number: 1
On record number: 1
...
Since the iterator is never advancing.
position()
Returns the position of the last item fetched.
So if you start up an iterator and execute the following code:
my $iter = $spops_class->fetch_iterator({ where => "active = 'yes'" });
my $obj = $iter->get_next;
print "Position is: ", $iter->position, "\n";
my $another_obj = $iter->get_next;
print "Position is: ", $iter->position, "\n";
It would print:
Position is: 1
Position is: 2
Note that if you have specified to retrieve only a certain number of records the position will indicate this:
my $iter = $spops_class->fetch_iterator({ ..., limit => '10,20' });
my $obj = $iter->get_next;
print "Position is: ", $iter->position, "\n";
my $another_obj = $iter->get_next;
print "Position is: ", $iter->position, "\n";
Would print:
Position is: 10
Position is: 11
Since you requested to fetch the values from 10 to 20.
is_first()
Returns true if the last item fetched is the first one.
is_last()
Returns true is the lsat item fetched is the last one.
is_done()
Alias for is_last()
get_next()
Returns next SPOPS object in the iterator if there is one to return, or undef
otherwise. Also advances the iterator to the next element. Should be wrapped in an eval
block to trap errors. If an error is generated you can get the message from $@
and also get additional information by requesting:
my $error_info = SPOPS::Error->get;
Example:
my $iter = $spops_class->fetch_iterator({ where => "active = 'yes'" });
while ( $iter->has_next() ) {
my $object = $iter->get_next;
my $related_objects = $object->related;
...
}
You can also do this:
my $iter = $spops_class->fetch_iterator({ where => "active = 'yes'" });
while ( my $object = $iter->get_next ) {
my $related_objects = $object->related;
...
}
This is arguably the more perlish way to do it, and both interfaces are currently supported.
get_all()
Returns an arrayref of all remaining SPOPS objects in the iterator.
Example:
my $iter = $spops_class->fetch_iterator({ where => "active = 'yes'" });
my $object_one = $iter->get_next;
my $object_two = $iter->get_next;
my $object_remaining_list = $iter->get_all();
discard()
Tells the iterator that you are done with the results and that it can perform any cleanup actions. The iterator will still exist after you call discard()
, but you cannot fetch any more records, the is_done()
method will return true and the position()
method will return the position of the last item fetched.
The is_last()
method will also return true. This might seem counterintuitive since you never reached the end of the iterator, but since you closed the iterator yourself this seems the right thing to do.
Example:
my $iter = $spops_class->fetch_iterator({ where => "active = 'yes'" });
my ( $penguin );
while ( my $object = $iter->get_next ) {
if ( $object->{last_name} eq 'Lemieux' ) {
$penguin = $object;
$iter->discard;
last;
}
print "Player $object->{first_name} $object->{last_name} is not Mario.\n";
}
from_list( \@objects )
As a convenience you can create an iterator from an existing list of objects. The utility of this might not be immediately obvious -- if you already have a list, what do you need an iterator for? But this allows you to create one set of code for object lists while allowing your code to accept both object lists and object iterators. For instance:
unless( $params->{iterator} {
$params->{iterator} = SPOPS::Iterator->from_list( $params->{list} );
}
my $template = Template->new;
$template->process( \*DATA, $params );
__DATA__
Object listing:
[% WHILE ( object = iterator.get_next ) -%]
Object: [% object.name %] (ID: [% object.id %])
[% END -%]
INTERNAL DOCUMENTATION
Methods documented below are not meant to be called by users of an iterator object. This documentation is meant for SPOPS driver authors who need to implement their own SPOPS::Iterator
subclasses.
Subclassing SPOPS::Iterator
Creating a subclass of SPOPS::Iterator
is quite easy. Subclass authors only need to override the following methods:
initialize()
fetch_object()
finish()
Everything else is done for you.
new( \%params )
The constructor is generally called behind the scenes -- the only people having to deal with it are SPOPS driver authors.
The constructor takes a single hashref for an argument. The keys and values of the hashref depend on the driver implementation, but some consistent ones are:
where: Set conditions for fetching
value: Set values to be used in the where clause
skip_security: Set to true if you want to skip security checks.
skip_cache: Set to true if you want to bypass the cache. (Since the cache is not yet implemented, this has no effect.)
limit: Either a max value ('n') or an offset and max ('m,n') which limits the return results. This way you can run a query to fetch lots of objects but only have the iterator return objects 30 - 39.
The constructor should do all the work necessary to setup a query, take the returned setup value and keep it around so it can call on it repeatedly as requested.
initialize( \%params )
Coders implementing the interface of SPOPS::Iterator
should create a method initialize()
which takes the values passed into new()
and gets the iterator ready to rumble. This might be preparing a SQL statement and executing it, or opening up a file and positioning the seek at the first record: whatever.
There is no return value from this method. If you encounter an error you should throw a SPOPS::Exception object or appropriate subclass.
fetch_object()
Internal method that must be overridden by a SPOPS::Iterator
implementation. This is what actually does the work for retrieving the next object.
Return value is a list with two members. The first is the object returned, the second is the count of the object. This count becomes the return value for position()
.
If the list is exhausted, simply return the constant ITER_IS_DONE
, which is exported from SPOPS::Iterator
.
load_next()
Loads the object into the 'next' slot.
finish()
Internal method for cleaning up resources. It is called when the user calls discard()
on an iterator, when the fetch_object()
method returns that it is done and, as a last resort, when the iterator object is garbage-collected and the DESTROY()
method called.
If necessary, SPOPS::Iterator
implementors should override this method to perform whatever cleanup actions are necessary for the iterator -- closing the database statement handle, closing the file, etc. If you do not override it we tell the object it is finished and no cleanup is done beyond what is done normally by Perl when variables go out of scope.
Note that the overridden method should set:
$self->{ ITER_FINISHED() }
to a true value to let the iterator know that it has been cleaned up. This way we will not call finish()
a second time when the object is garbage-collected.
DESTROY()
Ensure that an iterator is properly cleaned up when it goes out of scope.
BUGS
None yet!
TO DO
Provide more 'position' management
Subclasses generally need to maintain the position themselves, which can be irritating.
Relationship calls return iterators
Relationship calls (for relationships created by SPOPS::ClassFactory and/or one of its utilized behaviors) should be modified to optionally return an SPOPS::Iterator
object. So you could do:
my $iter = $user->group({ iterator => 1 });
while ( my $group = $iter->get_next ) {
print "User is in group: $group->{name}\n";
}
Other options:
my $iter = $user->group_iterator();
my $iter = $user->relation_iterator( 'group' );
SEE ALSO
Talks and papers by Mark-Jason Dominus on infinite lists and iterators. (See: http://www.plover.com/perl/)
COPYRIGHT
Copyright (c) 2001-2002 intes.net, inc.. All rights reserved.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
AUTHORS
Chris Winters <chris@cwinters.com>