File Coverage

blib/lib/DBIx/MoCo/Relation.pm
Criterion Covered Total %
statement 140 146 95.8
branch 47 68 69.1
condition 29 42 69.0
subroutine 20 20 100.0
pod 0 5 0.0
total 236 281 83.9


line stmt bran cond sub pod time code
1             package DBIx::MoCo::Relation;
2 15     15   90 use strict;
  15         36  
  15         606  
3 15     15   82 use Carp;
  15         33  
  15         1328  
4 15     15   11916 use UNIVERSAL::require;
  15         28089  
  15         253  
5              
6             my $relation = {};
7              
8             sub register {
9 96     96 0 248 my $class = shift;
10 96         211 my ($klass, $type, $attr, $model, $option) = @_;
11              
12 96 50       731 $model->require or die $@;
13 96         8033 $model->import;
14              
15 96   100     815 $relation->{$klass} ||= {has_a => {},has_many => {}};
16 96   50     699 $relation->{$klass}->{$type}->{$attr} = {
17             class => $model,
18             option => $option || '',
19             };
20 96         390 my $registry = 'register_' . $type;
21 96         390 $class->$registry(@_);
22 96         384 $class->register_flusher(@_);
23             }
24              
25             sub register_has_a {
26 48     48 0 96 my $class = shift;
27 48         136 my ($klass, $type, $attr, $model, $option) = @_;
28 48         93 my ($my_key, $other_key);
29 48 50       174 $option->{key} or return;
30 48 50       202 if (ref $option->{key} eq 'HASH') {
31 0         0 ($my_key, $other_key) = %{$option->{key}};
  0         0  
32             } else {
33 48         122 $my_key = $other_key = $option->{key};
34             }
35 48         84 my $icache_key = $attr;
36 48         420 my $cs = $klass->cache_status;
37 48         201 my $subname = $klass . '::' . $attr;
38 15     15   4078 no strict 'refs';
  15         48  
  15         504  
39 15     15   77 no warnings 'redefine';
  15         36  
  15         8746  
40 48 100       539 if ($klass->icache_expiration) {
41             *$subname = sub {
42 4     4   1701 my $self = shift;
43 4         22 my $ic = $self->icache;
44 4 100 66     34 if ($ic && defined $ic->{$icache_key}) {
45 2         6 $cs->{retrieve_count}++;
46 2         5 $cs->{retrieve_icache_count}++;
47 2         14 return $ic->{$icache_key};
48             } else {
49 2 50       12 defined $self->{$my_key} or return;
50 2   50     38 my $o = $model->retrieve($other_key => $self->{$my_key}) || undef;
51 2 50       11 $ic->{$icache_key} = $o if $o;
52 2         74 return $o;
53             }
54 1         21 };
55             } else {
56             *$subname = sub {
57 30     30   410 my $self = shift;
58 30 100       425 defined $self->{$my_key} or return;
59 27 100       191 $model->retrieve($other_key => $self->{$my_key}) || undef;
60 47         1325 };
61             }
62             }
63              
64             sub register_has_many {
65 48     48 0 122 my $class = shift;
66 48         116 my ($klass, $type, $attr, $model, $option) = @_;
67             # my $array_key = $klass->has_many_keys_name($attr);
68             # my $max_key = $klass->has_many_max_offset_name($attr);
69              
70 48 50       161 $option->{key} or confess 'key is not specified';
71              
72             {
73 15     15   261 no strict 'refs';
  15         31  
  15         1869  
  48         84  
74 15     15   86 no warnings 'redefine';
  15         28  
  15         13201  
75              
76 48         341 *{"$klass\::$attr"} = sub {
77 34 50 66 34   19387 my $extend = pop if ref $_[-1] and ref $_[-1] eq 'HASH';
78 34         80 my ($self, $off, $lt) = @_;
79              
80 34   100     389 $extend ||= {};
81 34   100     160 $off ||= 0;
82 34 100       566 my $max_off = defined $lt ? $off + $lt : undef;
83 34         181 my $icache = $self->icache;
84 34         167 my $cache_key = $self->has_many_keys_cache_name($attr);
85 34         85 my $icache_key = $attr;
86 34         176 my $cs = $klass->cache_status;
87              
88 34         89 $cs->{has_many_count}++;
89              
90             my $cond_cached = sub {
91 34     34   80 my ($keys, $max_offset) = @_;
92 34 100       133 defined $keys or return;
93 22 50       85 defined $keys->{array} or return;
94              
95 22   66     545 return (defined $keys->{max_offset} && $keys->{max_offset} == -1)
96             || ($max_off && 0 <= $max_off && $max_off <= $keys->{max_offset});
97 34         227 };
98              
99 34         141 my $keys = $self->cache($cache_key);
100              
101 34 100       113 if ( $cond_cached->($keys, $max_off) ) {
102             # warn "use cache for $cache_key";
103 19         42 $cs->{has_many_cache_count}++;
104              
105 19 100 100     182 if ($icache && $icache->{$icache_key}) {
106 7         113 $cs->{has_many_icache_count}++;
107             # warn "use icache $icache_key for " . $self;
108 7 100       53 return $icache->{$icache_key}->slice( $off, defined $max_off ? $max_off - 1 : undef );
109             }
110              
111             # warn "$attr cache($keys->{max_offset}) is in range $max_off";
112             } else {
113             # warn "use db for $cache_key";
114 15         27 my ($my_key, $other_key);
115 0         0 ref $option->{key} eq 'HASH'
116 15 50       108 ? ($my_key, $other_key) = %{$option->{key}}
117             : $my_key = $other_key = $option->{key};
118              
119 15 50       53 defined $self->{$my_key} or return;
120              
121 15         44 my $where_clause = "$other_key = ?";
122 15 50       57 $where_clause = join ' AND ', "$other_key = ?", $option->{condition} if $option->{condition};
123              
124 15 50       508 $keys = {
125             array => $model->db->search(
126             where => [ $where_clause, $self->{$my_key} ],
127 15 50 50     121 field => join(',', @{$model->retrieve_keys || $model->primary_keys}),
    50 50        
    100 66        
      100        
128             table => $model->table,
129             order => $option ? $option->{order} || '' : '',
130             group => $option ? $option->{group} || '' : '',
131             limit => (defined $max_off && $max_off > 0) ? $max_off : '',
132             ),
133             max_offset => $max_off || -1,
134             };
135              
136 15         129 $self->cache($cache_key, $keys);
137             # warn @{$self->{$array_key}};
138             }
139              
140 27 50 66     124 my $last = ($max_off && $max_off <= $#{$keys->{array}}) ? $max_off - 1 : $#{$keys->{array}};
  27         162  
141              
142 27         46 my $res;
143 27 100       83 if ($icache) {
144             # warn "set icache and return";
145 5         8 $icache->{$icache_key} = $model->retrieve_multi(@{$keys->{array}});
  5         41  
146 5         32 $res = $icache->{$icache_key}->slice($off, $last);
147             } else {
148 22   100     122 $res = $model->retrieve_multi(@{$keys->{array}}[$off || 0 .. $last]);
  22         163  
149             }
150              
151 27   33     202 my $with = $extend->{with} || $class->find_relation_by_attr($klass => $attr)->{option}->{with};
152 27         52 my $without = $extend->{without};
153              
154 27 50 33     94 if ($with and $res->size > 0) {
155 0         0 $model->merge_with($res, $with, $without);
156             }
157              
158 27 50       317 wantarray ? @$res : $res;
159 48         484 };
160             }
161             }
162              
163             sub register_flusher {
164 96     96 0 135 shift; # Relation
165 96         213 my ($klass, $type, $attr, $model, $option) = @_;
166 96         227 my $flusher = $klass . '::flush_belongs_to';
167 15     15   189 no strict 'refs';
  15         36  
  15         498  
168 15     15   76 no warnings 'redefine';
  15         29  
  15         6660  
169             *$flusher = sub {
170             # warn "level 1 flusher called for $flusher";
171 10     10   28 my ($class, $self) = @_;
172 10 50       49 $self or confess '$self is not specified';
173 10         43 my $has_a = $relation->{$klass}->{has_a};
174 10         50 for my $attr (keys %$has_a) {
175 6         17 my $ha = $has_a->{$attr};
176 6         16 my $oa = [];
177 6         41 my $other = $relation->{$ha->{class}};
178 6         13 for my $oattr (keys %{$other->{has_many}}) {
  6         31  
179 11         30 my $hm = $other->{has_many}->{$oattr};
180 11 100       51 if ($hm->{class} eq $class) {
181             # push @$oa, $ha->{class}->has_many_keys_name($oattr);
182 5         20 push @$oa, $oattr;
183             }
184             }
185 6         59 $ha->{other_attrs} = $oa;
186             # warn join(' / ', %$ha);
187             }
188             *$flusher = sub {
189             # warn "level 2 flusher called for $flusher";
190 38     38   472 my ($class, $self) = @_;
191 38         249 for my $attr (keys %$has_a) {
192 23 100       103 my $parent = $self->$attr() or next;
193 13         27 for my $oattr (@{$has_a->{$attr}->{other_attrs}}) {
  13         107  
194             # warn "call $self->$attr->flush($oattr)";
195 9         68 $parent->flush_has_many_keys($oattr);
196 9         124 $parent->flush_icache($oattr);
197             }
198             }
199 10         122 };
200 10         68 goto &$flusher;
201 96         1367 };
202             }
203              
204             sub find_relation_by_attr {
205 27     27 0 64 my ($class, $klass, $attr) = @_;
206 27 50       111 $relation->{$klass} or return;
207              
208 27 50       119 if (my $has_a = $relation->{$klass}->{has_a}->{$attr}) {
209 0         0 return $has_a;
210             }
211              
212 27 50       130 if (my $has_many = $relation->{$klass}->{has_many}->{$attr}) {
213 27         139 return $has_many;
214             }
215              
216 0           return;
217             }
218              
219             1;
220              
221             __END__