File Coverage

blib/lib/Persistence/ORM.pm
Criterion Covered Total %
statement 36 216 16.6
branch 0 52 0.0
condition 0 11 0.0
subroutine 12 42 28.5
pod 29 29 100.0
total 77 350 22.0


line stmt bran cond sub pod time code
1             package Persistence::ORM;
2              
3 17     17   96 use strict;
  17         36  
  17         578  
4 17     17   88 use warnings;
  17         28  
  17         518  
5              
6 17     17   89 use Abstract::Meta::Class ':all';
  17         26  
  17         3637  
7              
8 17     17   10686 use Persistence::Attribute::AMCAdapter;
  17         68  
  17         563  
9 17     17   9836 use Persistence::Relationship ':all';
  17         172  
  17         3131  
10 17     17   20487 use Persistence::LOB;
  17         53  
  17         867  
11 17     17   12031 use Persistence::Relationship::ToOne ':all';
  17         45  
  17         2076  
12 17     17   11562 use Persistence::Relationship::OneToMany ':all';
  17         44  
  17         2287  
13 17     17   9349 use Persistence::Relationship::ManyToMany ':all';
  17         51  
  17         2044  
14              
15              
16 17     17   111 use vars qw(@EXPORT_OK %EXPORT_TAGS $VERSION);
  17         1328  
  17         2566  
17 17     17   90 use Carp 'confess';
  17         36  
  17         792  
18 17     17   92 use base 'Exporter';
  17         33  
  17         61590  
19              
20             $VERSION = 0.04;
21              
22             @EXPORT_OK = qw(entity column trigger to_one one_to_many many_to_many lob LAZY EAGER NONE ALL ON_INSERT ON_UPDATE ON_DELETE);
23             %EXPORT_TAGS = (all => \@EXPORT_OK);
24              
25             =head1 NAME
26              
27             Persistence::ORM - Object-relational mapping.
28              
29             =cut
30              
31             =head1 SYNOPSIS
32              
33             package Employee;
34              
35             use Abstract::Meta::Class ':all';
36             use Persistence::ORM ':all';
37              
38             entity 'emp';
39             column empno => has('$.no') ;
40             column ename => has('$.name');
41              
42              
43             =head1 DESCRIPTION
44              
45             Object-relational mapping module.
46              
47             =head1 EXPORT
48              
49             entity column trigger to_one one_to_many many_to_many
50             LAZY EAGER NONE ALL ON_INSERT ON_UPDATE ON_DELETE by 'all' tag
51              
52             =head2 ATTRIBUTES
53              
54             =over
55              
56             =item class
57              
58             class name
59              
60             =cut
61              
62             has '$.class' => (
63             required => 1,
64             on_change => sub {
65             my ($self, $attribute, $scope, $value_ref) = @_;
66             mapping_meta($$value_ref, $self);
67             }
68             );
69              
70              
71             =item entity_name
72              
73             entity name.
74              
75             =cut
76              
77             has '$.entity_name' => (required => 1);
78              
79              
80             =item columns
81              
82             A map between database column and object attribute
83              
84             =cut
85              
86             has '%.columns' => (
87             item_accessor => '_column',
88             associated_class => 'Persistence::Attribute',
89             index_by => 'column_name',
90             on_validate => sub {
91             my ($self, $attribute, $scope, $value_ref) = @_;
92             my $values = $$value_ref;
93             if (ref($values) eq 'HASH') {
94             my $class = $self->class;
95             foreach my $k (keys %$values) {
96             my $value = $values->{$k};
97             $values->{$k} = $self->_create_meta_attribute($value, $class, $k)
98             if(ref($value) eq 'HASH')
99             }
100             }
101             }
102             );
103              
104              
105             =item lobs
106              
107             Assocation to LOB objects definition.
108              
109             =cut
110              
111             has '%.lobs' => (item_accessor => '_lob', associated_class => 'Persistence::LOB', the_other_end => 'orm');
112              
113              
114             =item relationships
115              
116             Assocation to objects relationship definition.
117              
118             =cut
119              
120             has '%.relationships' => (item_accessor => '_relationship', associated_class => 'Persistence::Relationship', index_by => 'attribute_name', the_other_end => 'orm');
121              
122              
123             =item trigger
124              
125             Defines tigger that will execute on one of the following event
126             before_insert after_insert before_update after_update before_delete after_delete, on_fetch
127             Takes event name as first parameter, and callback as secound parameter.
128              
129             $entity_manager->trigger(before_insert => sub {
130             my ($self) = @_;
131             #do stuff
132             });
133              
134             =cut
135              
136             {
137              
138             has '%.triggers' => (
139             item_accessor => '_trigger',
140             on_change => sub {
141             my ($self, $attribute, $scope, $value, $key) = @_;
142             if($scope eq 'mutator') {
143             my $hash = $$value;
144             foreach my $k (keys %$hash) {
145             $self->validate_trigger($k. $hash->{$k});
146             }
147             } else {
148             $self->validate_trigger($key, $$value);
149             }
150             $self;
151             },
152             );
153             }
154              
155              
156             =item entity_manager
157              
158             =cut
159              
160             has '$.entity_manager' => (transistent => 1);
161              
162              
163             =item mop_attribute_adapter
164              
165             Name of the class that is an adapter to meta object protocols.
166             That class have to implements Persistence::Attribute interface.
167              
168             =cut
169              
170             has '$.mop_attribute_adapter' => (
171             default => 'Persistence::Attribute::AMCAdapter',
172             );
173              
174              
175             =item object_creation_method
176              
177             Returns object creation method.
178             Allowed values: bless or new
179              
180             =cut
181              
182             has '$.object_creation_method' => (
183             default => 'bless',
184             on_change => sub {
185             my ($self, $attribute, $scope, $value) = @_;
186             confess "invalid value for " . __PACKAGE__ . "::object_creation_method - allowed values(bless | new)"
187             if ($$value ne 'bless' && $$value ne 'new');
188             $self;
189             }
190             );
191              
192              
193             =item _attributes_to_columns
194              
195             Cache for the attributes_to_columns method result
196              
197             =cut
198              
199             has '$._attributes_to_columns';
200              
201              
202             =item _columns_to_attributes
203              
204             Cache for the columns_to_attributes method result
205              
206             =cut
207              
208             has '$._columns_to_attributes';
209              
210              
211             =item _columns_to_storage_attributes
212              
213             Cache for the columns_to_storage_attributes method result
214              
215             =cut
216              
217             has '$._columns_to_storage_attributes';
218              
219              
220             =back
221              
222             =head2 METHODS
223              
224             =over
225              
226             =item entity
227              
228             Creates a meta entity class.
229              
230             =cut
231              
232             sub entity {
233 0     0 1   my ($name, $package) = @_;
234 0   0       $package ||= caller();
235 0           __PACKAGE__->new(entity_name => $name, class => $package);
236             }
237              
238              
239             {
240             my %meta;
241              
242             =item mapping_meta
243              
244             Returns meta enity class.
245             Takes optionally package name as parameter.
246              
247             =cut
248              
249             sub mapping_meta {
250 0     0 1   my ($package, $value) = @_;
251 0   0       $package ||= caller();
252 0 0         $meta{$package} = $value if defined $value;
253 0           $meta{$package};
254             }
255             }
256              
257             =item column
258              
259             Adds mapping between column name and related attribute.
260             Takes column name and attribute object as parameter.
261              
262             column ('column1' => has '$.attr1');
263              
264             =cut
265              
266             sub column {
267 0     0 1   my ($name, $attribute) = @_;
268 0           my $attr_class = 'Persistence::Attribute';
269 0           my $package = caller();
270 0 0         my $self = mapping_meta($package) or confess "no entity defined for class $package";
271 0           my $attribute_class = $self->mop_attribute_adapter;
272 0 0         $attribute = $attribute_class->new(attribute => $attribute, column_name => $name)
273             unless $attribute->isa('Persistence::Attribute');
274 0           $self->add_columns($attribute);
275             }
276              
277              
278             =item lob
279              
280             Adds mapping between lob column name and related attribute.
281              
282             lob 'lob_column' => (
283             attribute => has('$.photo'),
284             fetch_method => LAZY,
285             );
286              
287              
288             =cut
289              
290             sub lob {
291 0     0 1   my ($name, %args) = @_;
292 0           my $attribute = $args{attribute};
293 0           my $attr_class = 'Persistence::Attribute';
294 0           my $package = caller();
295 0 0         my $self = mapping_meta($package) or confess "no entity defined for class $package";
296 0           my $attribute_class = $self->mop_attribute_adapter;
297 0 0         $args{attribute} = $attribute_class->new(attribute => $attribute, column_name => $name)
298             unless $attribute->isa('Persistence::Attribute');
299 0           $self->add_lobs(Persistence::LOB->new(%args));
300             }
301              
302              
303             =item covert_to_attributes
304              
305             Converts passed in data structure to attributes
306              
307             =cut
308              
309             sub covert_to_attributes {
310 0     0 1   my ($self, $columns) = @_;
311 0           my $class = $self->class;
312 0           my $attribute_class = $self->mop_attribute_adapter;
313 0           my $result = {};
314 0           for my $column(keys %$columns) {
315 0           my $meta_attribute = $columns->{$column};
316 0           my $attribute = $attribute_class->find_attribute($class, $meta_attribute->{name});
317 0 0         unless ($attribute) {
318 0           $attribute = $self->_create_meta_attribute($meta_attribute, $class, $column);
319             } else {
320 0           $attribute = $attribute_class->new(attribute => $attribute, column_name => $column);
321             }
322 0           $result->{$column} = $attribute;
323             }
324 0           $result;
325             }
326              
327              
328             =item covert_to_lob_attributes
329              
330             Converts passed in data structure to lob attributes
331              
332             =cut
333              
334             sub covert_to_lob_attributes {
335 0     0 1   my ($self, $lobs) = @_;
336 0           my $class = $self->class;
337 0           my $attribute_class = $self->mop_attribute_adapter;
338 0           my $result = {};
339 0           for my $lob (@$lobs) {
340 0           my $column = $lob->{name};
341 0           my $fetch_method = $lob->{fetch_method};
342 0           my $attribute_name = $lob->{attribute};
343            
344 0           my $attribute = $attribute_class->find_attribute($class, $attribute_name);
345 0 0         unless ($attribute) {
346 0           $attribute = $self->_create_meta_attribute({name => $attribute_name}, $class, $column);
347             } else {
348 0           $attribute = $attribute_class->new(attribute => $attribute, column_name => $column);
349             }
350 0 0         $result->{$column} = Persistence::LOB->new(
351             attribute => $attribute,
352             ($fetch_method ? (fetch_method => Persistence::LOB->$fetch_method) :())
353             );
354             }
355 0           $result;
356             }
357              
358              
359             =item _create_meta_attribute
360              
361             Creates a meta attribute
362              
363             =cut
364              
365             sub _create_meta_attribute {
366 0     0     my ($clazz, $meta_attribute, $class, $column_name) = @_;
367 0 0         my $self = mapping_meta($class) or confess "no entity defined for class $class";
368 0           my $attribute_class = $self->mop_attribute_adapter;
369 0           $attribute_class->create_meta_attribute($meta_attribute, $class, $column_name);
370             }
371              
372              
373             =item add_lob_column
374              
375             Adds lob column.
376             Takes lob column name, attribute name;
377              
378             =cut
379              
380             sub add_lob_column {
381 0     0 1   my ($self, $column, $attribute_name, $fetch_method) = @_;
382 0 0         $self->add_lobs(
383             Persistence::LOB->new(
384             name => 'column',
385             attribute => $self->attribute($attribute_name),
386             ($fetch_method ? (fetch_method => Persistence::LOB->$fetch_method) :()),
387             )
388             );
389             }
390              
391              
392             =item eager_fetch_lobs
393              
394             =cut
395              
396             sub eager_fetch_lobs {
397 0     0 1   my ($self) = @_;
398 0           my $lobs = $self->lobs;
399 0           Persistence::LOB->eager_fetch_filter($lobs);
400             }
401              
402              
403             =item lazy_fetch_lobs
404              
405             =cut
406              
407             sub lazy_fetch_lobs {
408 0     0 1   my ($self) = @_;
409 0           my $lobs = $self->lobs;
410 0           Persistence::LOB->lazy_fetch_filter($lobs);
411             }
412              
413              
414             =item attribute
415              
416             =cut
417              
418             sub attribute {
419 0     0 1   my ($self, $attribute_name) = @_;
420 0 0         my $meta = Abstract::Meta::Class::meta_class($self->class)
421             or confess "cant find meta class defintion (Abstract::Meta::Class) for " . $self->class;
422 0 0         my $attribute = $meta->attribute($attribute_name)
423             or confess "cant find attribute ${attribute_name} for class " . $self->class;
424 0           $attribute;
425             }
426              
427              
428             =item deserialise
429              
430             Deserialises resultset to object.
431              
432             =cut
433              
434             sub deserialise {
435 0     0 1   my ($self, $args, $entity_manager) = @_;
436 0           my $object_creation_method = $self->object_creation_method;
437 0           my $columns_to_attributes = $self->columns_to_attributes;
438 0           my $result = $object_creation_method eq 'bless'
439             ? bless ({
440             $self->storage_attribute_values($args)
441             }, $self->class)
442 0 0         : $self->class->new(map { $args->{$_} } keys %$columns_to_attributes);
443              
444 0           $entity_manager->initialise_operation($self->entity_name, $result);
445 0           $self->deserialise_eager_relation_attributes($result, $entity_manager);
446 0           $self->deserialise_eager_lob_attributes($result, $entity_manager);
447 0           $entity_manager->complete_operation($self->entity_name);
448 0           $self->run_event('on_fetch', $result);
449 0           $result;
450             }
451              
452              
453             =item deserialise_eager_relation_attributes
454              
455             =cut
456              
457             sub deserialise_eager_relation_attributes {
458 0     0 1   my ($self, $object, $entity_manager) = @_;
459 0           my @relations = Persistence::Relationship->eager_fetch_relations(ref($object));
460 0           foreach my $relation (@relations) {
461 0           $relation->deserialise_attribute($object, $entity_manager, $self);
462             }
463             }
464              
465              
466             =item deserialise_eager_lob_attributes
467              
468             =cut
469              
470             sub deserialise_eager_lob_attributes {
471 0     0 1   my ($self, $object, $entity_manager) = @_;
472 0           my @lobs = $self->eager_fetch_lobs;
473 0           foreach my $lob (@lobs) {
474 0           $lob->deserialise_attribute($object, $entity_manager, $self);
475             }
476             }
477              
478              
479             =item deserialise_lazy_relation_attributes
480              
481             =cut
482              
483             sub deserialise_lazy_relation_attributes {
484 0     0 1   my ($self, $object, $entity_manager) = @_;
485 0           my @relations = Persistence::Relationship->lazy_fetch_relations(ref($object));
486 0           foreach my $relation (@relations) {
487 0           my $name = $relation->attribute->name;
488 0           $object->$name;
489             }
490             }
491              
492              
493             =item update_object
494              
495             =cut
496              
497             sub update_object {
498 0     0 1   my ($self, $object, $column_values, $columns_to_update) = @_;
499 0           my $columns = $self->columns;
500 0   0       $columns_to_update ||= [keys %$column_values];
501 0           for my $column_name (@$columns_to_update) {
502 0 0         my $attribute = $columns->{$column_name} or next;
503 0           $attribute->set_value($object, $column_values->{$column_name});
504             }
505             }
506              
507              
508             =item join_columns_values
509              
510             Returns join columns values for passed in relation
511              
512             =cut
513              
514             sub join_columns_values {
515 0     0 1   my ($self, $entity, $relation_name, $object) = @_;
516 0           my $relation = $entity->to_many_relationship($relation_name);
517 0           my $pk_values = $self->column_values($object, $entity->primary_key);
518 0 0         unless ($entity->has_primary_key_values($pk_values)) {
519 0           my $values = $self->unique_values($object, $entity);
520 0           $pk_values = $self->retrive_primary_key_values($values);
521             }
522 0           $entity->_join_columns_values($relation, $pk_values);
523             }
524              
525              
526             =item unique_values
527              
528             Return unique columns values
529              
530             =cut
531              
532             sub unique_values {
533 0     0 1   my ($self, $object, $entity) = @_;
534 0           my @unique_columns = map { $_->name } $entity->unique_columns;;
  0            
535 0           $self->column_values($object, $entity->primary_key, @unique_columns);
536             }
537              
538              
539             =item primary_key_values
540              
541             Return primary key values
542              
543             =cut
544              
545             sub primary_key_values {
546 0     0 1   my ($self, $object, $entity) = @_;
547 0           $self->column_values($object, $entity->primary_key);
548             }
549              
550              
551             =item trigger
552              
553             =cut
554              
555             sub trigger {
556 0     0 1   my ($event_name, $code_ref) = @_;
557 0           my $attr_class = 'Abstract::Meta::Attribute';
558 0           my $package = caller();
559 0 0         my $mapping_meta = mapping_meta($package) or confess "no entity defined for class $package";
560 0           $mapping_meta->_trigger($event_name, $code_ref);
561             }
562              
563              
564             =item validate_trigger
565              
566             Validates triggers types
567              
568             =cut
569              
570             {
571             my @triggers = qw(before_insert after_insert before_update after_update before_delete after_delete on_fetch);
572             sub validate_trigger {
573 0     0 1   my ($self, $name, $value) = @_;
574 0           confess "invalid trigger name: $name , must be one of " . join(",", @triggers)
575 0 0         unless (grep {$name eq $_} @triggers);
576 0 0         confess "secound parameter must be a callback"
577             unless ref($value) eq 'CODE';
578             }
579             }
580              
581              
582             =item run_event
583              
584             =cut
585              
586             sub run_event {
587 0     0 1   my ($self, $name, @args) = @_;
588 0           my $event = $self->_trigger($name);
589 0 0         $event->($self, @args) if $event;
590             }
591              
592              
593             =item attributes_to_columns
594              
595             =cut
596              
597             sub attributes_to_columns {
598 0     0 1   my ($self) = @_;
599 0           my $attributes_to_columns = $self->_attributes_to_columns;
600 0 0         return $attributes_to_columns if $attributes_to_columns;
601 0           my $columns = $self->columns;
602 0           my $result = {};
603 0           foreach my $k (keys %$columns) {
604 0           $result->{$columns->{$k}->name} = $k;
605             }
606 0           $self->_attributes_to_columns($result);
607 0           return $result;
608             }
609              
610              
611             =item columns_to_attributes
612              
613             =cut
614              
615             sub columns_to_attributes {
616 0     0 1   my ($self) = @_;
617 0           my $columns_to_attributes = $self->_columns_to_attributes;
618 0 0         return $columns_to_attributes if $columns_to_attributes;
619 0           my $columns = $self->columns;
620 0           my $result = {};
621 0           foreach my $k (keys %$columns) {
622 0           $result->{$k} = $columns->{$k}->name;
623             }
624 0           my $lobs = $self->lobs;
625 0           foreach my $k (keys %$lobs) {
626 0           my $attribute = $lobs->{$k}->attribute;
627 0           $result->{$attribute->column_name} = $attribute->name;
628             }
629              
630 0           $self->_columns_to_attributes($result);
631 0           return $result;
632             }
633              
634              
635              
636             =item columns_to_storage_attributes
637              
638             =cut
639              
640             sub columns_to_storage_attributes {
641 0     0 1   my ($self) = @_;
642 0           my $columns_to_storage_attributes = $self->_columns_to_storage_attributes;
643 0 0         return $columns_to_storage_attributes if $columns_to_storage_attributes;
644 0           my $columns = $self->columns;
645 0           my $result = {};
646 0           foreach my $k (keys %$columns) {
647 0           $result->{$k} = $columns->{$k}->storage_key;
648             }
649 0           $self->_columns_to_storage_attributes($result);
650 0           return $result;
651             }
652              
653              
654             =item attribute_to_column
655              
656             Returns column name.
657             Takes attribute name.
658              
659             =cut
660              
661             sub attribute_to_column {
662 0     0 1   my ($self, $attribute_name) = @_;
663 0           my $attributes_to_columns = $self->attributes_to_columns;
664 0           $attributes_to_columns->{$attribute_name};
665             }
666              
667              
668             =item storage_attribute_values
669              
670             Transforms column values to the hash that can be blessed as an object.
671             Takes hash ref of column_values
672              
673             =cut
674              
675             sub storage_attribute_values {
676 0     0 1   my ($self, $column_values) = @_;
677 0           my $columns = $self->columns;
678 0           my $columns_to_storage_attributes = $self->columns_to_storage_attributes;
679 0           my %result = map {
680 0           ($columns_to_storage_attributes->{$_}, $column_values->{$_})} keys %$columns;
681 0 0         wantarray ? (%result) : \%result;
682             }
683              
684              
685             =item attribute_values
686              
687             Transforms column values to the object attribute value hash.
688             Takes hash ref of column_values
689              
690             =cut
691              
692             sub attribute_values {
693 0     0 1   my ($self, $column_values) = @_;
694 0           my $columns = $self->columns;
695 0           my $columns_to_attributes = $self->columns_to_attributes;
696 0           my %result = map {
697 0           ($columns_to_attributes->{$_}, $column_values->{$_} )} keys %$columns;
698 0 0         wantarray ? (%result) : \%result;
699             }
700              
701              
702             =item column_values
703              
704             Transforms objects attributes to column values
705             Takes object, optionally required columns. (by default all colunms)
706              
707             =cut
708              
709             sub column_values {
710 0     0 1   my ($self, $obj, @columns) = @_;
711 0           my $columns_to_attributes = $self->columns_to_attributes;
712 0           my $lobs = $self->lobs;
713 0 0         @columns = (keys %$columns_to_attributes)
714             unless @columns;
715 0           my %result = map {
716 0           my $accessor = $columns_to_attributes->{$_};
717 0           ($_, $obj->$accessor)} @columns;
718 0 0         wantarray ? (%result) : \%result;
719             }
720              
721              
722             =item attribute_values_to_column_values
723              
724             Returns column values.
725             Takes attribute values hash.
726              
727             =cut
728              
729             sub attribute_values_to_column_values {
730 0     0 1   my ($self, %args) = @_;
731 0           my $attributes_to_columns = $self->attributes_to_columns;
732 0           my %result;
733 0           for my $k(keys %args) {
734 0   0       my $column = $attributes_to_columns->{$k} || $k;
735 0           $result{$column} = $args{$k};
736             }
737 0           (%result);
738             }
739              
740              
741              
742             1;
743              
744             __END__