File Coverage

blib/lib/Jifty/DBI/Record/Memcached.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1 2     2   231302 use warnings;
  2         3  
  2         68  
2 2     2   7 use strict;
  2         3  
  2         59  
3              
4             package Jifty::DBI::Record::Memcached;
5              
6 2     2   473 use Jifty::DBI::Record;
  0            
  0            
7             use Jifty::DBI::Handle;
8             use base qw (Jifty::DBI::Record);
9              
10             use Cache::Memcached;
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<Jifty::DBI::Record> 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             use vars qw/$MEMCACHED/;
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             my ( $self, @args ) = @_;
48             $MEMCACHED ||= Cache::Memcached->new( {$self->memcached_config} );
49             $self->SUPER::_init(@args);
50             }
51              
52             sub load_from_hash {
53             my $self = shift;
54              
55             # Blow away the primary cache key since we're loading.
56             if ( ref($self) ) {
57             my ( $rvalue, $msg ) = $self->SUPER::load_from_hash(@_);
58             ## Check the return value, if its good, cache it!
59             $self->_store() if ($rvalue);
60             return ( $rvalue, $msg );
61             } else {
62             $self = $self->SUPER::load_from_hash(@_);
63             ## Check the return value, if its good, cache it!
64             $self->_store() if ( $self->id );
65             return $self;
66              
67             }
68             }
69              
70             sub load_by_cols {
71             my ( $class, %attr ) = @_;
72              
73             my ($self);
74             if ( ref($class) ) {
75             ( $self, $class ) = ( $class, undef );
76             } else {
77             $self = $self->new( handle => ( delete $attr{'_handle'} || undef ) );
78             }
79              
80             ## Generate the cache key
81             my $key = $self->_gen_load_by_cols_key(%attr);
82             if ( $self->_get($key) ) {
83             if ($class) { return $self }
84             else { return ( 1, "Fetched from cache" ) }
85             }
86             ## Fetch from the DB!
87             my ( $rvalue, $msg ) = $self->SUPER::load_by_cols(%attr);
88             ## Check the return value, if its good, cache it!
89             if ($rvalue) {
90             $self->_store();
91             if ( $key ne $self->_primary_key ) {
92             $MEMCACHED->add( $key, $self->_primary_cache_key,
93             $self->_cache_config->{'cache_for_sec'} );
94             $self->{'loaded_by_cols'} = $key;
95             }
96             }
97             if ($class) { return $self }
98             else {
99             return ( $rvalue, $msg );
100             }
101             }
102              
103             # Function: __set
104             # Type : (overloaded) public instance
105             # Args : see Jifty::DBI::Record::_Set
106             # Lvalue : ?
107              
108             sub __set () {
109             my ( $self, %attr ) = @_;
110             $self->_expire();
111             return $self->SUPER::__set(%attr);
112              
113             }
114              
115             # Function: _delete
116             # Type : (overloaded) public instance
117             # Args : nil
118             # Lvalue : ?
119              
120             sub __delete () {
121             my ($self) = @_;
122             $self->_expire();
123             return $self->SUPER::__delete();
124             }
125              
126             # Function: _expire
127             # Type : private instance
128             # Args : string(cache_key)
129             # Lvalue : 1
130             # Desc : Removes this object from the cache.
131              
132             sub _expire (\$) {
133             my $self = shift;
134             $MEMCACHED->delete($self->_primary_cache_key);
135             $MEMCACHED->delete($self->{'loaded_by_cols'}) if ($self->{'loaded_by_cols'});
136              
137             }
138              
139             # Function: _get
140             # Type : private instance
141             # Args : string(cache_key)
142             # Lvalue : 1
143             # Desc : Get an object from the cache, and make this object that.
144              
145             sub _get () {
146             my ( $self, $cache_key ) = @_;
147             my $data = $MEMCACHED->get($cache_key) or return;
148             # If the cache value is a scalar, that's another key
149             unless (ref $data) { $data = $MEMCACHED->get($data); }
150             unless (ref $data) { return undef; }
151             @{$self}{ keys %$data } = values %$data; # deserialize
152             }
153              
154             sub __value {
155             my $self = shift;
156             my $column = shift;
157             return ( $self->SUPER::__value($column) );
158             }
159              
160             # Function: _store
161             # Type : private instance
162             # Args : string(cache_key)
163             # Lvalue : 1
164             # Desc : Stores this object in the cache.
165              
166             sub _store (\$) {
167             my $self = shift;
168             # Blow away the primary cache key since we're loading.
169             $self->{'_jifty_cache_pkey'} = undef;
170             $MEMCACHED->set( $self->_primary_cache_key,
171             { values => $self->{'values'},
172             table => $self->table,
173             fetched => $self->{'fetched'}
174             },
175             $self->_cache_config->{'cache_for_sec'}
176             );
177             }
178              
179              
180             # Function: _gen_load_by_cols_key
181             # Type : private instance
182             # Args : hash (attr)
183             # Lvalue : 1
184             # Desc : Takes a perl hash and generates a key from it.
185              
186             sub _gen_load_by_cols_key {
187             my ( $self, %attr ) = @_;
188              
189             my $cache_key = $self->cache_key_prefix . '-'. $self->table() . ':';
190             my @items;
191             while ( my ( $key, $value ) = each %attr ) {
192             $key ||= '__undef';
193             $value ||= '__undef';
194              
195             if ( ref($value) eq "HASH" ) {
196             $value = ( $value->{operator} || '=' ) . $value->{value};
197             } else {
198             $value = "=" . $value;
199             }
200             push @items, $key.$value;
201              
202             }
203             $cache_key .= join(',',@items);
204             return ($cache_key);
205             }
206              
207             # Function: _primary_cache_key
208             # Type : private instance
209             # Args : none
210             # Lvalue: : 1
211             # Desc : generate a primary-key based variant of this object's cache key
212             # primary keys is in the cache
213              
214             sub _primary_cache_key {
215             my ($self) = @_;
216              
217             return undef unless ( $self->id );
218              
219             unless ( $self->{'_jifty_cache_pkey'} ) {
220              
221             my $primary_cache_key = $self->cache_key_prefix .'-' .$self->table() . ':';
222             my @attributes;
223             foreach my $key ( @{ $self->_primary_keys } ) {
224             push @attributes, $key . '=' . $self->SUPER::__value($key);
225             }
226              
227             $primary_cache_key .= join( ',', @attributes );
228              
229             $self->{'_jifty_cache_pkey'} = $primary_cache_key;
230             }
231             return ( $self->{'_jifty_cache_pkey'} );
232              
233             }
234              
235             =head2 _cache_config
236              
237             You can override this method to change the duration of the caching
238             from the default of 5 seconds.
239              
240             For example, to cache records for up to 30 seconds, add the following
241             method to your class:
242              
243             sub _cache_config {
244             { 'cache_for_sec' => 30 }
245             }
246              
247             =cut
248              
249             sub _cache_config {
250             {
251             'cache_for_sec' => 180,
252             };
253             }
254              
255              
256             sub memcached_config {
257             servers => ['127.0.0.1:11211'],
258             debug => 0
259              
260             }
261              
262             =head2 cache_key_prefix
263              
264             Returns the prefix we should prepend to all cache keys. If you're using one memcached for multiple
265             applications, you want this to be different for each application or they might end up mingling data.
266              
267             =cut
268              
269             sub cache_key_prefix {
270             return 'Jifty-DBI';
271             }
272              
273             1;
274              
275             __END__
276              
277              
278             =head1 AUTHOR
279              
280             Matt Knopp <mhat@netlag.com>
281              
282             =head1 SEE ALSO
283              
284             L<Jifty::DBI>, L<Jifty::DBI::Record>
285              
286             =cut
287              
288