File Coverage

blib/lib/Jifty/DBI/Record/Memcached.pm
Criterion Covered Total %
statement 22 98 22.4
branch 0 34 0.0
condition 0 11 0.0
subroutine 8 20 40.0
pod 4 4 100.0
total 34 167 20.3


line stmt bran cond sub pod time code
1 2     2   231118 use warnings;
  2         5  
  2         84  
2 2     2   11 use strict;
  2         6  
  2         101  
3              
4             package Jifty::DBI::Record::Memcached;
5              
6 2     2   885 use Jifty::DBI::Record;
  2         7  
  2         15  
7 2     2   14 use Jifty::DBI::Handle;
  2         4  
  2         23  
8 2     2   54 use base qw (Jifty::DBI::Record);
  2         4  
  2         219  
9              
10 2     2   14 use Cache::Memcached;
  2         4  
  2         32  
11              
12              
13             =head1 NAME
14              
15             Jifty::DBI::Record::Memcached - records with caching behavior
16              
17             =head1 SYNOPSIS
18              
19             package Myrecord;
20             use base qw/Jifty::DBI::Record::Memcached/;
21              
22             =head1 DESCRIPTION
23              
24             This module subclasses the main L package to add a
25             caching layer.
26              
27             The public interface remains the same, except that records which have
28             been loaded in the last few seconds may be reused by subsequent get
29             or load methods without retrieving them from the database.
30              
31             =head1 METHODS
32              
33             =cut
34              
35              
36 2     2   73 use vars qw/$MEMCACHED/;
  2         4  
  2         2340  
37              
38              
39              
40              
41             # Function: _init
42             # Type : class ctor
43             # Args : see Jifty::DBI::Record::new
44             # Lvalue : Jifty::DBI::Record::Cachable
45              
46             sub _init () {
47 0     0   0 my ( $self, @args ) = @_;
48 0   0     0 $MEMCACHED ||= Cache::Memcached->new( {$self->memcached_config} );
49 0         0 $self->SUPER::_init(@args);
50             }
51              
52             =head2 load_from_hash
53              
54             Overrides the implementation from L to add support for caching.
55              
56             =cut
57              
58             sub load_from_hash {
59 0     0 1 0 my $self = shift;
60              
61             # Blow away the primary cache key since we're loading.
62 0 0       0 if ( ref($self) ) {
63 0         0 my ( $rvalue, $msg ) = $self->SUPER::load_from_hash(@_);
64             ## Check the return value, if its good, cache it!
65 0 0       0 $self->_store() if ($rvalue);
66 0         0 return ( $rvalue, $msg );
67             } else {
68 0         0 $self = $self->SUPER::load_from_hash(@_);
69             ## Check the return value, if its good, cache it!
70 0 0       0 $self->_store() if ( $self->id );
71 0         0 return $self;
72              
73             }
74             }
75              
76             =head2 load_by_cols
77              
78             Overrides the implementation from L to add support for caching.
79              
80             =cut
81              
82             sub load_by_cols {
83 0     0 1 0 my ( $class, %attr ) = @_;
84              
85 0         0 my ($self);
86 0 0       0 if ( ref($class) ) {
87 0         0 ( $self, $class ) = ( $class, undef );
88             } else {
89 0   0     0 $self = $class->new( handle => ( delete $attr{'_handle'} || undef ) );
90             }
91              
92             ## Generate the cache key
93 0         0 my $key = $self->_gen_load_by_cols_key(%attr);
94 0 0       0 if ( $self->_get($key) ) {
95 0 0       0 if ($class) { return $self }
  0         0  
96 0         0 else { return ( 1, "Fetched from cache" ) }
97             }
98             ## Fetch from the DB!
99 0         0 my ( $rvalue, $msg ) = $self->SUPER::load_by_cols(%attr);
100             ## Check the return value, if its good, cache it!
101 0 0       0 if ($rvalue) {
102 0         0 $self->_store();
103 0 0       0 if ( $key ne $self->_primary_key ) {
104 0         0 my $cache_key = $self->_primary_cache_key;
105 0 0       0 $MEMCACHED->add( $key, $cache_key,
106             $self->_cache_config->{'cache_for_sec'} )
107             if defined $cache_key;
108 0         0 $self->{'loaded_by_cols'} = $key;
109             }
110             }
111 0 0       0 if ($class) { return $self }
  0         0  
112             else {
113 0         0 return ( $rvalue, $msg );
114             }
115             }
116              
117             # Function: __set
118             # Type : (overloaded) public instance
119             # Args : see Jifty::DBI::Record::_Set
120             # Lvalue : ?
121              
122             sub __set () {
123 0     0   0 my ( $self, %attr ) = @_;
124 0         0 $self->_expire();
125 0         0 return $self->SUPER::__set(%attr);
126              
127             }
128              
129             # Function: _delete
130             # Type : (overloaded) public instance
131             # Args : nil
132             # Lvalue : ?
133              
134             sub __delete () {
135 0     0   0 my ($self) = @_;
136 0         0 $self->_expire();
137 0         0 return $self->SUPER::__delete();
138             }
139              
140             # Function: _expire
141             # Type : private instance
142             # Args : string(cache_key)
143             # Lvalue : 1
144             # Desc : Removes this object from the cache.
145              
146             sub _expire (\$) {
147 0     0   0 my $self = shift;
148 0         0 $MEMCACHED->delete($self->_primary_cache_key);
149 0 0       0 $MEMCACHED->delete($self->{'loaded_by_cols'}) if ($self->{'loaded_by_cols'});
150              
151             }
152              
153             # Function: _get
154             # Type : private instance
155             # Args : string(cache_key)
156             # Lvalue : 1
157             # Desc : Get an object from the cache, and make this object that.
158              
159             sub _get () {
160 0     0   0 my ( $self, $cache_key ) = @_;
161 0 0       0 my $data = $MEMCACHED->get($cache_key) or return;
162             # If the cache value is a scalar, that's another key
163 0 0       0 unless (ref $data) { $data = $MEMCACHED->get($data); }
  0         0  
164 0 0       0 unless (ref $data) { return undef; }
  0         0  
165 0         0 @{$self}{ keys %$data } = values %$data; # deserialize
  0         0  
166             }
167              
168             # Function: _store
169             # Type : private instance
170             # Args : string(cache_key)
171             # Lvalue : 1
172             # Desc : Stores this object in the cache.
173              
174             sub _store (\$) {
175 0     0   0 my $self = shift;
176             # Blow away the primary cache key since we're loading.
177 0         0 $self->{'_jifty_cache_pkey'} = undef;
178 0         0 $MEMCACHED->set( $self->_primary_cache_key,
179             { values => $self->{'values'},
180             table => $self->table,
181             fetched => $self->{'fetched'},
182             raw_values => $self->{'raw_values'},
183             },
184             $self->_cache_config->{'cache_for_sec'}
185             );
186             }
187              
188              
189             # Function: _gen_load_by_cols_key
190             # Type : private instance
191             # Args : hash (attr)
192             # Lvalue : 1
193             # Desc : Takes a perl hash and generates a key from it.
194              
195             sub _gen_load_by_cols_key {
196 0     0   0 my ( $self, %attr ) = @_;
197              
198 0         0 my $cache_key = $self->cache_key_prefix . '-'. $self->table() . ':';
199 0         0 my @items;
200 0         0 while ( my ( $key, $value ) = each %attr ) {
201 0   0     0 $key ||= '__undef';
202 0   0     0 $value ||= '__undef';
203              
204 0 0       0 if ( ref($value) eq "HASH" ) {
205 0   0     0 $value = ( $value->{operator} || '=' ) . $value->{value};
206             } else {
207 0         0 $value = "=" . $value;
208             }
209 0         0 push @items, $key.$value;
210              
211             }
212 0         0 $cache_key .= join(',',@items);
213 0         0 return ($cache_key);
214             }
215              
216             # Function: _primary_cache_key
217             # Type : private instance
218             # Args : none
219             # Lvalue: : 1
220             # Desc : generate a primary-key based variant of this object's cache key
221             # primary keys is in the cache
222              
223             sub _primary_cache_key {
224 0     0   0 my ($self) = @_;
225              
226 0 0       0 return undef unless ( defined $self->id );
227              
228 0 0       0 unless ( $self->{'_jifty_cache_pkey'} ) {
229              
230 0         0 my $primary_cache_key = $self->cache_key_prefix .'-' .$self->table() . ':';
231 0         0 my @attributes;
232 0         0 foreach my $key ( @{ $self->_primary_keys } ) {
  0         0  
233 0         0 push @attributes, $key . '=' . $self->SUPER::__value($key);
234             }
235              
236 0         0 $primary_cache_key .= join( ',', @attributes );
237              
238 0         0 $self->{'_jifty_cache_pkey'} = $primary_cache_key;
239             }
240 0         0 return ( $self->{'_jifty_cache_pkey'} );
241              
242             }
243              
244             =head2 _cache_config
245              
246             You can override this method to change the duration of the caching
247             from the default of 5 seconds.
248              
249             For example, to cache records for up to 30 seconds, add the following
250             method to your class:
251              
252             sub _cache_config {
253             { 'cache_for_sec' => 30 }
254             }
255              
256             =cut
257              
258             sub _cache_config {
259             {
260 0     0   0 'cache_for_sec' => 180,
261             };
262             }
263              
264             =head2 memcached_config
265              
266             Returns a hash containing arguments to pass to L during construction. The defaults are like:
267              
268             (
269             services => [ '127.0.0.1:11211' ],
270             debug => 0,
271             )
272              
273             You may want to override this method if you want a customized cache configuration:
274              
275             sub memcached_config {
276             (
277             servers => [ '10.0.0.15:11211', '10.0.0.15:11212',
278             '10.0.0.17:11211', [ '10.0.0.17:11211', 3 ] ],
279             debug => 0,
280             compress_threshold => 10_000,
281             );
282             }
283              
284             =cut
285              
286              
287             sub memcached_config {
288 1     1 1 44 servers => ['127.0.0.1:11211'],
289             debug => 0
290              
291             }
292              
293             =head2 cache_key_prefix
294              
295             Returns the prefix we should prepend to all cache keys. If you're using one memcached for multiple
296             applications, you want this to be different for each application or they might end up mingling data.
297              
298             =cut
299              
300             sub cache_key_prefix {
301 0     0 1   return 'Jifty-DBI';
302             }
303              
304             1;
305              
306             __END__