File Coverage

blib/lib/Fey/Meta/Class/Table.pm
Criterion Covered Total %
statement 172 216 79.6
branch 46 60 76.6
condition 11 15 73.3
subroutine 35 45 77.7
pod 7 7 100.0
total 271 343 79.0


line stmt bran cond sub pod time code
1             package Fey::Meta::Class::Table;
2              
3 10     10   50 use strict;
  10         26  
  10         334  
4 10     10   50 use warnings;
  10         15  
  10         273  
5 10     10   44 use namespace::autoclean;
  10         15  
  10         62  
6              
7             our $VERSION = '0.47';
8              
9 10     10   755 use Fey::Exceptions qw( param_error );
  10         15  
  10         772  
10 10     10   4615 use Fey::Hash::ColumnsKey;
  10         22  
  10         313  
11 10     10   4088 use Fey::Object::Policy;
  10         43  
  10         442  
12 10     10   6411 use Fey::Meta::Attribute::FromInflator;
  10         196  
  10         657  
13 10     10   7131 use Fey::Meta::Attribute::FromColumn;
  10         39  
  10         537  
14 10     10   6530 use Fey::Meta::Attribute::FromSelect;
  10         42  
  10         963  
15 10     10   3603 use Fey::Meta::Class::Schema;
  10         37  
  10         506  
16 10     10   6803 use Fey::Meta::HasOne::ViaFK;
  10         44  
  10         489  
17 10     10   941101 use Fey::Meta::HasOne::ViaSelect;
  10         47  
  10         1143  
18 10     10   6325 use Fey::Meta::HasMany::ViaFK;
  10         43  
  10         460  
19 10     10   6094 use Fey::Meta::HasMany::ViaSelect;
  10         46  
  10         457  
20 10     10   6461 use Fey::Meta::Method::Constructor;
  10         38  
  10         557  
21 10     10   6161 use Fey::Meta::Method::FromSelect;
  10         40  
  10         576  
22             use Fey::ORM::Types
23 10     10   94 qw( Bool ClassName CodeRef DoesHasMany DoesHasOne HashRef Object );
  10         20  
  10         114  
24 10     10   86016 use List::AllUtils qw( all );
  10         26  
  10         794  
25 10     10   73 use Try::Tiny ();
  10         25  
  10         254  
26              
27 10     10   54 use Moose;
  10         19  
  10         94  
28 10     10   47109 use MooseX::ClassAttribute;
  10         21  
  10         107  
29 10     10   45847 use MooseX::SemiAffordanceAccessor;
  10         27  
  10         96  
30              
31             extends 'Moose::Meta::Class';
32              
33             class_has '_ClassToTableMap' => (
34             traits => ['Hash'],
35             is => 'ro',
36             isa => HashRef ['Fey::Table'],
37             default => sub { {} },
38             lazy => 1,
39             handles => {
40             TableForClass => 'get',
41             _SetTableForClass => 'set',
42             _ClassHasTable => 'exists',
43             },
44             );
45              
46             has '_object_cache_is_enabled' => (
47             is => 'rw',
48             isa => Bool,
49             lazy => 1,
50             default => 0,
51             );
52              
53             has '_object_cache' => (
54             is => 'ro',
55             isa => HashRef [Object],
56             lazy => 1,
57             default => sub { {} },
58             clearer => '_clear_object_cache',
59             );
60              
61             has 'table' => (
62             is => 'rw',
63             isa => 'Fey::Table',
64             writer => '_set_table',
65             predicate => '_has_table',
66             );
67              
68             has 'inflators' => (
69             traits => ['Hash'],
70             is => 'ro',
71             isa => HashRef [CodeRef],
72             default => sub { {} },
73             lazy => 1,
74             handles => {
75             _add_inflator => 'set',
76             has_inflator => 'exists',
77             },
78             );
79              
80             has 'deflators' => (
81             traits => ['Hash'],
82             is => 'ro',
83             isa => HashRef [CodeRef],
84             default => sub { {} },
85             lazy => 1,
86             handles => {
87             deflator_for => 'get',
88             _add_deflator => 'set',
89             has_deflator => 'exists',
90             },
91             );
92              
93             has 'schema_class' => (
94             is => 'ro',
95             isa => ClassName,
96             lazy => 1,
97             default => sub {
98             Fey::Meta::Class::Schema->ClassForSchema( $_[0]->table()->schema() );
99             },
100             );
101              
102             has 'policy' => (
103             is => 'rw',
104             isa => 'Fey::Object::Policy',
105             default => sub { Fey::Object::Policy->new() },
106             );
107              
108             has '_has_ones' => (
109             traits => ['Hash'],
110             is => 'ro',
111             isa => HashRef [DoesHasOne],
112             default => sub { {} },
113             lazy => 1,
114             handles => {
115             _has_one => 'get',
116             _add_has_one => 'set',
117             _has_has_one => 'exists',
118             has_ones => 'values',
119             _remove_has_one => 'delete',
120             },
121             );
122              
123             has '_has_manies' => (
124             traits => ['Hash'],
125             is => 'ro',
126             isa => HashRef [DoesHasMany],
127             default => sub { {} },
128             lazy => 1,
129             handles => {
130             _has_many => 'get',
131             _add_has_many => 'set',
132             _has_has_many => 'exists',
133             has_manies => 'values',
134             _remove_has_many => 'delete',
135             },
136             );
137              
138             has '_select_sql_cache' => (
139             is => 'ro',
140             isa => 'Fey::Hash::ColumnsKey',
141             lazy => 1,
142             default => sub { Fey::Hash::ColumnsKey->new() },
143             );
144              
145             has '_sql_string_cache' => (
146             is => 'ro',
147             isa => HashRef [HashRef],
148             lazy => 1,
149             default => sub {
150             { {} }
151             },
152             );
153              
154             has '_select_by_pk_sql' => (
155             is => 'ro',
156             isa => 'Fey::SQL::Select',
157             lazy => 1,
158             default => sub { return $_[0]->name()->_MakeSelectByPKSQL() },
159             );
160              
161             has '_count_sql' => (
162             is => 'ro',
163             isa => 'Fey::SQL::Select',
164             lazy => 1,
165             builder => '_build_count_sql',
166             );
167              
168             sub ClassForTable {
169 27     27 1 905 my $class = shift;
170              
171             return @_ == 1
172             ? $class->_ClassForTable(@_)
173 27 100       258 : map { $class->_ClassForTable($_) } @_;
  2         84  
174             }
175              
176             sub _ClassForTable {
177 28     28   50 my $class = shift;
178 28         47 my $table = shift;
179              
180 28         1022 my $map = $class->_ClassToTableMap();
181              
182 28         54 for my $class_name ( keys %{$map} ) {
  28         114  
183 27         187 my $potential_table = $map->{$class_name};
184              
185 27 100 100     629 return $class_name
186             if $potential_table->name() eq $table->name()
187             && $potential_table->schema()->name() eq $table->schema()->name();
188             }
189              
190 24         254 return;
191             }
192              
193             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
194             sub _search_cache {
195 0     0   0 my $self = shift;
196 0         0 my $p = shift;
197              
198 0         0 my $cache = $self->_object_cache();
199              
200 0         0 for my $key ( @{ $self->table()->candidate_keys() } ) {
  0         0  
201 0         0 my @names = map { $_->name() } @{$key};
  0         0  
  0         0  
202 0 0   0   0 next unless all { defined $p->{$_} } @names;
  0         0  
203              
204 0         0 my $cache_key = join "\0", map { ( $_, $p->{$_} ) } sort @names;
  0         0  
205              
206             return $cache->{$cache_key}
207 0 0       0 if exists $cache->{$cache_key};
208             }
209             }
210              
211             sub _write_to_cache {
212 0     0   0 my $self = shift;
213 0         0 my $object = shift;
214              
215 0         0 my $cache = $self->_object_cache();
216              
217 0         0 for my $key ( @{ $self->table()->candidate_keys() } ) {
  0         0  
218 0         0 my @names = map { $_->name() } @{$key};
  0         0  
  0         0  
219              
220 0         0 my @pieces = map { ( $_, $object->$_() ) } sort @names;
  0         0  
221              
222 0 0   0   0 next unless all {defined} @pieces;
  0         0  
223              
224 0         0 my $cache_key = join "\0", @pieces;
225              
226 0         0 $cache->{$cache_key} = $object;
227             }
228             }
229              
230             sub _associate_table {
231 26     26   54 my $self = shift;
232 26         43 my $table = shift;
233 26         43 my $context = shift;
234              
235 26         143 my $caller = $self->name();
236              
237 26 100       1040 param_error 'Cannot call has_table() more than once per class'
238             if $self->_has_table();
239              
240 25 100       143 param_error 'Cannot associate the same table with multiple classes'
241             if $self->ClassForTable($table);
242              
243 24 100       760 param_error 'A table object passed to has_table() must have a schema'
244             unless $table->has_schema();
245              
246 23         716 my $class = Fey::Meta::Class::Schema->ClassForSchema( $table->schema() );
247              
248 23 50 66     696 param_error 'You must load your schema class before calling has_table()'
      66        
249             unless $class
250             && $class->can('meta')
251             && $class->meta()->_has_schema();
252              
253             param_error
254             'A table object passed to has_table() must have at least one key'
255 22 100       45 unless @{ $table->primary_key() };
  22         578  
256              
257 21         9648 $self->_SetTableForClass( $self->name() => $table );
258              
259 21         855 $self->_set_table($table);
260              
261 21         105 $self->_make_column_attributes($context);
262             }
263             ## use critic;
264              
265             sub _make_column_attributes {
266 21     21   38 my $self = shift;
267 21         43 my $context = shift;
268              
269 21         711 my $table = $self->table();
270              
271 21         124 for my $column ( $table->columns() ) {
272 78         6258 my $name = $column->name();
273              
274 78 100       850 next if $self->has_method($name);
275              
276             my %attr_p = (
277             metaclass => 'Fey::Meta::Attribute::FromColumn',
278             is => 'rw',
279             isa => $self->_type_for_column($column),
280             lazy => 1,
281 0     0   0 default => sub { $_[0]->_get_column_value($name) },
282 77         3181 column => $column,
283             writer => q{_set_} . $name,
284             clearer => q{_clear_} . $name,
285             predicate => q{has_} . $name,
286             definition_context => $context,
287             );
288              
289 77         559 $self->add_attribute( $name, %attr_p );
290              
291 77 50       896083 if ( my $transform = $self->policy()->transform_for_column($column) )
292             {
293 0         0 $self->_add_transform( $name, {}, %{$transform} );
  0         0  
294             }
295             }
296             }
297              
298             # XXX - can this be overridden or customized? should it account for
299             # per-column/policy-level transforms?
300             {
301             my %FeyToMoose = (
302             text => 'Str',
303             blob => 'Str',
304             integer => 'Int',
305             float => 'Num',
306             datetime => 'Str',
307             date => 'Str',
308             time => 'Str',
309             boolean => 'Bool',
310             other => 'Value',
311             );
312              
313             sub _type_for_column {
314 77     77   158 my $self = shift;
315 77         118 my $column = shift;
316              
317 77         2484 my $type = $FeyToMoose{ $column->generic_type() };
318              
319 77 100       14829 $type .= q{ | Undef}
320             if $column->is_nullable();
321              
322 77         1866 return $type;
323             }
324             }
325              
326             sub _add_transform {
327 7     7   10 my $self = shift;
328 7         11 my $name = shift;
329 7         10 my $context = shift;
330 7         17 my %p = @_;
331              
332 7         34 my $attr = $self->get_attribute($name);
333              
334 7 100       66 param_error "The column $name does not exist as an attribute"
335             unless $attr;
336              
337             $self->_add_inflator_to_attribute(
338             $name,
339             $context,
340             $attr,
341             $p{inflate},
342             $p{handles}
343 6 100       35 ) if $p{inflate};
344              
345 5 50       18 if ( $p{deflate} ) {
346 5 100       188 param_error
347             "Cannot provide more than one deflator for a column ($name)"
348             if $self->has_deflator($name);
349              
350 4         139 $self->_add_deflator( $name => $p{deflate} );
351             }
352             }
353              
354             ## no critic (Subroutines::ProhibitManyArgs)
355             sub _add_inflator_to_attribute {
356 5     5   8 my $self = shift;
357 5         8 my $name = shift;
358 5         8 my $context = shift;
359 5         7 my $attr = shift;
360 5         9 my $inflator = shift;
361 5         8 my $handles = shift;
362              
363 5 100       49 param_error "Cannot provide more than one inflator for a column ($name)"
364             if $attr->isa('Fey::Meta::Attribute::FromInflator');
365              
366 4         25 $self->remove_attribute($name);
367              
368 4         1297 my $raw_name = $name . q{_raw};
369              
370             # XXX - should the private writer invoke the deflator?
371 4         26 my $raw_attr = $attr->clone(
372             name => $raw_name,
373             reader => $raw_name,
374             definition_context => $context,
375             );
376              
377 4         10123 $self->add_attribute($raw_attr);
378              
379 4         37065 my $inflated_predicate = q{_has_inflated_} . $name;
380 4         11 my $inflated_clear = q{_clear_inflated_} . $name;
381              
382             my $default = sub {
383 4     4   918 my $self = shift;
384              
385 4         53 return $self->$inflator( $self->$raw_name() );
386 4         25 };
387              
388 4 100       17 my %handles = $handles ? ( handles => $handles ) : ();
389              
390 4         23 $self->add_attribute(
391             $name,
392             metaclass => 'Fey::Meta::Attribute::FromInflator',
393             is => 'ro',
394             lazy => 1,
395             default => $default,
396             predicate => $inflated_predicate,
397             clearer => $inflated_clear,
398             init_arg => undef,
399             raw_attribute => $raw_attr,
400             inflator => $inflator,
401             definition_context => $context,
402             %handles,
403             );
404              
405             my $clear_inflated = sub {
406 2     2   932 my $self = shift;
407              
408 2         38 $self->$inflated_clear();
409 4         20356 };
410              
411 4         39 $self->add_after_method_modifier( $raw_attr->clearer(), $clear_inflated );
412 4         821 $self->add_after_method_modifier( $raw_attr->writer(), $clear_inflated );
413              
414 4         634 $self->_add_inflator( $name => $inflator );
415             }
416             ## use critic
417              
418             sub add_has_one {
419 12     12 1 28 my $self = shift;
420 12         42 my %p = @_;
421              
422 12 50       497 param_error 'You must call has_table() before calling has_one().'
423             unless $self->_has_table();
424              
425             param_error
426             'You cannot pass both a select and fk parameter when creating a has-one relationship'
427 12 50 66     67 if $p{select} && $p{fk};
428              
429             my $class
430             = $p{select}
431 12 100       49 ? 'Fey::Meta::HasOne::ViaSelect'
432             : 'Fey::Meta::HasOne::ViaFK';
433              
434 12         52 $p{foreign_table} = delete $p{table};
435              
436             $p{is_cached} = delete $p{cache}
437 12 100       56 if exists $p{cache};
438             $p{allows_undef} = delete $p{undef}
439 12 100       45 if exists $p{undef};
440              
441 12         435 my $has_one = $class->new(
442             table => $self->table(),
443             namer => $self->policy()->has_one_namer(),
444             %p,
445             );
446              
447 11         68 $has_one->attach_to_class($self);
448              
449 9         62159 $self->_add_has_one( $has_one->name() => $has_one );
450             }
451              
452             sub remove_has_one {
453 8     8 1 10840 my $self = shift;
454 8         15 my $name = shift;
455              
456 8 50       370 return unless $self->_has_has_one($name);
457              
458 8         308 my $has_one = $self->_has_one($name);
459              
460 8         47 $has_one->detach_from_class();
461              
462 8         267 $self->_remove_has_one( $has_one->name() );
463             }
464              
465             sub add_has_many {
466 10     10 1 19 my $self = shift;
467 10         37 my %p = @_;
468              
469 10 50       534 param_error 'You must call has_table() before calling has_many().'
470             unless $self->_has_table();
471              
472             param_error
473             'You cannot pass both a select and fk parameter when creating a has-many relationship'
474 10 50 66     64 if $p{select} && $p{fk};
475              
476             my $class
477             = $p{select}
478 10 100       48 ? 'Fey::Meta::HasMany::ViaSelect'
479             : 'Fey::Meta::HasMany::ViaFK';
480              
481 10         37 $p{foreign_table} = delete $p{table};
482              
483             $p{is_cached} = delete $p{cache}
484 10 100       50 if exists $p{cache};
485              
486 10         418 my $has_many = $class->new(
487             table => $self->table(),
488             namer => $self->policy()->has_many_namer(),
489             %p,
490             );
491              
492 9         80 $has_many->attach_to_class($self);
493              
494 7         924 $self->_add_has_many( $has_many->name() => $has_many );
495             }
496              
497             sub remove_has_many {
498 5     5 1 13325 my $self = shift;
499 5         12 my $name = shift;
500              
501 5 100       303 return unless $self->_has_has_many($name);
502              
503 4         204 my $has_many = $self->_has_many($name);
504              
505 4         25 $has_many->detach_from_class();
506              
507 4         210 $self->_remove_has_many( $has_many->name() );
508             }
509              
510             sub _build_count_sql {
511 0     0     my $self = shift;
512              
513 0           my $table = $self->table();
514              
515 0           my $select = $self->schema_class()->SQLFactoryClass()->new_select();
516              
517 0           $select->select( Fey::Literal::Function->new( 'COUNT', '*' ) )
518             ->from($table);
519              
520 0           return $select;
521             }
522              
523             sub add_query_method {
524 0     0 1   my $self = shift;
525              
526 0           my $method = Fey::Meta::Method::FromSelect->new(
527             package_name => $self->name(),
528             @_,
529             );
530              
531 0           $self->add_method( $method->name() => $method );
532              
533 0           return;
534             }
535              
536             sub make_immutable {
537             shift->SUPER::make_immutable(
538 0     0 1   @_,
539             constructor_class => 'Fey::Meta::Method::Constructor',
540             );
541             }
542              
543             if ( $Moose::VERSION >= 1.9900 ) {
544              
545             # XXX - can we refactor Moose/CMOP core to make overriding (and copying)
546             # all of this unnecessary?
547             override _inline_new_object => sub {
548             my $self = shift;
549              
550             #<<<
551             return (
552             'my $class = shift;',
553             '$class = Scalar::Util::blessed($class) || $class;',
554             $self->_inline_fallback_constructor('$class'),
555             $self->_inline_params( '$params', '$class' ),
556             $self->_inline_search_cache(),
557             'my $instance;',
558             '$class->_ClearConstructorError();',
559             'my @args = @_;',
560             'Try::Tiny::try {',
561             '@_ = @args;',
562             $self->_inline_generate_instance( '$instance', '$class' ),
563             $self->_inline_slot_initializers,
564             $self->_inline_preserve_weak_metaclasses,
565             $self->_inline_extra_init,
566             '}',
567             'Try::Tiny::catch {',
568             'die $_ unless Scalar::Util::blessed($_) && $_->isa(q{Fey::Exception::NoSuchRow});',
569             '$class->_SetConstructorError($_);',
570             'undef $instance;',
571             '};',
572             'return unless $instance;',
573             $self->_inline_write_to_cache(),
574             'return $instance',
575             );
576             #>>>
577             };
578              
579             # The default version of this sticks a "my" in front of the declaration,
580             # but we want to declare the $instance var earlier.
581             override _inline_generate_instance => sub {
582             my $self = shift;
583             my ( $inst, $class ) = @_;
584             return (
585             $inst . ' = ' . $self->_inline_create_instance($class) . ';',
586             );
587             };
588             }
589              
590             sub _inline_search_cache {
591 0     0     my $self = shift;
592              
593             #<<<
594             return
595 0           ( 'if ( $metaclass->_object_cache_is_enabled() ) {',
596             'my $cached = $metaclass->_search_cache($params);',
597             'return $cached if $cached;',
598             '}'
599             );
600             #>>>
601             }
602              
603             sub _inline_write_to_cache {
604 0     0     my $self = shift;
605              
606             return
607 0           '$metaclass->_write_to_cache($instance) if $metaclass->_object_cache_is_enabled();';
608             }
609              
610             __PACKAGE__->meta()->make_immutable();
611              
612             1;
613              
614             # ABSTRACT: A metaclass for table classes
615              
616             __END__
617              
618             =pod
619              
620             =head1 NAME
621              
622             Fey::Meta::Class::Table - A metaclass for table classes
623              
624             =head1 VERSION
625              
626             version 0.47
627              
628             =head1 SYNOPSIS
629              
630             package MyApp::User;
631              
632             use Fey::ORM::Table;
633              
634             print __PACKAGE__->meta()->ClassForTable($table);
635              
636             =head1 DESCRIPTION
637              
638             This is the metaclass for table classes. When you use
639             L<Fey::ORM::Table> in your class, it uses this class to do all the
640             heavy lifting.
641              
642             =head1 METHODS
643              
644             This class provides the following methods:
645              
646             =head2 Fey::Meta::Class::Table->ClassForTable( $table1, $table2 )
647              
648             Given one or more L<Fey::Table> objects, this method returns the name
649             of the class which "has" that table, if any.
650              
651             =head2 Fey::Meta::Class::Table->TableForClass($class)
652              
653             Given a class, this method returns the L<Fey::Table> object associated
654             with that class, if any.
655              
656             =head2 $meta->table()
657              
658             Returns the L<Fey::Table> for the metaclass's class.
659              
660             =head2 $meta->add_has_one(%options)
661              
662             Creates a new L<Fey::Meta::HasOne::ViaFK> or
663             L<Fey::Meta::HasOne::ViaSelect> object and adds it to the
664             metaclass. Internally, this will call C<attach_to_class()> on the
665             C<HasOne> meta-object.
666              
667             =head2 $meta->has_ones()
668              
669             Returns a list of the C<HasOne> objects added to this metaclass.
670              
671             =head2 $meta->remove_has_one($name)
672              
673             Removes the named C<HasOne> meta-object. Internally, this will call
674             C<detach_from_class()> on the C<HasOne> meta-object.
675              
676             =head2 $meta->add_has_many(%options)
677              
678             Creates a new L<Fey::Meta::HasMany::ViaFK> or
679             L<Fey::Meta::HasMany::ViaSelect> object and adds it to the
680             metaclass. Internally, this will call C<attach_to_class()> on the
681             C<HasMany> meta-object.
682              
683             =head2 $meta->has_manies()
684              
685             Returns a list of the C<HasMany> objects added to this metaclass.
686              
687             =head2 $meta->remove_has_many($name)
688              
689             Removes the named C<HasMany> meta-object. Internally, this will call
690             C<detach_from_class()> on the C<HasMany> meta-object.
691              
692             =head2 $meta->has_inflator($name)
693              
694             Returns a boolean indicating whether or not there is an inflator
695             defined for the named column.
696              
697             =head2 $meta->has_deflator($name)
698              
699             Returns a boolean indicating whether or not there is an inflator
700             defined for the named column.
701              
702             =head2 $meta->add_query_method(%options)
703              
704             Adds a new L<Fey::Meta::Method::FromSelect> method to the class. The
705             C<%options> passed to this method will be passed to the
706             L<Fey::Meta::Method::FromSelect> constructor.
707              
708             =head2 $meta->make_immutable()
709              
710             This class overrides C<< Moose::Meta::Class->make_immutable() >> in
711             order to do its own optimizations for immutability.
712              
713             =head1 AUTHOR
714              
715             Dave Rolsky <autarch@urth.org>
716              
717             =head1 COPYRIGHT AND LICENSE
718              
719             This software is copyright (c) 2011 - 2015 by Dave Rolsky.
720              
721             This is free software; you can redistribute it and/or modify it under
722             the same terms as the Perl 5 programming language system itself.
723              
724             =cut