File Coverage

lib/Oryx/Class.pm
Criterion Covered Total %
statement 44 176 25.0
branch 10 68 14.7
condition 8 24 33.3
subroutine 11 33 33.3
pod 15 18 83.3
total 88 319 27.5


line stmt bran cond sub pod time code
1             package Oryx::Class;
2              
3 18     18   95 use Carp qw(carp croak);
  18         29  
  18         1061  
4 18     18   89 use UNIVERSAL qw(isa can);
  18         32  
  18         85  
5 18     18   5925 use Scalar::Util qw(weaken);
  18         40  
  18         2014  
6 18     18   89 use warnings;
  18         34  
  18         526  
7 18     18   88 use strict;
  18         28  
  18         773  
8 18     18   100 no strict 'refs';
  18         36  
  18         594  
9              
10 18     18   86 use base qw(Class::Data::Inheritable Class::Observable);
  18         45  
  18         17716  
11              
12             =head1 NAME
13              
14             Oryx::Class - abstract base class for Oryx classes
15              
16             =head1 SYNOPSIS
17            
18             # define a persistent class
19             package CMS::Page;
20             use base qw(Oryx::Class);
21            
22             # ... class meta-data here (see DEFINING CLASS META-DATA below) ...
23            
24             1;
25            
26             #===========================================================================
27             # use a persistent class
28             use CMS::Page;
29            
30             $page = CMS::Page->create({title => 'Life in the Metaverse'});
31             $page = CMS::Page->retrieve($id);
32            
33             $page->update;
34             $page->delete;
35            
36             @pages = CMS::Page->search({author => 'Richard Hun%'}, \@order, $limit, $offset);
37            
38             #===========================================================================
39             # commit your changes
40             $page->dbh->commit; # or simply ...
41             $page->commit;
42            
43             #===========================================================================
44             # attribute mutator
45             $page->title('The Metamanic Mechanic');
46             $tite = $page->title;
47            
48             #===========================================================================
49             # reference association mutator
50             $template_obj = $page->template;
51             $page->template( $template_obj );
52            
53             #===========================================================================
54             # array association accessor
55             $page->paragraphs->[0] = $intro_para;
56             $paragraph = $page->paragraphs->[42];
57            
58             #===========================================================================
59             # array association operators
60             $concl = pop @{$page->paragraphs};
61             $intro = shift @{$page->paragraphs};
62             push @{$page->paragraphs}, $concl;
63             unshift @{$page->paragraphs}, $new_intro;
64             splice @{$page->paragraphs}, 1, 4, ($summary);
65            
66             #===========================================================================
67             # hash association accessor
68             $image_obj = $page->images->{logo};
69             $page->images->{mug_shot} = $my_ugly_mug;
70             @keys = keys %{$page->images};
71             @values = values %{$page->images};
72            
73             #===========================================================================
74             # support for Class::Observable
75             Page->add_observer(sub {
76             my ($item, $action) = @_;
77             #...
78             });
79             $page->add_observer(...); # instance
80              
81             =head1 DESCRIPTION
82              
83             Abstract base class for Oryx persistent classes.
84              
85             =head1 ABSTRACT METHODS
86              
87             These methods are overridden by the implementing Class class, i.e.
88             L or L, for example, but the
89             interfaces stay the same, so they are documented here.
90              
91             =over
92              
93             =item B
94              
95             creates a persistent object using C<\%proto> to set up the initial state.
96              
97             =item B
98              
99             retrieves an object from storage by its object id
100              
101             =item B
102              
103             updates storage to persist and reflect changes in the object
104              
105             =item B
106              
107             deletes the object from storage
108              
109             =item B
110              
111             searches for objects with fields matching C<\%param>. SQL style
112             C<%> wildcards are supported. C<\@order>, C<$limit> and C<$offset> are
113             optional. C<\@order> is a list of columns which are used to sort the
114             results, C<$limit> is an integer which is used to limit the number of
115             results, and C<$offset> is used to exclude the first results up to
116             that number. These last two arguments are useful for paging through
117             search results.
118              
119             =item B
120              
121             commits the transaction if your database supports it and AutoCommit
122             is disabled, then you must do this.
123              
124             =back
125              
126             =head2 Observers
127              
128             Oryx::Class objects now unherit from L thereby
129             implementing a publish/subscribe system similar to triggers.
130              
131             The signals are named according to the 6 interface methods prefixed with
132             I and I, so the following signals are sent:
133              
134             =over 4
135              
136             =item before_create
137              
138             Handler is passed a hashref as argument with fields: C, the search parameters, and C, the L where clause
139              
140             =item after_create
141              
142             Handler is passed a hashref as argument with fields: C, the search parameters, and C, the hashref which will be blessed into an instance of this class (during 'construct')
143              
144             =item before_retrieve
145              
146             Handler is passed a hashref as argument with fields: C, the id of the object to fetch, and C, the L where clause
147              
148             =item after_retrieve
149              
150             Handler is passed a hashref as argument with fields: C, the hashref which will be blessed into an instance of this class (during 'construct')
151              
152             =item before_update
153              
154             Handler is passed a hashref as argument with fields: C, the L where clause.
155              
156             =item after_update
157              
158             Handler takes no arguments.
159              
160             =item before_delete
161              
162             Handler is passed a hashref as argument with fields: C, the L where clause.
163              
164             =item after_delete
165              
166             Handler takes no arguments.
167              
168             =item before_search
169              
170             Handler is passed a hashref as argument with fields: C, the L where clause, C, the search parameters, the C and C parameters.
171              
172             =item after_search
173              
174             Handler is passed a hashref as argument with fields: C, the L where clause, C, the search parameters, the C and C parameters, and C, an arrayref of objects returned by the search.
175              
176             =item before_construct
177              
178             Handler is passed a hashref as argument with fields: C, the hashref which will be blessed into an instance of this class.
179              
180             =item after_construct
181              
182             Handler is passed a hashref as argument with fields: C, the persistent object.
183              
184             =back
185              
186              
187             =cut
188              
189 18     18   121294 use vars qw($XML_DOM_Lite_Is_Available);
  18         39  
  18         1519  
190              
191             BEGIN {
192 18     18   284 __PACKAGE__->mk_classdata("auto_deploy");
193 18         551 __PACKAGE__->mk_classdata("dont_cache");
194              
195 18         274 $XML_DOM_Lite_Is_Available = 1;
196 18     18   1166 eval "use XML::DOM::Lite qw(Parser Node :constants);";
  18         7398  
  0         0  
  0         0  
197 18 50       37964 $XML_DOM_Lite_Is_Available = 0 if $@;
198             }
199              
200             our $DEBUG = 0;
201             our $PARSER;
202              
203             sub parser {
204 0 0   0 0 0 $PARSER = Parser->new( whitespace => 'strip' ) unless defined $PARSER;
205 0         0 $PARSER;
206             }
207              
208             our %Orphans;
209              
210             # Object Cache
211             our %Live_Objects;
212              
213             =head1 METHODS
214              
215             These methods are concrete.
216              
217             =over
218              
219             =item init
220              
221             Initialises the class data (see L)
222              
223             =cut
224              
225             sub init {
226 0     0 1 0 my $class = shift;
227 0 0       0 $DEBUG && $class->_carp('initializing class data');
228              
229             # set up class data accessors :
230 0         0 $class->mk_classdata("_meta");
231 0         0 $class->mk_classdata("attributes");
232 0         0 $class->mk_classdata("associations");
233 0         0 $class->mk_classdata("methods");
234 0         0 $class->mk_classdata("parents");
235              
236             # DATA section cache
237 0         0 $class->mk_classdata('dataNode');
238              
239 0         0 $class->meta({});
240 0         0 $class->attributes({});
241 0         0 $class->associations({});
242 0         0 $class->methods({});
243 0         0 $class->parents([]);
244              
245 0         0 ${ $class.'::__initialized' } = 1;
  0         0  
246             }
247              
248             =item import
249              
250             Does the work of constructing the class' meta-instances
251             (L, L and L
252             instances) from the C<$shema> class variable or defined
253             in the DATA section of the module if you have L
254             installed.
255              
256             =cut
257              
258             sub import {
259 50     50   377768 my $class = shift;
260 50         146 my %param = @_;
261              
262 50 50       281 $DEBUG>1 && $class->_carp("importing...");
263              
264 50 100 100     319 if ($class eq __PACKAGE__ and defined $param{auto_deploy}) {
265 6         45 $class->auto_deploy($param{auto_deploy});
266 6         187 return; # not interested in doing anything further
267             }
268 44 50 66     13083 return if $class eq __PACKAGE__
      66        
269             or $class eq 'Oryx::MetaClass'
270             or $class =~ /Oryx::[^:]+::Class/;
271              
272 26 50       45 if (${ $class.'::__initialized' }) {
  26         169  
273 0 0       0 $DEBUG>1 && $class->_carp('already initialized, returning');
274 0         0 return;
275             }
276              
277 26 50 33     414 if (can($class, 'storage') and $class->storage) {
278 0 0       0 if (%Orphans) {
279 0         0 foreach (keys %Orphans) {
280 0 0       0 $DEBUG>1 && $_->_carp('YAY! I am no longer an Orphan');
281 0         0 $class->storage->schema->addClass($_)
282             }
283 0         0 %Orphans = ();
284             }
285 0         0 $class->storage->schema->addClass($class);
286             } else {
287 26 50       85 $DEBUG && $class->_carp("no storage available Orphaning");
288 26         74 $Orphans{$class}++;
289 26 100       227 $class->auto_deploy($param{auto_deploy})
290             if defined $param{auto_deploy};
291 26         32190 return;
292             }
293              
294             # initialise class data
295 0           $class->init;
296 0 0         $DEBUG && $class->_carp("setting up...");
297              
298             # first set up parent relationships (this doesn't *have* to be
299             # done first, but I believe that the chicken came before the
300             # egg... the are, as always, good semantic and performance reasons
301             # behind this belief... if not behind this particular fragment of
302             # code being here instead of at the bottom of this function).
303 0           foreach (@{$class.'::ISA'}) {
  0            
304             # only if the superclass is a subclass of Oryx::DBx::Class
305 0 0 0       if (isa($_, __PACKAGE__)
      0        
306             and $_ ne __PACKAGE__
307             and $_ !~ /Oryx::[^:]+::Class/) {
308 0           $class->addParent($_);
309             }
310             }
311              
312 0           my $schema;
313 0 0         unless ($schema = ${$class.'::schema'}) {
  0            
314 0           my $xmldata = $class->parseDataIO;
315 0           eval(q{use Oryx::Schema::Generator});
316 0 0         die $@ if $@;
317 0           $schema = Oryx::Schema::Generator->generate($class, $xmldata);
318             }
319 0 0         if ($schema) {
320 0 0         $class->name($schema->{name}) if defined $schema->{name};
321 0           foreach (@{$schema->{attributes}}) {
  0            
322 0           $class->addAttribute($_);
323             }
324 0           foreach (@{$schema->{associations}}) {
  0            
325 0           $class->addAssociation($_);
326             }
327 0           foreach (@{$schema->{methods}}) {
  0            
328 0           $class->addMethod($_);
329             }
330             }
331 0 0 0       if ($class->auto_deploy or $param{auto_deploy}) {
332 0 0         unless ($class->storage->util->table_exists(
333             $class->dbh, $class->table)) {
334 0           $class->storage->deploy_class($class);
335             }
336             }
337 0 0         if ($param{dont_cache}) { $class->dont_cache(1) }
  0            
338             }
339              
340             =item meta
341              
342             Simple accessor to the class meta data.
343              
344             =cut
345              
346             sub meta {
347 0     0 1   my $class = shift;
348 0 0         $class->_meta(shift) if @_;
349 0           $class->_meta;
350             }
351              
352             =item construct( $class, $proto )
353              
354             This is typically called from within the C and C
355             methods of the implementation class (L or
356            
357             into C<$class> and then allows each class meta-instance to frobnicate
358             it in turn if they have any need to, before handing the instance to
359             you.
360              
361             =cut
362              
363             sub construct {
364 0     0 1   my ($class, $proto) = @_;
365              
366 0           my $object;
367 0           my $key = $class->_mk_cache_key($proto->{id});
368 0 0         return $object if ($object = $Live_Objects{$key});
369              
370 0           $class->notify_observers('before_construct', { proto => $proto });
371 0           $object = bless $proto, $class;
372              
373 0           $_->construct($object) foreach $class->members;
374 0           $_->construct($object) foreach @{$class->parents};
  0            
375              
376 0           $class->notify_observers('after_construct', { object => $object });
377 0 0         $DEBUG && $class->_carp("constructing $object id => ".$object->id);
378              
379 0 0         weaken($Live_Objects{$key} = $object) unless $object->dont_cache;
380 0           return $object;
381             }
382              
383             =item addAttribute( $meta )
384              
385             Creates an Attribute meta-instance and associates it with the class.
386              
387             =cut
388              
389             sub addAttribute {
390 0     0 1   my ($class, $meta) = @_;
391 0           my $attrib =
392             (ref($class->storage).'::Attribute')->new($meta, $class);
393 0           $class->attributes->{$attrib->name} = $attrib;
394             }
395              
396             =item addAssociation( $meta )
397              
398             Creates an Association meta-instance and associates it with the class.
399              
400             =cut
401              
402             sub addAssociation {
403 0     0 1   my ($class, $meta) = @_;
404 0           my $assoc =
405             (ref($class->storage).'::Association')->new($meta, $class);
406 0           $class->associations->{$assoc->role} = $assoc;
407             }
408              
409             =item addMethod( $meta )
410              
411             Does nothing at the moment as I cannot decide what such a method
412             would be used for exactly.
413              
414             =cut
415              
416             sub addMethod {
417 0     0 1   my ($class, $meta) = @_;
418 0           my $methd =
419             (ref($class->storage).'::Method')->new($meta, $class);
420 0           $class->methods->{$methd->name} = $methd;
421             }
422              
423             =item addParent( $super )
424              
425             Creates a Parent meta-instance and associates it with the class.
426              
427             =cut
428              
429             sub addParent {
430 0     0 1   my ($class, $super) = @_;
431 0           push @{$class->parents},
  0            
432             (ref($class->storage).'::Parent')->new($super, $class);
433             }
434              
435             =item id
436              
437             Returns the object id.
438              
439             =cut
440              
441 0     0 1   sub id { $_[0]->{id} }
442              
443             =item is_abstract
444              
445             True if the class does not define any attributes. This is used
446             for creating a special table for sharing sequences accross subclasses
447             and for instantiating the correct subclass instance if C
448             is called on an abstract class.
449              
450             =cut
451              
452             sub is_abstract {
453 0     0 1   my $class = shift;
454 0           return not %{$class->attributes};
  0            
455             }
456              
457             =item table
458              
459             Returns the table name for this class.
460              
461             =cut
462              
463             sub table {
464 0     0 1   my $class = shift;
465 0 0         unless (defined $class->meta->{table}) {
466 0           $class->meta->{table} = $class->schema->prefix.$class->name;
467             }
468 0           $class->meta->{table};
469             }
470              
471             =item name([ $name ])
472              
473             Get or set the C meta-attribute for the class.
474              
475             =cut
476              
477             sub name {
478 0     0 1   my $class = shift;
479 0           my $name = shift;
480 0 0         $class->setMetaAttribute("name", $name) if $name;
481 0 0         unless (defined $class->getMetaAttribute("name")) {
482 0           $class =~ /([^:]+)$/;
483 0           $class->setMetaAttribute("name", lc("$1"));
484             }
485 0           $class->getMetaAttribute("name");
486             }
487              
488             =item members
489              
490             Return a list of all meta-instances (Attribute, Association, Method
491             and Parent instances).
492              
493             =cut
494              
495             sub members {
496 0     0 1   my $class = shift;
497             return (
498 0           values %{$class->attributes},
  0            
499 0           values %{$class->associations},
500 0           values %{$class->methods},
501             # not really members, but we'll treat the same
502             #@{$class->parents},
503             );
504             }
505              
506             =item commit
507              
508             calls $self->dbh->commit to commit the trasaction
509              
510             =cut
511              
512 0     0 1   sub commit { $_[0]->dbh->commit }
513              
514             =item schema
515              
516             shortcut for $self->storage->schema. Read only.
517              
518             =cut
519              
520 0     0 1   sub schema { $_[0]->storage->schema }
521              
522             sub parseDataIO {
523 0     0 0   my ($class) = @_;
524 0 0         unless ($XML_DOM_Lite_Is_Available) {
525 0           $class->_carp('XML DATA schemas are not supported unless'
526             .' you have XML::DOM::Lite installed');
527 0           return undef;
528             }
529 0           my $stream = $class->loadDataIO;
530 0 0         if ($stream) {
531 0           return $class->parser->parse($stream)->documentElement;
532             } else {
533 0           return undef;
534             }
535             }
536              
537             sub loadDataIO {
538 0     0 0   my $class = shift;
539 0           my $fh = *{"$class\::DATA"}{IO};
  0            
540 0 0         return undef unless $fh;
541 0           local $/ = undef;
542 0           my $stream = <$fh>;
543 0           return $stream;
544             }
545              
546             =item remove_from_cache
547              
548             Object method to remove it from the memory cache.
549              
550             =cut
551              
552             sub remove_from_cache {
553 0     0 1   my $self = shift;
554 0           my $key = $self->_mk_cache_key($self->id);
555 0           CORE::delete( $Live_Objects{$key} );
556             }
557              
558             sub _mk_cache_key {
559 0   0 0     my $class = ref($_[0]) || $_[0];
560 0           my $id = $_[1];
561 0           return join('|', ( $class, $id ));
562             }
563              
564             sub _carp {
565 0 0   0     my $thing = ref($_[0]) ? ref($_[0]) : $_[0];
566 0           carp("[$thing] $_[1]");
567             }
568              
569             sub _croak {
570 0 0   0     my $thing = ref($_[0]) ? ref($_[0]) : $_[0];
571 0           croak("[$thing] $_[1]");
572             }
573              
574 0     0     sub DESTROY { $_[0]->remove_from_cache }
575              
576             1;
577             __END__