package Class::PObject::Template;
# Template.pm,v 1.24 2005/02/20 18:05:00 sherzodr Exp
use strict;
#use diagnostics;
use Log::Agent;
use Carp;
use vars ('$VERSION');
use overload (
'""' => sub { $_[0]->id },
fallback=> 1
);
$VERSION = '1.93';
sub new {
my $class = shift;
$class = ref($class) || $class;
logtrc 2, "%s->new()", $class;
croak "Odd number of arguments passed to new(). May result in corrupted data" if @_ % 2;
my $props = $class->__props();
my $self = {
columns => { @_ }, # <-- holds key/value pairs
_is_new => 1
};
bless($self, $class);
# It's possible that new() was not given all the column/values. So we
# detect the ones missing, and assign them 'undef'
for my $colname ( @{$props->{columns}} ) {
unless ( defined $self->{columns}->{$colname} ) {
$self->{columns}->{$colname} = undef
}
}
$self->pobject_init;
return $self
}
#
# Extra init. code should be defined in parent
#
sub pobject_init { }
sub set_datasource {
$_[0]->__props()->{"datasource"} = $_[1] if defined( $_[1] );
}
sub set_driver {
$_[0]->__props()->{'driver'} = $_[1] if defined( $_[1] );
}
sub set {
my $self = shift;
my ($colname, $colvalue) = @_;
croak "set(): called as class method" unless ref( $self );
croak "set(): missing arguments" unless @_ == 2;
my $props = $self->__props();
my ($typeclass, $args) = $props->{tmap}->{$colname} =~ m/^([a-zA-Z0-9_:]+)(?:\(([^\)]+)\))?$/;
logtrc 3, "col: %s, type: %s, args: %s", $colname, $typeclass, $args;
if ( ref $colvalue eq $typeclass ) {
$self->{columns}->{$colname} = $colvalue;
} else {
$self->{columns}->{$colname} = $typeclass->new(id=>$colvalue);
}
}
sub get {
my ($self, $colname) = @_;
croak "get(): called as class method" unless ref( $self );
croak "get(): missing arguments" unless defined $colname;
my $colvalue = $self->{columns}->{$colname};
# If the value is undef, we should return it as is, not to surprise anyone.
# If we keep going, the user will end up with an object,
# which may not appear as empty
return unless defined( $colvalue );
# If we already have this value in our cache, let's return it
return $colvalue if ref( $colvalue );
# If we come this far, this value is being inquired for the first time. So we should load() it.
# To do this, we first need to identify its column type, to know how to inflate it.
my $props = $self->__props();
my ($typeclass, $args) = $props->{tmap}->{ $colname } =~ m/^([a-zA-Z0-9_:]+)(?:\(([^\)]+)\))?$/;
croak "set(): couldn't detect type of column '$colname'" unless $typeclass;
# We should cache the loaded object in the column
return $self->{columns}->{$colname} = $typeclass->load($colvalue);
}
sub save {
my $self = shift;
my $class = ref($self) || $self;
croak "save(): called as class method" unless ref $self;
logtrc 2, "%s->save(%s)", $class, join ", ", @_;
my $props = $self->__props();
my $driver_obj = $self->__driver();
my %columns = ();
while ( my ($k, $v) = each %{ $self->{columns} } ) {
# We should realize that column values are of Class::PObject::Type class,
# so their values should be stringified before being passed to drivers' save() method.
$v = $v->id while ref $v;
$columns{$k} = $v
}
# We call the driver's save() method, with the name of the class, all the props passed to pobject(),
# and column values to be stored
my $rv = $driver_obj->save($class, $props, \%columns);
unless ( defined $rv ) {
$self->errstr($driver_obj->errstr);
logerr $self->errstr;
return undef
}
$self->id($rv);
return $rv
}
sub fetch {
my $class = shift;
croak "fetch(): called as object method" if ref( $class );
my ($terms, $args) = @_;
$terms ||= {};
$args ||= {};
logtrc 2, "%s->fetch()", $class;
my $props = $class->__props();
my $driver = $class->__driver();
while ( my ($k, $v) = each %$terms ) {
$v = $v->id while ref $v;
$terms->{$k} = $v
}
my $ids = $driver->load_ids($class, $props, $terms, $args);
require Class::PObject::Iterator;
return Class::PObject::Iterator->new($class, $ids);
}
sub load {
my $class = shift;
croak "load(): called as object method" if ref($class);
my ($terms, $args) = @_;
#
# Initializing class attributes. This only makes difference if the class
# if making use of pobject_init()
#
$class->new();
logtrc 2, "%s->load()", $class;
$terms = {} unless defined $terms;
$args = {} unless defined $args;
# If we're called in void context, why bother?
return undef unless defined(wantarray);
unless ( wantarray ) {
$args->{"limit"} = 1;
$args->{"sort"} ||= 'id';
}
my $props = $class->__props();
my $driver_obj = $class->__driver();
my $ids = []; # we first initialize an empty ID list
# now, if we had a single argument, and that argument was not a HASH,
# we assume we received an ID
if ( defined($terms) && (ref $terms ne 'HASH') ) {
$ids = [ $terms ]
} else {
while ( my ($k, $v) = each %$terms ) {
if ( $props->{tmap}->{$k} =~ m/^(MD5|ENCRYPT)$/ ) {
carp "cannot select by '$1' type columns (Yet!)"
}
#
# Following trick will enable load(\%terms) syntax to work
# by passing objects.
#
$terms->{$k} = $terms->{$k}->id while ref $terms->{$k};
}
$ids = $driver_obj->load_ids($class, $props, $terms, $args) or return
}
return () unless scalar(@$ids);
# if called in array context, we return an array of objects:
if ( wantarray() ) {
my @data_set = ();
for my $id ( @$ids ) {
my $row = $driver_obj->load($class, $props, $id) or next;
my $o = $class->new( %$row );
$o->{_is_new} = 0;
push @data_set, $o
}
return @data_set
}
# if we come this far, we're being called in scalar context
my $row = $driver_obj->load($class, $props, $ids->[0]) or return;
my $o = $class->new( %$row );
$o->{_is_new} = 0;
return $o
}
sub remove {
my $self = shift;
croak "remove(): called as class method" unless ref($self);
logtrc 2, "%s->remove()", ref $self;
my $props = $self->__props();
my $driver_obj = $self->__driver();
# if 'id' field is missing, most likely it's because this particular object
# hasn't been saved into disk yet
croak "remove(): object id is missing. Cannot remove" unless defined $self->id;
my $rv = $driver_obj->remove( ref($self), $props, $self->id);
unless ( defined $rv ) {
$self->errstr($driver_obj->errstr);
return undef
}
return $rv
}
sub remove_all {
my $class = shift;
my ($terms) = @_;
croak "remove_all(): called as object method" if ref($class);
logtrc 2, "%s->remove_all()", $class;
$terms ||= {};
my $props = $class->__props();
my $driver_obj = $class->__driver();
while ( my ($k, $v) = each %$terms ) {
$v = $v->id while ref $v;
$terms->{$k} = $v
}
my $rv = $driver_obj->remove_all($class, $props, $terms);
unless ( defined $rv ) {
$class->errstr($driver_obj->errstr());
return undef
}
return 1
}
sub drop_datasource {
my $class = shift;
croak "drop_datasource(): called as object method" if ref( $class );
logtrc 2, "%s->drop_datasource", $class;
my $props = $class->__props();
my $driver_obj = $class->__driver();
my $rv = $driver_obj->drop_datasource($class, $props);
unless ( defined $rv ) {
$class->errstr( $driver_obj->errstr );
return undef
}
return 1
}
sub count {
my ($class, $terms) = @_;
croak "count(): called as object method" if ref ($class);
logtrc 2, "%s->count()", $class;
$terms ||= {};
my $props = $class->__props();
my $driver_obj = $class->__driver();
while ( my ($k, $v) = each %$terms ) {
$v = $v->id while ref $v;
$terms->{$k} = $v
}
return $driver_obj->count($class, $props, $terms)
}
sub errstr {
my $self = shift;
my $class = ref($self) || $self;
no strict 'refs';
if ( defined $_[0] ) {
${ "$class\::errstr" } = $_[0]
}
return ${ "$class\::errstr" }
}
sub columns {
my $self = shift;
my $class = ref($self) || $self;
logtrc 2, "%s->columns()", $class;
my %columns = ();
while ( my ($k, $v) = each %{$self->{columns}} ) {
$v = $v->id while ref $v;
$columns{$k} = $v;
}
return \%columns
}
sub dump {
my ($self, $indent) = @_;
require Data::Dumper;
my $d = Data::Dumper->new([$self], [ref $self]);
$d->Indent($indent||2);
$d->Deepcopy(1);
return $d->Dump()
}
sub __props {
my $class = shift;
#
# Can be called either as class or object method
#
no strict 'refs';
return ${ (ref($class) || $class) . '::props' }
}
sub __driver {
my $class = shift;
#
# Can be called either as class or object method
#
my $props = $class->__props();
my $pm = "Class::PObject::Driver::" . $props->{driver};
# closure for getting and setting driver object
my $get_set_driver = sub {
no strict 'refs';
if ( defined $_[0] ) {
${ "$pm\::__O" } = $_[0]
}
return ${ "$pm\::__O" }
};
my $driver_obj = $get_set_driver->();
return $driver_obj if defined $driver_obj;
#
# If we got this far, it's the first time the driver is
# required.
#
eval "require $pm";
if ( $@ ) {
logcroak $@
}
$driver_obj = $pm->new();
unless ( defined $driver_obj ) {
$class->errstr($pm->errstr);
return undef
}
$get_set_driver->($driver_obj);
return $driver_obj
}
package VARCHAR;
use vars ('@ISA');
require Class::PObject::Type::VARCHAR;
@ISA = ("Class::PObject::Type::VARCHAR");
package CHAR;
use vars ('@ISA');
require Class::PObject::Type::CHAR;
@ISA = ("Class::PObject::Type::CHAR");
package INTEGER;
use vars ('@ISA');
require Class::PObject::Type::INTEGER;
@ISA = ("Class::PObject::Type::INTEGER");
package TEXT;
use vars ('@ISA');
require Class::PObject::Type::TEXT;
@ISA = ("Class::PObject::Type::TEXT");
package ENCRYPT;
use vars ('@ISA');
require Class::PObject::Type::ENCRYPT;
@ISA = ("Class::PObject::Type::ENCRYPT");
package MD5;
use vars ('@ISA');
require Class::PObject::Type::MD5;
@ISA = ("Class::PObject::Type::MD5");
1;
__END__;
=pod
=head1 NAME
Class::PObject::Template - Class template for all the pobjects
=head1 DESCRIPTION
Class::PObject::Template defines the structure of all the classes created
through C<pobject()> construct.
All created pobjects are dynamically set to inherit from Class::PObject::Template.
=head1 NOTES
It would be nice if we had an option of setting an alternative template class
for pobjects individually.
=head1 AUTHOR and COPYRIGHT
For author and copyright information refer to L<Class::PObject|Class::PObject/>.
=cut