Catalyst::Plugin::Cache draft
Yuval Kogman [Sun, 28 May 2006 20:00:52 +0000 (20:00 +0000)]
lib/Catalyst/Plugin/Cache.pm [new file with mode: 0644]
lib/Catalyst/Plugin/Cache/Backend.pm [new file with mode: 0644]
lib/Catalyst/Plugin/Cache/Backend/Util.pm [new file with mode: 0644]
lib/Catalyst/Plugin/Cache/Choose/KeyRegexes.pm [new file with mode: 0644]
t/basic.t [new file with mode: 0644]
t/key_regexes.t [new file with mode: 0644]

diff --git a/lib/Catalyst/Plugin/Cache.pm b/lib/Catalyst/Plugin/Cache.pm
new file mode 100644 (file)
index 0000000..6aebac4
--- /dev/null
@@ -0,0 +1,140 @@
+#!/usr/bin/perl
+
+package Catalyst::Plugin::Cache;
+use base qw/Class::Data::Inheritable/;
+
+use strict;
+use warnings;
+
+use Scalar::Util ();
+use Carp ();
+use NEXT;
+
+__PACKAGE__->mk_classdata( "_cache_backends" );
+
+sub setup {
+    my $app = shift;
+
+    # set it once per app, not once per plugin,
+    # and don't overwrite if some plugin was wicked
+    $app->_cache_backends({}) unless $app->_cache_backends;
+
+    my $ret = $app->NEXT::setup( @_ );
+
+    $app->setup_cache_backends;
+
+    $ret;
+}
+
+sub setup_cache_backends { shift->NEXT::setup_cache_backends(@_) }
+
+sub cache {
+    my $c = shift;
+
+    if ( @_ ) {
+        my $name = shift;
+        $c->get_cache_backend($name);
+    } else {
+        $c->default_cache_backend;
+    }
+}
+
+sub get_cache_backend {
+    my ( $c, $name ) = @_;
+    $c->_cache_backends->{$name};
+}
+
+sub register_cache_backend {
+    my ( $c, $name, $backend ) = @_;
+
+    no warnings 'uninitialized';
+    Carp::croak("$backend does not look like a cache backend - "
+    . "it must be an object supporting get, set and delete")
+        unless eval { $backend->can("get") && $backend->can("set") && $backend->can("delete") };
+
+    $c->_cache_backends->{$name} = $backend;
+}
+
+sub unregister_cache_backend {
+    my ( $c, $name ) = @_;
+    delete $c->_cache_backends->{$name};
+}
+
+sub default_cache_backend {
+    my $c = shift;
+    $c->get_cache_backend( "default" ) || $c->temporary_cache_backend;
+}
+
+sub temporary_cache_backend {
+    my $c = shift;
+    die "FIXME - make up an in memory cache backend, that hopefully works well for the current engine";
+}
+
+# this gets a shit name so that the plugins can override a good name
+sub choose_cache_backend_wrapper {
+    my ( $c, @meta ) = @_;
+
+    Carp::croak("meta data must be an even sized list") unless @meta % 2 == 0;
+
+    my %meta = @meta;
+    
+    # allow the cache client to specify who it wants to cache with (but loeave room for a hook)
+    if ( exists $meta{backend} ) {
+        if ( Scalar::Util::blessed($meta{backend}) ) {
+            return $meta{backend};
+        } else {
+            return $c->get_cache_backend( $meta{backend} ) || $c->default_cache_backend;
+        }
+    };
+    
+
+    $meta{caller} = [ caller(2) ] unless exists $meta{caller}; # might be interesting
+
+    if ( my $chosen = $c->choose_cache_backend( %meta ) ) {
+        $chosen = $c->get_cache_backend( $chosen ) unless Scalar::Util::blessed($chosen); # if it's a name find it
+        return $chosen if Scalar::Util::blessed($chosen); # only return if it was an object or name lookup worked
+
+        # FIXME
+        # die "no such backend"?
+        # currently, we fall back to default
+    }
+    
+    return $c->default_cache_backend;
+}
+
+sub choose_cache_backend { shift->NEXT::choose_cache_backend( @_ ) } # a convenient fallback
+
+sub cache_set {
+    my ( $c, $key, $value, @meta ) = @_;
+    $c->choose_cache_backend_wrapper( key =>  $key, value => $value, @meta )->set( $key, $value );
+}
+
+sub cache_get {
+    my ( $c, $key, @meta ) = @_;
+    $c->choose_cache_backend_wrapper( key => $key, @meta )->get( $key );
+}
+
+sub cache_delete {
+    my ( $c, $key, @meta ) = @_;
+    $c->choose_cache_backend_wrapper( key => $key, @meta )->delete( $key );
+}
+
+__PACKAGE__;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Catalyst::Plugin::Cache - 
+
+=head1 SYNOPSIS
+
+       use Catalyst::Plugin::Cache;
+
+=head1 DESCRIPTION
+
+=cut
+
+
diff --git a/lib/Catalyst/Plugin/Cache/Backend.pm b/lib/Catalyst/Plugin/Cache/Backend.pm
new file mode 100644 (file)
index 0000000..0da37bb
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/perl
+
+package Catalyst::Plugin::Cache::Backend;
+
+use strict;
+use warnings;
+
+sub set {
+    my ( $self, $key, $value ) = @_;
+}
+
+sub get {
+    my ( $self, $key ) = @_;
+}
+
+sub delete {
+    my ( $self, $key ) = @_;
+}
+
+__PACKAGE__;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Catalyst::Plugin::Cache::Backend - Bare minimum backend interface.
+
+=head1 SYNOPSIS
+
+       use Catalyst::Plugin::Cache::Backend;
+
+=head1 DESCRIPTION
+
+=cut
+
+
diff --git a/lib/Catalyst/Plugin/Cache/Backend/Util.pm b/lib/Catalyst/Plugin/Cache/Backend/Util.pm
new file mode 100644 (file)
index 0000000..38ccc0f
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/perl
+
+package Catalyst::Plugin::Cache::Backend::Util;
+
+use strict;
+use warnings;
+
+sub default_ttl {
+    my ( $self, $key ) = @_;
+    2 * 60 * 60; # 2 hours
+}
+
+sub expires {
+    my ( $self, $key ) = @_;
+    time() + $self->default_ttl( $key );
+}
+
+sub serialize_value {
+
+}
+
+sub deserialize_value {
+
+}
+
+__PACKAGE__;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Catalyst::Plugin::Cache::Backend::Util - Useful base class methods for cache
+backends.
+
+=head1 SYNOPSIS
+
+       use Catalyst::Plugin::Cache::Backend::Util;
+
+=head1 DESCRIPTION
+
+=cut
+
+
diff --git a/lib/Catalyst/Plugin/Cache/Choose/KeyRegexes.pm b/lib/Catalyst/Plugin/Cache/Choose/KeyRegexes.pm
new file mode 100644 (file)
index 0000000..66a5839
--- /dev/null
@@ -0,0 +1,54 @@
+#!/usr/bin/perl
+
+package Catalyst::Plugin::Cache::Choose::KeyRegexes;
+
+use strict;
+use warnings;
+
+sub setup {
+    my $app = shift;
+    my $ret = $app->NEXT::setup( @_ );
+
+    my $regexes = $app->config->{cache}{key_regexes} ||= [];
+
+    die "the regex list must be an array containing regexex/backend pairs" unless ref $regexes eq "ARRAY";
+
+    $ret;
+}
+
+sub get_cache_key_regexes {
+    my ( $c, %meta ) = @_;
+    @{ $c->config->{cache}{key_regexes} };
+}
+
+sub choose_cache_backend {
+    my ( $c, %meta ) = @_;
+
+    my @regexes = $c->get_cache_key_regexes( %meta );
+
+    while ( @regexes and my ( $re, $backend ) = splice( @regexes, 0, 2 ) ) {
+        return $backend if $meta{key} =~ $re;
+    }
+
+    $c->NEXT::choose_cache_backend( %meta );
+}
+
+__PACKAGE__;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Catalyst::Plugin::Cache::Choose::KeyRegex - Choose a cache backend based on key regexes.
+
+=head1 SYNOPSIS
+
+       use Catalyst::Plugin::Cache::Choose::KeyRegex;
+
+=head1 DESCRIPTION
+
+=cut
+
+
diff --git a/t/basic.t b/t/basic.t
new file mode 100644 (file)
index 0000000..89700b2
--- /dev/null
+++ b/t/basic.t
@@ -0,0 +1,83 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More 'no_plan';
+use Test::Exception;
+
+use ok "Catalyst::Plugin::Cache";
+
+{
+    package MockApp;
+    use base qw/Catalyst::Plugin::Cache/;
+
+    package MemoryCache;
+    use Storable qw/freeze thaw/;
+    
+    sub new { bless {}, shift }
+    sub get { ${thaw($_[0]{$_[1]}) || return} };
+    sub set { $_[0]{$_[1]} = freeze(\$_[2]) };
+    sub delete { delete $_[0]{$_[1]} };
+}
+
+MockApp->setup;
+my $c = bless {}, "MockApp";
+
+can_ok( $c, "register_cache_backend" );
+can_ok( $c, "unregister_cache_backend" );
+
+MockApp->register_cache_backend( default => MemoryCache->new );
+MockApp->register_cache_backend( moose => MemoryCache->new );
+
+can_ok( $c, "cache" );
+
+ok( $c->cache, "->cache returns a value" );
+
+can_ok( $c->cache, "get" ); #, "rv from cache" );
+can_ok( $c->cache("default"), "get" ); #, "default backend" );
+can_ok( $c->cache("moose"), "get" ); #, "moose backend" );
+
+ok( !$c->cache("lalalala"), "no lalala backend");
+
+MockApp->unregister_cache_backend( "moose" );
+
+ok( !$c->cache("moose"), "moose backend unregistered");
+
+
+dies_ok {
+    MockApp->register_cache_backend( ding => undef );
+} "can't register invalid backend";
+
+dies_ok {
+    MockApp->register_cache_backend( ding => bless {}, "SomeClass" );
+} "can't register invalid backend";
+
+
+
+can_ok( $c, "default_cache_backend" );
+is( $c->default_cache_backend, $c->cache, "cache with no args retrurns default" );
+
+can_ok( $c, "choose_cache_backend_wrapper" );
+can_ok( $c, "choose_cache_backend" );
+
+can_ok( $c, "cache_set" );
+can_ok( $c, "cache_get" );
+can_ok( $c, "cache_delete" );
+
+$c->cache_set( foo => "bar" );
+is( $c->cache_get("foo"), "bar", "set" );
+
+$c->cache_delete( "foo" );
+is( $c->cache_get("foo"), undef, "delete" );
+
+MockApp->register_cache_backend( elk => MemoryCache->new );
+
+is( $c->choose_cache_backend_wrapper( key => "foo" ), $c->default_cache_backend, "choose default" );
+is( $c->choose_cache_backend_wrapper( key => "foo", backend => "elk" ), $c->get_cache_backend("elk"), "override choice" );
+
+
+$c->cache_set( foo => "gorch", backend => "elk" );
+is( $c->cache_get("foo"), undef, "set to custom backend (get from non custom)" );
+is( $c->cache_get("foo", backend => "elk"), "gorch", "set to custom backend (get from custom)" );
+
diff --git a/t/key_regexes.t b/t/key_regexes.t
new file mode 100644 (file)
index 0000000..6cdaf26
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More 'no_plan';
+
+use ok "Catalyst::Plugin::Cache";
+use ok "Catalyst::Plugin::Cache::Choose::KeyRegexes";
+
+{
+    package MockApp;
+    use base qw/Catalyst::Plugin::Cache Catalyst::Plugin::Cache::Choose::KeyRegexes/;
+
+    our %config = (
+        cache => {
+            key_regexes => [
+                qr/^foo/ => "foo_store",
+                qr/^bar/ => "bar_store",
+            ],
+        },
+    );
+    sub config { \%config }
+
+    package MemoryCache;
+    use Storable qw/freeze thaw/;
+    
+    sub new { bless {}, shift }
+    sub get { ${thaw($_[0]{$_[1]}) || return} };
+    sub set { $_[0]{$_[1]} = freeze(\$_[2]) };
+    sub delete { delete $_[0]{$_[1]} };
+}
+
+
+MockApp->setup;
+my $c = bless {}, "MockApp";
+
+MockApp->register_cache_backend( default => MemoryCache->new );
+MockApp->register_cache_backend( foo_store => MemoryCache->new );
+MockApp->register_cache_backend( bar_store => MemoryCache->new );
+
+is( $c->choose_cache_backend_wrapper( key => "baz" ), $c->cache, "chose default" );
+is( $c->choose_cache_backend_wrapper( key => "foo" ), $c->cache("foo_store"), "chose foo" );
+is( $c->choose_cache_backend_wrapper( key => "bar" ), $c->cache("bar_store"), "chose bar" );
+
+$c->cache_set( foo_laa => "laa" );
+$c->cache_set( bar_laa => "laa" );
+$c->cache_set( baz_laa => "laa" );
+
+is( $c->cache->get("baz_laa"), "laa", "non match stored in default" );
+is( $c->cache->get("foo_laa"), undef, "no foo key" );
+is( $c->cache->get("bar_laa"), undef, "no bar key" );
+
+
+is( $c->cache("foo_store")->get("baz_laa"), undef, "no non match in foo store" );
+is( $c->cache("foo_store")->get("foo_laa"), "laa", "has foo key" );
+is( $c->cache("foo_store")->get("bar_laa"), undef, "no bar key" );
+
+
+is( $c->cache("bar_store")->get("baz_laa"), undef, "no non match in bar store" );
+is( $c->cache("bar_store")->get("foo_laa"), undef, "no foo key" );
+is( $c->cache("bar_store")->get("bar_laa"), "laa", "has bar key" );
+
+