File Coverage

blib/lib/DBIx/SearchBuilder/Record/Cachable.pm
Criterion Covered Total %
statement 70 92 76.0
branch 12 24 50.0
condition 11 23 47.8
subroutine 16 22 72.7
pod 3 3 100.0
total 112 164 68.2


line stmt bran cond sub pod time code
1             package DBIx::SearchBuilder::Record::Cachable;
2              
3 2     2   126264 use strict;
  2         16  
  2         60  
4 2     2   9 use warnings;
  2         20  
  2         72  
5              
6 2     2   633 use DBIx::SearchBuilder::Handle;
  2         4  
  2         64  
7 2     2   971 use Cache::Simple::TimedExpiry;
  2         1472  
  2         66  
8              
9 2     2   13 use base qw(DBIx::SearchBuilder::Record);
  2         15  
  2         1791  
10              
11             =head1 NAME
12              
13             DBIx::SearchBuilder::Record::Cachable - Records with caching behavior
14              
15             =head1 SYNOPSIS
16              
17             package MyRecord;
18             use base qw/DBIx::SearchBuilder::Record::Cachable/;
19              
20             =head1 DESCRIPTION
21              
22             This module subclasses the main L package
23             to add a caching layer.
24              
25             The public interface remains the same, except that records which have
26             been loaded in the last few seconds may be reused by subsequent fetch
27             or load methods without retrieving them from the database.
28              
29             =head1 METHODS
30              
31             =cut
32              
33              
34             my %_CACHES = ();
35              
36             sub _SetupCache {
37 4     4   12 my ($self, $cache) = @_;
38 4         28 $_CACHES{$cache} = Cache::Simple::TimedExpiry->new();
39 4         33 $_CACHES{$cache}->expire_after( $self->_CacheConfig->{'cache_for_sec'} );
40 4         64 return $_CACHES{$cache};
41             }
42              
43              
44             =head2 FlushCache
45              
46             This class method flushes the _global_ DBIx::SearchBuilder::Record::Cachable
47             cache. All caches are immediately expired.
48              
49             =cut
50              
51             sub FlushCache {
52 1     1 1 13 %_CACHES = ();
53             }
54              
55             =head2 _FlushKeyCache
56              
57             Blow away this record type's key cache
58              
59             =cut
60              
61             sub _FlushKeyCache {
62 0     0   0 my $self = shift;
63 0   0     0 my $cache = ($self->{_class}||= ref($self))."-KEYS";
64 0         0 return $self->_SetupCache($cache);
65             }
66              
67             sub _KeyCache {
68 6     6   10 my $self = shift;
69 6   66     26 my $cache = ($self->{_class}||= ref($self))."-KEYS";
70 6   66     27 return $_CACHES{$cache} || $self->_SetupCache($cache);
71             }
72              
73             sub _RecordCache {
74 6     6   10 my $self = shift;
75 6   33     16 my $cache = ($self->{_class}||= ref($self));
76 6   66     28 return $_CACHES{$cache} || $self->_SetupCache($cache);
77             }
78              
79             # Function: LoadFromHash
80             # Type : (overloaded) public instance
81             # Args : See DBIx::SearchBuilder::Record::LoadFromHash
82             # Lvalue : array(boolean, message)
83              
84             sub LoadFromHash {
85 0     0 1 0 my $self = shift;
86              
87             # Blow away the primary cache key since we're loading.
88 0         0 $self->{'_SB_Record_Primary_RecordCache_key'} = undef;
89 0         0 my ( $rvalue, $msg ) = $self->SUPER::LoadFromHash(@_);
90              
91             ## Check the return value, if its good, cache it!
92 0 0       0 $self->_store if $rvalue;
93              
94 0         0 return ( $rvalue, $msg );
95             }
96              
97             # Function: LoadByCols
98             # Type : (overloaded) public instance
99             # Args : see DBIx::SearchBuilder::Record::LoadByCols
100             # Lvalue : array(boolean, message)
101              
102             sub LoadByCols {
103 4     4 1 16 my ( $self, %attr ) = @_;
104              
105             # Blow away the primary cache key since we're loading.
106 4         8 $self->{'_SB_Record_Primary_RecordCache_key'} = undef;
107              
108             # generate the alternate cache key
109 4         21 my $alt_key = $self->_gen_alternate_RecordCache_key(%attr);
110             # get primary cache key
111 4         15 my $cache_key = $self->_lookup_primary_RecordCache_key($alt_key);
112 4 100 66     142 if ( $cache_key && $self->_fetch( $cache_key ) ) {
113 2         10 return ( 1, "Fetched from cache" );
114             }
115              
116             # Fetch from the DB!
117 2         68 my ( $rvalue, $msg ) = $self->SUPER::LoadByCols(%attr);
118             # Check the return value, if its good, cache it!
119 2 50       7 if ($rvalue) {
120 2         13 $self->_store();
121             # store alt_key as alias for pk
122 2         8 $self->_KeyCache->set( $alt_key, $self->_primary_RecordCache_key);
123             }
124 2         46 return ( $rvalue, $msg );
125             }
126              
127             # Function: __Set
128             # Type : (overloaded) public instance
129             # Args : see DBIx::SearchBuilder::Record::_Set
130             # Lvalue : ?
131              
132             sub __Set () {
133 0     0   0 my $self = shift;
134              
135 0         0 $self->_expire;
136              
137 0         0 return $self->SUPER::__Set( @_ );
138             }
139              
140             # Function: Delete
141             # Type : (overloaded) public instance
142             # Args : nil
143             # Lvalue : ?
144              
145             sub __Delete () {
146 0     0   0 my $self = shift;
147              
148 0         0 $self->_expire;
149              
150 0         0 return $self->SUPER::__Delete( @_ );
151              
152             }
153              
154             # Function: _expire
155             # Type : private instance
156             # Args : string(cache_key)
157             # Lvalue : 1
158             # Desc : Removes this object from the cache.
159              
160             sub _expire (\$) {
161 0     0   0 my $self = shift;
162 0 0       0 my $cache_key = $self->_primary_RecordCache_key or return;
163 0         0 $self->_RecordCache->set( $cache_key, undef, time-1 );
164             # We should be doing something more surgical to clean out the
165             # key cache. but we do need to expire it
166 0         0 $self->_FlushKeyCache;
167             }
168              
169             # Function: _fetch
170             # Type : private instance
171             # Args : string(cache_key)
172             # Lvalue : 1
173             # Desc : Get an object from the cache, and make this object that.
174              
175             sub _fetch () {
176 4     4   10 my ( $self, $cache_key ) = @_;
177 4 100       12 my $data = $self->_RecordCache->fetch( $cache_key ) or return 0;
178 2         29 @{$self}{keys %$data} = values %$data; # deserialize
  2         6  
179 2         8 return 1;
180             }
181              
182              
183             # Function: _store
184             # Type : private instance
185             # Args : string(cache_key)
186             # Lvalue : 1
187             # Desc : Stores this object in the cache.
188              
189             sub _store (\$) {
190 2     2   5 my $self = shift;
191 2 50       7 my $key = $self->_primary_RecordCache_key or return 0;
192 2         7 $self->_RecordCache->set( $key, $self->_serialize );
193 2         36 return 1;
194             }
195              
196             sub _serialize {
197 2     2   4 my $self = shift;
198             return {
199             values => $self->{'values'},
200             table => $self->Table,
201 2         8 fetched => $self->{'fetched'}
202             };
203             }
204              
205             # Function: _gen_alternate_RecordCache_key
206             # Type : private instance
207             # Args : hash (attr)
208             # Lvalue : 1
209             # Desc : Takes a perl hash and generates a key from it.
210              
211             sub _gen_alternate_RecordCache_key {
212 4     4   12 my ( $self, %attr ) = @_;
213 4         6 my $cache_key = '';
214 4         25 foreach my $key ( sort keys %attr ) {
215 4         10 my $value = $attr{$key};
216 4 50       15 unless ( defined $value ) {
    50          
217 0         0 $value = '=__undef';
218             }
219 0         0 elsif ( ref($value) eq "HASH" ) {
220             $value = ( $value->{operator} || '=' )
221 0 0 0     0 . ( defined $value->{value}? $value->{value}: '__undef' );
222             }
223             else {
224 4         10 $value = "=" . $value;
225             }
226 4         13 $cache_key .= $key . $value . ',';
227             }
228 4         12 chop($cache_key);
229 4         10 return ($cache_key);
230             }
231              
232             # Function: _primary_RecordCache_key
233             # Type : private instance
234             # Args : none
235             # Lvalue: : 1
236             # Desc : generate a primary-key based variant of this object's cache key
237             # primary keys is in the cache
238              
239             sub _primary_RecordCache_key {
240 4     4   8 my ($self) = @_;
241              
242             return $self->{'_SB_Record_Primary_RecordCache_key'}
243 4 100       15 if $self->{'_SB_Record_Primary_RecordCache_key'};
244              
245 2         4 my $cache_key = '';
246 2         6 my %pk = $self->PrimaryKeys;
247 2         8 foreach my $key ( sort keys %pk ) {
248 2         13 my $value = $pk{$key};
249 2 50       7 return undef unless defined $value;
250 2         9 $cache_key .= $key . '=' . $value .',';
251             }
252 2         5 chop $cache_key;
253 2         8 return $self->{'_SB_Record_Primary_RecordCache_key'} = $cache_key;
254             }
255              
256             # Function: lookup_primary_RecordCache_key
257             # Type : private class
258             # Args : string(alternate cache id)
259             # Lvalue : string(cache id)
260              
261             sub _lookup_primary_RecordCache_key {
262 4     4   9 my ($self, $key) = @_;
263 4 50       10 return undef unless $key;
264 4   66     17 return $self->_KeyCache->fetch($key) || $key;
265             }
266              
267             =head2 _CacheConfig
268              
269             You can override this method to change the duration of the caching from the default of 5 seconds.
270              
271             For example, to cache records for up to 30 seconds, add the following method to your class:
272              
273             sub _CacheConfig {
274             { 'cache_for_sec' => 30 }
275             }
276              
277             =cut
278              
279             sub _CacheConfig {
280             return {
281 0     0     'cache_for_sec' => 5,
282             };
283             }
284              
285             1;