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