File Coverage

blib/lib/DBIx/MoCo.pm
Criterion Covered Total %
statement 523 573 91.2
branch 211 312 67.6
condition 43 83 51.8
subroutine 85 89 95.5
pod 30 53 56.6
total 892 1110 80.3


line stmt bran cond sub pod time code
1             package DBIx::MoCo;
2 15     15   792121 use strict;
  15         38  
  15         579  
3 15     15   83 use warnings;
  15         45  
  15         414  
4 15     15   77 use base qw (Class::Data::Inheritable);
  15         31  
  15         13460  
5              
6 15     15   15524 use DBIx::MoCo::Relation;
  15         42  
  15         175  
7 15     15   9428 use DBIx::MoCo::List;
  15         88  
  15         274  
8 15     15   10333 use DBIx::MoCo::Cache;
  15         44  
  15         196  
9 15     15   9131 use DBIx::MoCo::Cache::Dummy;
  15         208  
  15         156  
10 15     15   8617 use DBIx::MoCo::Schema;
  15         46  
  15         150  
11 15     15   7732 use DBIx::MoCo::Column;
  15         42  
  15         167  
12              
13 15     15   347 use Carp;
  15         29  
  15         732  
14 15     15   12284 use Class::Trigger;
  15         21843  
  15         102  
15 15     15   21047 use Tie::IxHash;
  15         88563  
  15         187  
16 15     15   520 use File::Spec;
  15         37  
  15         173  
17 15     15   359 use UNIVERSAL::require;
  15         53  
  15         120  
18              
19             our $VERSION = '0.18';
20             our $AUTOLOAD;
21              
22             my $cache_status = {
23             retrieve_count => 0,
24             retrieve_cache_count => 0,
25             retrieve_icache_count => 0,
26             retrieve_all_count => 0,
27             has_many_count => 0,
28             has_many_cache_count => 0,
29             has_many_icache_count => 0,
30             retrieved_oids => [],
31             };
32             my ($db,$session,$schema);
33              
34             __PACKAGE__->mk_classdata($_) for
35             qw(cache_object default_cache_expiration icache_expiration
36             cache_null_object table cache_cols_only _db_object save_explicitly list_class);
37              
38             ## NOTE: INIT block does not work well under mod_perl or POE.
39             ## Please set cache_object() explicitly if you want to use transparent caching.
40             # INIT {
41             # unless (defined __PACKAGE__->cache_object) {
42             # if (Cache::FastMmap->require) {
43             # my $file = File::Spec->catfile('/tmp', __PACKAGE__);
44              
45             # File::Spec->require or die $@;
46             # __PACKAGE__->cache_object(
47             # Cache::FastMmap->new(
48             # share_file => $file,
49             # unlink_on_exit => 1,
50             # expire_time => 600, # sec
51             # ) or die $!
52             # );
53              
54             # chmod(0666, $file) or die $! if -e $file;
55             # } else {
56             # warn "Using DBIx::MoCo::Cache is now deprecated because of memory leak."
57             # . "Install Cache::FastMmap instead, or setup cache_object explicitly.";
58              
59             # DBIx::MoCo::Cache->require or die $@;
60             # __PACKAGE__->cache_object( DBIx::MoCo::Cache->new );
61             # }
62             # }
63             # }
64              
65             __PACKAGE__->default_cache_expiration(60 * 60 * 3); # 3 hours
66             __PACKAGE__->icache_expiration(0); # Instance cache
67             __PACKAGE__->cache_null_object(1);
68              
69             # SESSION & CACHE CONTROLLERS
70             __PACKAGE__->add_trigger(after_create => sub {
71             my ($class, $self) = @_;
72             $self or confess '$self is not specified';
73             $class->store_self_cache($self);
74             $class->flush_belongs_to($self);
75             });
76             __PACKAGE__->add_trigger(before_update => sub {
77             my ($class, $self) = @_;
78             $self or confess '$self is not specified';
79             $class->flush_self_cache($self);
80             });
81             __PACKAGE__->add_trigger(after_update => sub {
82             my ($class, $self) = @_;
83             $self or confess '$self is not specified';
84             $class->store_self_cache($self);
85             });
86             __PACKAGE__->add_trigger(before_delete => sub {
87             my ($class, $self) = @_;
88             $self or confess '$self is not specified';
89             $class->flush_self_cache($self);
90             $class->flush_belongs_to($self);
91             });
92              
93 139     139 1 1897 sub cache_status { $cache_status }
94              
95             sub cache {
96 385     385 0 935 my $class = shift;
97 385 100       1324 $class = ref($class) if ref($class);
98              
99             ## It is no matter costs of creating Dummy objects because it is a singleton.
100 385   66     1566 my $cache = $class->cache_object || DBIx::MoCo::Cache::Dummy->instance;
101              
102 385         6787 my ($k,$v,$ex) = @_;
103             # warn "$cache in $class";
104 385         1344 my $s = $class->is_in_session;
105 385 100       1474 if (defined $v) {
    50          
106 200   33     1953 $ex ||= $class->default_cache_expiration;
107 200 50 33     3651 $ex = "+$ex" if ($ex && ref($cache) eq 'Cache::Memory');
108 200 100       821 if ($v eq '') {
109 28 50       190 if ($cache->can('remove')) {
110 28         112 $cache->remove($k);
111             }
112 28 100       117 if ($s) {
113 1 50       5 delete $s->{cache}->{$k} if $k;
114             }
115             } else {
116 172 100 66     975 if ($class->cache_cols_only && ref($v) &&
      100        
      66        
117             ref($v) =~ /::/ && $v->isa($class)) {
118             # remove additional elements
119 42         1137 my @cols = @{$v->columns};
  42         164  
120 42         118 for (qw(changed_cols to_be_updated object_id)) {
121 126 100       588 push @cols, $_ if (defined $v->{$_});
122             }
123 42         315 my $hash = {map {$_ => $v->{$_}} @cols};
  168         673  
124 42         243 my $o = bless $hash, $class;
125 42         278 $cache->set($k,$o,$ex);
126 42 50       246 $s->{cache}->{$k} = $o if $s;
127             } else {
128 130         1927 $cache->set($k,$v,$ex);
129 130 100       509 $s->{cache}->{$k} = $v if $s;
130             }
131             }
132             # warn $cache . '->set(' . $k . ')';
133 200         888 return $v;
134             } elsif ($k) {
135             # warn "hit session cache for $k" if ($s && $s->{cache}->{$k});
136 185   100     1697 return $s->{cache}->{$k} || $cache->get($k);
137             }
138             }
139              
140 0     0 1 0 sub flush_belongs_to {} # it's delivered from MoCo::Relation
141              
142             sub flush_self_cache {
143 26     26 1 60 my ($class, $self) = @_;
144 26 50 33     128 if (!$self && ref $class) {
145 0         0 $self = $class;
146 0         0 $class = ref $self;
147             }
148 26 50       98 $self or confess '$self is not specified';
149              
150 26 100       277 return unless $class->cache_object;
151              
152 10 50       121 my $rm = $class->cache_object->can('remove') ? 'remove' : 'delete';
153 10         130 for (@{$self->object_ids}) {
  10         31  
154             # warn "flush $_";
155             #weaken($class->cache($_));
156 18         65 $class->cache_object->$rm($_);
157             }
158             }
159              
160             sub store_self_cache {
161 86     86 1 264 my ($class, $self) = @_;
162 86 100 66     547 if (!$self && ref $class) {
163 3         8 $self = $class;
164 3         9 $class = ref $self;
165             }
166 86 50       583 $self or confess '$self is not specified';
167             # warn "store $_" for @{$self->object_ids};
168 86         552 my $icache = $self->icache;
169 86         620 $self->flush_icache;
170 86         147 $class->cache($_, $self) for @{$self->object_ids};
  86         616  
171 86 100       555 $self->icache($icache) if $icache;
172             }
173              
174             sub icache {
175 125     125 0 422 my $self = shift;
176 125 100       420 if ($_[0]) {
177 1         4 $self->{_icache} = shift;
178             } else {
179 124         786 my $ex = $self->icache_expiration;
180 124 100       1576 $ex > 0 or return;
181 17 100 100     161 if (!$self->{_icache} ||
182             ($self->{_icache}->{_created} + $ex < time())) {
183 5         58 $self->{_icache} = {_created => time()};
184             }
185             }
186 18         75 return $self->{_icache};
187             }
188              
189             sub flush_icache {
190 96     96 0 219 my $self = shift;
191 96 100       1409 $self->{_icache} or return;
192 2 50       8 if ($_[0]) {
193             # warn "flush icache $_[0] for " . $self;
194 0         0 delete $self->{_icache}->{$_[0]};
195             } else {
196 2         6 $self->{_icache} = undef;
197             }
198             }
199              
200             sub has_many_keys_cache_name {
201 46     46 0 528 my $self = shift;
202 46 50       144 my $attr = shift or return;
203 46 50       131 my $oid = $self->object_id or return;
204 46         323 return sprintf('%s-%s_keys', $oid, $attr);
205             }
206              
207             sub flush_has_many_keys {
208 11     11 0 40 my $self = shift;
209 11 50       51 my $attr = shift or return;
210             # $self->flush($self->has_many_keys_name($attr));
211             # $self->flush($self->has_many_max_offset_name($attr));
212 11         55 my $key = $self->has_many_keys_cache_name($attr);
213 11         36 $self->cache($key, '');
214             }
215              
216             # session controllers
217             sub start_session {
218 7     7 1 1934 my $class = shift;
219 7 100       96 $class->end_session if $class->is_in_session;
220 7         65 $session = {
221             changed_objects => [],
222             cache => {},
223             pid => $$,
224             created => time(),
225             };
226             }
227              
228 560     560 1 3579 sub is_in_session { $session }
229 15     15 0 368 sub session { $session }
230             sub session_cache {
231 0 0   0 0 0 my $s = shift->session or return;
232 0         0 return $s->{cache};
233             }
234              
235             sub end_session {
236 6     6 1 876 my $class = shift;
237 6 50       23 $session or return;
238 6         19 $class->save_changed;
239 6         20 $cache_status->{retrieved_oids} = [];
240 6         34 $session = undef;
241             }
242              
243             sub save_changed {
244 113     113 0 207 my $class = shift;
245 113 100       452 $class->is_in_session or return;
246 11         19 for (@{$class->session->{changed_objects}}) {
  11         29  
247 4 50       11 $_ or next;
248 4         23 $_->save;
249             }
250             }
251              
252             # CLASS DEFINISION METHODS
253 96     96 0 574 sub relation { 'DBIx::MoCo::Relation' }
254              
255             sub db_object {
256 212     212 0 6470 my $class = shift;
257 212 100       790 if (my $db = shift) {
258 19         201 $class->_db_object($db);
259             }
260 212         2152 $class->_db_object;
261             }
262              
263             sub has_a {
264 48     48 1 37357 my $class = shift;
265 48         315 $class->relation->register($class, 'has_a', @_);
266             }
267             sub has_many {
268 48     48 1 5718 my $class = shift;
269 48         225 $class->relation->register($class, 'has_many', @_);
270             }
271              
272             sub schema {
273 631     631 1 13768 my $class = shift;
274 631 100       1873 $class = ref $class if ref $class;
275 631 100       1932 unless ($schema->{$class}) {
276 20         189 $schema->{$class} = DBIx::MoCo::Schema->new($class);
277             }
278 631         3557 return $schema->{$class};
279             }
280              
281             for my $attr (qw/primary_keys unique_keys retrieve_keys columns/) {
282             my $classdata = "_" . $attr;
283             __PACKAGE__->mk_classdata($classdata);
284              
285 15     15   36525 no strict 'refs';
  15         44  
  15         52504  
286             *{__PACKAGE__ . "\::$attr"} = sub {
287 626     626   5375 my $class = shift;
288 626 100       1800 if (@_) {
289 1 50 33     13 my @keys = (ref $_[0] and ref $_[0] eq 'ARRAY') ? @{$_[0]} : @_;
  1         4  
290 1         6 $class->$classdata(\@keys);
291             } else {
292 625 100       3117 $class->$classdata
293             ? $class->$classdata
294             : $class->schema->$attr;
295             }
296             };
297             }
298              
299             sub has_muid {
300 66     66 0 125 my $class = shift;
301             return ($class->has_column('muid') &&
302 66   33     355 scalar @{$class->primary_keys} == 1);
303             }
304              
305             sub has_column {
306 71     71 1 145 my $class = shift;
307 71 50       225 my $col = shift or return;
308 71 100       553 $class->columns or return;
309 70         157 grep { $col eq $_ } @{$class->columns};
  224         938  
  70         244  
310             }
311              
312             sub utf8_columns {
313 5     5 1 1515 my $class = shift;
314 5         29 $class->schema->utf8_columns(@_);
315             }
316              
317             sub is_utf8_column {
318 3     3 0 269 my $class = shift;
319 3 50       15 my $col = shift or return;
320 3 50       25 my $utf8 = $class->utf8_columns or return;
321 3 50       16 ref $utf8 eq 'ARRAY' or return;
322 3         7 return grep { $_ eq $col } @$utf8;
  6         30  
323             }
324              
325             # DATA OPERATIONAL METHODS
326             sub object_id {
327 658     658 0 1814 my $self = shift;
328 658   66     2835 my $class = ref($self) || $self;
329 658 100       1951 $self = undef unless ref($self);
330 658 100 100     3129 if ($self && $self->{object_id}) {
331 226         1239 return $self->{object_id};
332             }
333 432   50     1786 my $prefix = $class->object_id_prefix || '';
334 432         776 my ($key, $col);
335 432 50 66     2101 if ($self && @{$class->retrieve_keys || $class->primary_keys}) {
  66 100       388  
    100          
    100          
336 66 50       589 if ($self->has_muid) {
337 0         0 $key = $self->muid;
338             } else {
339 66 50       140 for (sort @{$class->retrieve_keys || $class->primary_keys}) {
  66         997  
340 76 50 0     596 defined($self->{$_}) or warn "$_ is undefined for $self" and return;
341 76         1084 $key .= "-$_-" . $self->{$_};
342             }
343 66 50       256 $key or die "couldn't create object_id for " . $self;
344 66         268 $key = $prefix . $key;
345             }
346 281         1010 } elsif ($_[3]) {
347 85         252 my %args = @_;
348 85         690 $key .= "-$_-$args{$_}" for (sort keys %args);
349 85         293 $key = $prefix . $key;
350             } elsif (@{$class->primary_keys} == 1) {
351 257         1381 my @args = @_;
352 257 100       781 $col = defined $args[1] ? $args[0] : $class->primary_keys->[0];
353 257 100       864 my $value = defined $args[1] ? $args[1] : $args[0];
354 257 50       610 if ($col eq 'muid') {
355 0         0 $key = $value;
356             } else {
357 257         1069 $key = $prefix . '-' . $col . '-' . $value;
358             }
359             }
360 432 100       2096 $self->{object_id} = $key if $self;;
361 432         2579 return $key;
362             }
363              
364             sub object_id_prefix {
365 432     432 1 739 my $class = shift;
366 432 50       1044 $class = ref $class if ref $class;
367 432         1540 return $class;
368             }
369              
370 193     193 0 1045 sub db { $_[0]->db_object }
371              
372             sub retrieve {
373 97     97 1 29062 my $cs = $cache_status;
374 97         329 $cs->{retrieve_count}++;
375 97         186 my $class = shift;
376 97 50       339 $_[0] or carp "Retrieve keys not found";
377 97         701 my $oid = $class->object_id(@_);
378 97         470 my $c = $class->cache($oid);
379 97 100       281 if (defined $c) {
380             # warn "use cache $oid";
381 47         114 $cs->{retrieve_cache_count}++;
382 47         225 return $c;
383             } else {
384             # warn "use db $oid";
385 50         301 my $o = $class->retrieve_by_db(@_);
386 50 100       368 if ($o) {
387 33         212 $class->store_self_cache($o);
388 33 100       104 push @{$cs->{retrieved_oids}}, $oid if $class->is_in_session;
  1         5  
389             } else {
390             # $class->cache($oid => $o) if $o;
391             # cache null object for performance.
392 17 50       139 $class->cache($oid => $o) if $class->cache_null_object;
393             }
394 50         356 return $o;
395             }
396             }
397              
398             sub retrieve_by_db {
399 50     50 1 102 my $class = shift;
400 50 100       258 my %args = defined $_[1] ? @_ : ($class->primary_keys->[0] => $_[0]);
401 50         373 my $res = $class->db->select($class->table,'*',\%args);
402 50         146 my $h = $res->[0];
403 50 100       476 return $h ? $class->new(%$h) : '';
404             }
405              
406             sub restore_from_db {
407 1     1 1 5 my $self = shift;
408 1 50       6 my $class = ref $self or return;
409 1 50       10 my $hash = $self->primary_keys_hash or return;
410 1         7 my $res = $class->db->select($class->table,'*',$hash);
411 1 50       6 my $h = $res->[0] or return;
412 1         4 @{$self}{keys %$h} = @{$h}{keys %$h};
  1         4  
  1         3  
413 1         7 $class->store_self_cache($self);
414 1         5 return $self;
415             }
416              
417             sub retrieve_multi {
418 27     27 1 48 my $class = shift;
419 27 100       110 my @list = @_ or return $class->_list([]);
420              
421 26         44 my (@cached_objects, @non_cached_queries);
422 26 50 33     110 if ($class->cache_object && $class->cache_object->can('get_multi')) {
423 0         0 my $ids = [ map { $class->object_id(%$_) } @list ];
  0         0  
424 0   0     0 my $hash = $class->cache_object->get_multi(@$ids) || {};
425              
426 0         0 for (my $i = 0; $i <= $#list; $i++) {
427 0         0 my $object = $hash->{$ids->[$i]};
428 0 0       0 $object
429             ? push @cached_objects, $object
430             : push @non_cached_queries, $list[$i];
431             }
432             } else {
433 26         613 for (@list) {
434 52         233 my $cached_object = $class->cache( $class->object_id(%$_) );
435 52 100       241 $cached_object
436             ? push @cached_objects, $cached_object
437             : push @non_cached_queries, $_;
438             }
439             }
440              
441             ## Updating cache status
442 26         124 $class->cache_status->{retrieve_count} += scalar @list;
443 26         67 $class->cache_status->{retrieve_cache_count} += scalar @cached_objects;
444              
445             ## All objects were found in cache.
446 26 100       84 if (@cached_objects == @list) {
447 21         110 my @ordered= $class->_merge_objects(\@list, @cached_objects);
448 21 50       589 wantarray ? return @ordered : return $class->_list(\@ordered);
449             }
450              
451 5         12 my (@clauses, @bind_values);
452 5         15 for my $cond (@non_cached_queries) {
453 12         44 my $subclause = join ' AND ', map {
454 7         24 push @bind_values, $cond->{$_};
455 12         60 sprintf "%s = ?", $_
456             } keys %$cond;
457              
458 7         27 push @clauses, $subclause;
459             }
460 5         13 my $where_clause = join ' OR ', map { sprintf "(%s)", $_ } @clauses;
  7         31  
461              
462 5         51 my @objects_from_db = $class->search( where => [ $where_clause, @bind_values ] );
463              
464 5 50       28 if ($class->is_in_session) {
465 0         0 push @{$class->cache_status->{retrieved_oids}}, map { $_->object_id } @objects_from_db;
  0         0  
  0         0  
466             }
467              
468 5         15 for my $object (@objects_from_db) {
469 7         41 $class->store_self_cache($object);
470             }
471              
472 5         44 my @merged = $class->_merge_objects(\@list, @cached_objects, @objects_from_db);
473 5 50       135 wantarray ? return @merged : return $class->_list(\@merged);
474             }
475              
476             sub _merge_objects {
477 26     26   59 my $class = shift;
478 26         42 my $order = shift;
479              
480 26         261 my $tied = tie my %idt, 'Tie::IxHash'; ## orderd Hash
481 26         497 $tied->Push($class->object_id( %$_ ) => undef) for @$order;
482              
483 26         685 for (@_) {
484 52         537 my $id = $_->object_id;
485 52 50       276 die "assert" if not exists $idt{$id};
486 52         436 $tied->Push($id => $_);
487             }
488              
489             ## cache_null_object() is now deprecated
490             # for (keys %idt) {
491             # if (not $idt{$_} and $class->cache_null_object) {
492             # $class->cache( $_ => '' );
493             # }
494             # }
495              
496 26         635 grep { defined $_ } values %idt;
  52         940  
497             }
498              
499             sub retrieve_or_create {
500 2     2 1 172 my $class = shift;
501 2         9 my %args = @_;
502 2         6 my %keys;
503 2         5 @keys{@{$class->primary_keys}} = @args{@{$class->primary_keys}};
  2         15  
  2         10  
504 2 100       29 $class->retrieve(%keys) || $class->create(%args);
505             }
506              
507             sub retrieve_all {
508 4     4 1 2106 my $cs = $cache_status;
509 4         11 $cs->{retrieve_all_count}++;
510 4         9 my $class = shift;
511 4         19 my %args = @_;
512 4         11 my $result = [];
513 4         25 my $list = $class->retrieve_all_id_hash(%args);
514 4         34 push @$result, $class->retrieve(%$_) for (@$list);
515 4 50       24 wantarray ? @$result :
516             $class->_list($result);
517             }
518              
519             sub retrieve_all_id_hash {
520 6     6 0 165 my $class = shift;
521 6         18 my %args = @_;
522 6         29 $args{table} = $class->table;
523 6 50       69 $args{field} = join(',', @{$class->retrieve_keys || $class->primary_keys});
  6         103  
524 6         442 my $res = $class->db->search(%args);
525 6         27 return $res;
526             }
527              
528             sub create {
529 26     26 1 5632 my $class = shift;
530 26         112 my %args = @_;
531 26         250 $class->call_trigger('before_create', \%args);
532 26         1350 my $o = $class->new(%args);
533             # if ($class->is_in_session && $o->has_primary_keys) {
534             # $o->set(to_be_inserted => 1);
535             # $o->changed_cols->{$_}++ for (keys %args);
536             # push @{$class->session->{changed_objects}}, $o;
537             # } else {
538 26 50       305 if ($class->save_explicitly) {
539 0         0 $o->set(to_be_inserted => 1);
540 0         0 $o->changed_cols->{$_}++ for keys %args;
541             } else {
542 26 50       408 $class->db->insert($class->table,\%args) or croak 'couldnt create';
543 26         385 my $pk = $class->primary_keys->[0];
544 26 100       370 unless (defined $args{$pk}) {
545 6         545 my $id = $class->db->last_insert_id;
546 6         205 $o->set($pk => $id);
547             }
548             }
549 26         285 $class->call_trigger('after_create', $o);
550 26         666 return $o;
551             }
552              
553             sub delete {
554 10     10 1 675 my $self = shift;
555 10 100       48 my $class = ref($self) ? ref($self) : $self;
556 10 100       39 $self = shift unless ref($self);
557 10 50       62 $self or return;
558 10         80 $self->call_trigger('before_delete', $self);
559 10 50       196 $self->has_primary_keys or return;
560 10         20 my %args;
561 10         19 for (@{$class->primary_keys}) {
  10         33  
562 10         55 $args{$_} = $self->{$_};
563 10 50       45 defined($args{$_}) or die "$self doesn't have $_";
564             }
565 10 50       34 %args or die "$self doesn't have where condition";
566 10 50       47 my $res = $class->db->delete($class->table,\%args) or croak 'couldnt delete';
567 10         48 $self = undef;
568 10         273 return $res;
569             }
570              
571             sub delete_all {
572 1     1 1 3 my $class = shift;
573 1         12 my %args = @_;
574 1 50       10 ref $args{where} eq 'HASH' or die 'please specify where in hash';
575 1         11 my $list = $class->retrieve_all_id_hash(%args);
576 1         5 my $caches = [];
577 1         4 for (@$list) {
578 2         13 my $oid = $class->object_id(%$_);
579 2 50       9 my $c = $class->cache($oid) or next;
580 2         9 push @$caches, $c;
581             }
582 1         8 $class->call_trigger('before_delete', $_) for (@$caches);
583 1 50       21 $class->db->delete($class->table,$args{where}) or croak 'couldnt delete';
584 1         34 return 1;
585             }
586              
587             sub search {
588 21     21 1 2947 my $class = shift;
589 21         182 my %args = @_;
590              
591 21         57 my $with = delete $args{with};
592              
593 21         204 $args{table} = $class->table;
594 21         355 my $res = $class->db->search(%args);
595 21         216 $_ = $class->new(%$_) for @$res;
596 21 50       79 $class->merge_with($res, $with) if $with;
597              
598 21 100       184 wantarray ? @$res : $class->_list($res);
599             }
600              
601             sub merge_with {
602 0     0 0 0 my ($class, $res, $with, $without) = @_;
603              
604 0 0 0     0 my @with_attrs = (ref $with and ref $with eq 'ARRAY') ? @$with : $with;
605              
606 0 0       0 if ($without) {
607 0 0 0     0 my @withouts = (ref $without and ref $without eq 'ARRAY') ? @$without : $without;
608 0         0 my $regex = sprintf '(?:^%s$)', join '|', @withouts;
609 0         0 @with_attrs = grep { $_ !~ m/$regex/ } @with_attrs;
  0         0  
610             }
611              
612 0         0 for my $with_attr (@with_attrs) {
613 0 0       0 my $rel = $class->relation->find_relation_by_attr($class => $with_attr)
614             or croak "No such relation for attr '$with_attr' in $class";
615              
616 0 0       0 my $key = $rel->{option}->{key} or next;
617              
618 0         0 my ($my_key, $other_key);
619 0 0 0     0 (ref $key and ref $key eq 'HASH')
620             ? ($my_key, $other_key) = %$key
621             : $my_key = $other_key = $key;
622              
623 0         0 my @queries = map { +{ $other_key => $_->$my_key } } @$res;
  0         0  
624              
625             ## Only creating caches for less SQL queries.
626             ## Those caches will be stored to the session cache if the session is activated.
627 0         0 $rel->{class}->retrieve_multi(@queries);
628             }
629              
630 0         0 $res;
631             }
632              
633             sub count {
634 7     7 1 2513 my $class = shift;
635 7         16 my $where = '';
636 7 50       83 if ($_[1]) {
    100          
637 0         0 my %args = @_;
638 0         0 $where = \%args;
639             } elsif ($_[0]) {
640 3         7 $where = shift;
641             }
642 7         39 my $res = $class->db->search(
643             table => $class->table,
644             field => 'COUNT(*) as count',
645             where => $where,
646             );
647 7   50     73 return $res->[0]->{count} || 0;
648             }
649              
650             sub find {
651 2     2 1 362 my $class = shift;
652 2         4 my $where;
653 2 50       15 if ($_[1]) {
    50          
654 0         0 my %args = @_;
655 0         0 $where = \%args;
656             } elsif ($_[0]) {
657 2         6 $where = shift;
658             } else {
659 0         0 return;
660             }
661 2         19 $class->search(
662             where => $where,
663             offset => 0,
664             limit => 1,
665             )->first;
666             }
667              
668             sub quote {
669 3     3 1 2714 my $class = shift;
670 3         14 $class->db->dbh->quote(shift);
671             }
672              
673             sub scalar {
674 0     0 0 0 my ($class, $method, @args) = @_;
675 0         0 scalar $class->$method(@args);
676             }
677              
678             sub AUTOLOAD {
679 43     43   15106 my $self = $_[0];
680 43   66     188 my $class = ref($self) || $self;
681 43 100       139 $self = undef unless ref($self);
682 43         411 (my $method = $AUTOLOAD) =~ s!.+::!!;
683 43 50       295 return if $method eq 'DESTROY';
684 15     15   130 no strict 'refs';
  15         38  
  15         6384  
685 43 100 100     412 if ($method =~ /^retrieve_by_(.+?)(_or_create)?$/o) {
    100          
    100          
686 8         34 my ($by, $create) = ($1,$2);
687 8 100       68 *$AUTOLOAD = $create ? $class->_retrieve_by_or_create_handler($by) :
688             $class->_retrieve_by_handler($by);
689             } elsif ($method =~ /^(\w+)_as_(\w+)$/o) {
690 7         33 my ($col,$as) = ($1,$2);
691 7         57 *$AUTOLOAD = $class->_column_as_handler($col, $as);
692             } elsif (defined $self->{$method} || $class->has_column($method)) {
693 24     111   246 *$AUTOLOAD = sub { shift->param($method, @_) };
  111         24006  
694             } else {
695 4         1035 croak sprintf 'Can\'t locate object method "%s" via package %s', $method, $class;
696             }
697 39         251 goto &$AUTOLOAD;
698             }
699              
700             sub inflate_column {
701 1     1 0 723 my $class = shift;
702 1 50       7 @_ % 2 and croak "You gave me an odd number of parameters to inflate_column()";
703              
704 1         6 my %args = @_;
705 1         14 while (my ($col, $as) = each %args) {
706 15     15   124 no strict 'refs';
  15         42  
  15         487  
707 15     15   132 no warnings 'redefine';
  15         31  
  15         5136  
708              
709 2 100 66     19 if (ref $as and ref $as eq 'HASH') {
710 1         4 for (qw/inflate deflate/) {
711 2 50 33     20 if ($as->{$_} and ref $as->{$_} ne 'CODE') {
712 0         0 croak sprintf "parameter '%s' takes only CODE reference", $_
713             }
714             }
715              
716 1         17 *{"$class\::$col"} = sub {
717 6     6   36816 my $self = shift;
718 6 100       29 if (@_) {
719 1 50       14 $as->{deflate}
720             ? $self->param( $col => $as->{deflate}->(@_) )
721             : $self->param( $col => @_ );
722             } else {
723 5 50       38 $as->{inflate}
724             ? $as->{inflate}->( $self->param($col) )
725             : $self->param( $col );
726             }
727             }
728 1         7 } else {
729 1         9 *{"$class\::$col"} = $class->_column_as_handler($col, $as);
  1         21  
730             }
731             }
732             }
733              
734             {
735             my $real_can = \&UNIVERSAL::can;
736 15     15   597 no warnings 'redefine', 'once';
  15         29  
  15         1377  
737             *DBIx::MoCo::can = sub {
738 106     106   5886 my ($class, $method) = @_;
739 106 100       800 if (my $sub = $real_can->(@_)) {
740             # warn "found $method in $class";
741 99         354 return $sub;
742             }
743 15     15   83 no strict 'refs';
  15         29  
  15         3820  
744 7 50       10 if (my $auto = *{$class . '::AUTOLOAD'}{CODE}) {
  7         59  
745 0         0 return $auto;
746             }
747 7         24 $AUTOLOAD = $class . '::' . $method;
748 7 50       38 eval {&DBIx::MoCo::AUTOLOAD(@_)} unless *$AUTOLOAD{CODE};
  7         24  
749 7         232 return *$AUTOLOAD{CODE};
750             };
751             }
752              
753             sub _column_as_handler {
754 10     10   23 my $class = shift;
755 10         29 my ($colname, $as) = @_;
756 10 100       174 unless (DBIx::MoCo::Column->can($as)) {
757 7         25 my $plugin = "DBIx::MoCo::Column::$as";
758 7         69 $plugin->require;
759 7 50       141 croak "Couldn't load column plugin $plugin: $@" if $@;
760             {
761 15     15   86 no strict 'refs';
  15         31  
  15         26838  
  7         15  
762 7         17 push @{"DBIx::MoCo::Column::ISA"}, $plugin;
  7         178  
763             }
764             }
765             return sub {
766 23     23   64666 my $self = shift;
767 23 50       127 my $column = $self->column($colname) or return;
768 22 100       92 if (my $new = shift) {
769 6         243 my $as_string = $as . '_as_string'; # e.g. URI_as_string
770 6 50       112 my $v = $column->can($as_string) ?
771             $column->$as_string($new) : scalar $new;
772 6         395 $self->param($colname => $v);
773             }
774 22         90 $self->column($colname)->$as();
775             }
776 10         154 }
777              
778             sub column {
779 46     46 0 3529 my $self = shift;
780 46 50       157 my $col = shift or return;
781 46         380 return DBIx::MoCo::Column->new($self->{$col});
782             }
783              
784             sub _retrieve_by_handler {
785 6     6   13 my $class = shift;
786 6 50       21 my $by = shift or return;
787 6 100       33 if ($by =~ /.+_or_.+/) {
788 2         10 my @keys = split('_or_', $by);
789             return sub {
790 3     3   8 my $self = shift;
791 3         6 my $v = shift;
792 3         7 for (@keys) {
793 4         16 my $o = $self->retrieve($_ => $v);
794 4 100       20 return $o if $o;
795             }
796 2         18 };
797             } else {
798 4         20 my @keys = split('_and_', $by);
799             return sub {
800 6     6   185 my $self = shift;
801 6         12 my %args;
802 6         28 @args{@keys} = @_;
803 6         42 $self->retrieve(%args);
804 4         40 };
805             }
806             }
807              
808             sub _retrieve_by_or_create_handler {
809 2     2   6 my $class = shift;
810 2 50       10 my $by = shift or return;
811 2         9 my @keys = split('_and_', $by);
812             return sub {
813 3     3   7 my $self = shift;
814 3         6 my %args;
815 3         11 @args{@keys} = @_;
816 3   66     16 return $self->retrieve(%args) || $class->create(%args);
817 2         18 };
818             }
819              
820             sub _list {
821 36     36   72 my $class = shift;
822              
823 36 100       203 if ($class->list_class) {
824 1         10 $class->list_class->require;
825 1 50 33     19 if ($@ and $@ !~ m/^Can\'t locate .+? in \@INC/) {
826 0         0 die $@;
827             }
828 1         3 return $class->list_class->new(@_);
829             } else {
830 35         548 return DBIx::MoCo::List->new(@_);
831             }
832             }
833              
834             sub DESTROY {
835 107     107   34408 my $class = shift;
836 107         482 $class->save_changed;
837             }
838              
839             sub new {
840 100     100 0 1594 my $class = shift;
841 100         360 my %args = @_;
842 100         207 my $self = \%args;
843 100         279 $self->{changed_cols} = {};
844 100         315 bless $self, $class;
845 100         476 $self;
846             }
847              
848             sub flush {
849 2     2 1 451 my $self = shift;
850 2 50       10 my $attr = shift or return;
851             # warn "flush " . $self->object_id . '->' . $attr;
852 2         6 $self->{$attr} = undef;
853 2         14 $self->store_self_cache($self);
854             }
855              
856             sub param {
857 128     128 1 2122 my $self = shift;
858 128 100       443 my $class = ref $self or return;
859 127 50       369 return unless(defined $_[0]);
860             # if (defined $_[1]) {
861 127 100       348 if (@_ > 1) {
862 14 50       147 @_ % 2 and croak "You gave me an odd number of parameters to param()!";
863 14         56 my %args = @_;
864 14         192 $class->call_trigger('before_update', $self, \%args);
865 14         420 $self->{$_} = $args{$_} for (keys %args);
866 14 100       64 if ($class->is_in_session) {
    50          
867 2         7 $self->{to_be_updated}++;
868 2         12 $self->{changed_cols}->{$_}++ for (keys %args);
869 2         5 push @{$class->session->{changed_objects}}, $self;
  2         27  
870             } elsif ($class->save_explicitly) {
871 0         0 $self->{to_be_updated}++;
872 0         0 $self->{changed_cols}->{$_}++ for keys %args;
873             } else {
874 12 50       168 my $where = $self->primary_keys_hash or return;
875 12 50       54 %$where or return;
876 12 50       124 $class->db->update($class->table,\%args,$where) or croak 'couldnt update';
877             }
878 14         281 $class->call_trigger('after_update', $self);
879             # return 1;
880             }
881 127         1531 return $self->{$_[0]};
882             }
883              
884             sub primary_keys_hash {
885 15     15 0 45 my $self = shift;
886 15 50       63 my $class = ref $self or return;
887 15 50       26 @{$class->primary_keys} or return;
  15         60  
888 15         76 my $hash = {};
889 15         42 for (@{$class->primary_keys}) {
  15         45  
890 15 50       166 defined $self->{$_} or return;
891 15         66 $hash->{$_} = $self->{$_};
892             }
893 15         82 return $hash;
894             }
895              
896             sub set {
897 10     10 1 1460 my $self = shift;
898 10         29 my ($k,$v) = @_;
899 10         102 $self->{$k} = $v;
900             }
901              
902             sub has_primary_keys {
903 10     10 0 22 my $self = shift;
904 10         24 my $class = ref $self;
905 10         19 for (@{$class->primary_keys}) {
  10         50  
906 10 50       74 defined $self->{$_} or return;
907             }
908 10         43 return 1;
909             }
910              
911             sub save {
912 8     8 1 15 my $self = shift;
913 8         13 my $class = ref $self;
914 8 100       15 keys %{$self->{changed_cols}} or return;
  8         771  
915 2         4 my %args;
916 2         4 for (keys %{$self->{changed_cols}}) {
  2         8  
917             # defined $self->{$_} or croak "$_ is undefined";
918 2 50       9 exists $self->{$_} or croak "$_ is undefined";
919 2         8 $args{$_} = $self->{$_};
920             }
921 2 50       21 if ($self->{to_be_inserted}) {
    50          
922 0         0 $class->db->insert($class->table,\%args);
923 0         0 $self->{changed_cols} = {};
924 0         0 $self->{to_be_inserted} = undef;
925             } elsif ($self->{to_be_updated}) {
926 2 50       16 my $where = $self->primary_keys_hash or return;
927 2 50       7 %$where or return;
928 2         8 $class->db->update($class->table,\%args,$where);
929 2         20 $self->{changed_cols} = {};
930 2         30 $self->{to_be_updated} = undef;
931             }
932             }
933              
934             sub object_ids { # returns all possible oids
935 97     97 0 192 my $self = shift;
936 97 50       378 my $class = ref $self or return;
937 97         223 my $oids = {};
938 97 50       408 $oids->{$self->object_id} = 1 if $self->object_id;
939 97         486 for my $key (@{$class->unique_keys}) {
  97         866  
940 162 100       1920 next unless defined $self->{$key};
941 160 100       742 my $oid = $class->object_id($key => $self->{$key}) or next;
942 136         696 $oids->{$oid}++;
943             }
944 97         2013 return [sort keys %$oids];
945             }
946              
947             1;
948              
949             __END__