Template::Provider::DBIC - Load templates using DBIx::Class


Template-Provider-DBIC documentation Contained in the Template-Provider-DBIC distribution.

Index


Code Index:

NAME

Top

Template::Provider::DBIC - Load templates using DBIx::Class

SYNOPSIS

Top

    use My::DBIC::Schema;
    use Template;
    use Template::Provider::DBIC;

    my $schema = My::DBIC::Schema->connect(
        $dsn, $user, $password, \%options
    );
    my $resultset = $schema->resultset('Template');

If all of your templates are stored in a single table the most convenient method is to pass the provider a DBIx::Class::ResultSet.

    my $template = Template->new({
        LOAD_TEMPLATES => [
            Template::Provider::DBIC->new({
                RESULTSET => $resultset,
                # Other template options like COMPILE_EXT...
            }),
        ],
    });

    # Process the template 'my_template' from resultset 'Template'.
    $template->process('my_template');
    # Process the template 'other_template' from resultset 'Template'.
    $template->process('other_template');

Alternatively, where your templates are stored in several tables you can pass a DBIx::Class::Schema and specify the result set and template name in the form ResultSet/template_name.

    my $template2 = Template->new({
        LOAD_TEMPLATES => [
            Template::Provider::DBIC->new({
                SCHEMA => $schema,
                # Other template options...
            }),
        ],
    });

    # Process the template 'my_template' from resultset 'Template'.
    $template->process('Template/my_template');
    # Process the template 'my_template' from resultset 'Other'.
    $template->process('Other/my_template');

In cases where both are supplied, the more specific RESULTSET will take precedence.

DESCRIPTION

Top

Template::Provider::DBIC allows a Template object to fetch its data using DBIx::Class instead of, or in addition to, the default filesystem-based Template::Provider.

SCHEMA

This provider requires a schema containing at least the following:

A column containing the template name. When $template->provider($name) is called the provider will search this column for the corresponding $name. For this reason the column must be a unique key, else an exception will be raised.

A column containing the actual template content itself. This is what will be compiled and returned when the template is processed.

A column containing the time the template was last modified. This must return - or be inflated to - a date string recognisable by Date::Parse.

OPTIONS

In addition to supplying a RESULTSET or SCHEMA and the standard Template::Provider options, you may set the following preferences:

COLUMN_NAME

The table column that contains the template name. This will default to 'name'.

COLUMN_CONTENT

The table column that contains the template data itself. This will default to 'content'.

COLUMN_MODIFIED

The table column that contains the date that the template was last modified. This will default to 'modified'.

METHODS

Top

->fetch( $name )

This method is called automatically during Template's ->process() and returns a compiled template for the given $name, using the cache where possible.

USE WITH OTHER PROVIDERS

Top

By default Template::Provider::DBIC will raise an exception when it cannot find the named template. When TOLERANT is set to true it will defer processing to the next provider specified in LOAD_TEMPLATES where available. For example:

    my $template = Template->new({
        LOAD_TEMPLATES => [
            Template::Provider::DBIC->new({
                RESULTSET => $resultset,
                TOLERANT  => 1,
            }),
            Template::Provider->new({
                INCLUDE_PATH => $path_to_templates,
            }),
        ],
    });




CACHING

Top

When caching is enabled, by setting COMPILE_DIR and/or COMPILE_EXT, Template::Provider::DBIC will create a directory consisting of the database DSN and table name. This should prevent conflicts with other databases and providers.

SEE ALSO

Top

Template, Template::Provider, DBIx::Class::Schema

DIAGNOSTICS

Top

In addition to errors raised by Template::Provider and DBIx::Class, Template::Provider::DBIC may generate the following error messages:

A valid DBIx::Class::Schema or ::ResultSet is required

One of the SCHEMA or RESULTSET configuration options must be provided.

%s not valid: must be of the form $table/$template

When using Template::Provider::DBIC with a DBIx::Class::Schema object, the template name passed to ->process() must start with the name of the result set to search in.

'%s' is not a valid result set for the given schema

Couldn't find the result set %s in the given DBIx::Class::Schema object.

Could not retrieve '%s' from the result set '%s'

Unless TOLERANT is set to true failure to find a template with the given name will raise an exception.

DEPENDENCIES

Top

Carp

Date::Parse

File::Path

File::Spec

Template::Provider

Additionally, use of this module requires an object of the class DBIx::Class::Schema or DBIx::Class::ResultSet.

BUGS

Top

Please report any bugs or feature requests to bug-template-provider-dbic at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Template-Provider-DBIC.

SUPPORT

Top

You can find documentation for this module with the perldoc command.

    perldoc Template::Provider::DBIC

You may also look for information at:

* Template::Provider::DBIC

http://perlprogrammer.co.uk/modules/Template::Provider::DBIC/

* AnnoCPAN: Annotated CPAN documentation

http://annocpan.org/dist/Template-Provider-DBIC/

* RT: CPAN's request tracker

http://rt.cpan.org/NoAuth/Bugs.html?Dist=Template-Provider-DBIC

* Search CPAN

http://search.cpan.org/dist/Template-Provider-DBIC/

AUTHOR

Top

Dave Cardwell <dcardwell@cpan.org>

COPYRIGHT AND LICENSE

Top


Template-Provider-DBIC documentation Contained in the Template-Provider-DBIC distribution.
package Template::Provider::DBIC;

use strict;
use warnings;

use base qw/ Template::Provider /;

use Carp qw( croak );
use Date::Parse ();

our $VERSION = '0.02';


sub _init {
    my ( $self, $options ) = @_;
    
    # Provide defaults as necessary.
    $self->{ COLUMN_NAME }     = $options->{ COLUMN_NAME }     || 'name';
    $self->{ COLUMN_MODIFIED } = $options->{ COLUMN_MODIFIED } || 'modified';
    $self->{ COLUMN_CONTENT }  = $options->{ COLUMN_CONTENT }  || 'content';
    
    # Ensure that a RESULTSET or SCHEMA has been specified. In the case of
    # both RESULTSET takes precedence.
    my $storage;
    if ( defined $options->{ RESULTSET } ) {
        $self->{ RESULTSET } = $options->{ RESULTSET };
        $storage = $self->{ RESULTSET }->result_source->schema->storage;
    }
    elsif ( defined $options->{ SCHEMA } ) {
        $self->{ SCHEMA } = $options->{ SCHEMA };
        $storage = $self->{ SCHEMA }->storage;
    }
    else { # neither specified
        return $self->error(
            'A valid DBIx::Class::Schema or ::ResultSet is required'
        );
    }
    
    # The connection DSN will be used when caching templates.
    $self->{ DSN } = $storage->connect_info->[0];
    
    # Use Template::Provider's ->_init() to create the COMPILE_DIR...
    $self->SUPER::_init($options);
    
    # ...and add a directory for templates cached by this provider.
    if ( $self->{ COMPILE_DIR } ) {
        # Adapted from Template::Provider 2.91
        require File::Spec;
        require File::Path;
        
        my $wdir = $self->{ DSN };
        $wdir =~ s/://g if $^O eq 'MSWin32';
        $wdir =~ /(.*)/; # untaint
        $wdir =  File::Spec->catfile( $self->{ COMPILE_DIR }, $1 );
        File::Path::mkpath($wdir) unless -d $wdir;
    }
    
    return $self;
}


sub fetch {
    my ( $self, $name ) = @_;
    
    # We're not interested in GLOBs or file handles.
    if ( ref $name ) {
        return ( undef, Template::Constants::STATUS_DECLINED );
    }
    
    
    # Determine the name of the table we're dealing with.
    my $table;
    if ( $self->{ RESULTSET } ) {
        # We can extract the table name from a DBIx::Class::ResultSet.
        $table = $self->{ RESULTSET }->result_source->name;
    }
    else {
        # For DBIx::Class::Schema, however, we have to extract the table name
        # from the given template name if it is of the form
        # "$table/$template".
        if ( $name =~ m#^([^/]+)/(.+)$# ) {
            ( $table, $name ) = ( $1, $2 );
        }
        else {
            # In tolerant mode decline to handle the template, otherwise raise
            # an error.
            return $self->{ TOLERANT }
                ? ( undef, Template::Constants::STATUS_DECLINED )
                : ( "$name not valid: must be of the form "
                                     . '"$table/$template"',
                    Template::Constants::STATUS_ERROR )
            ;
        }
        
        # Make sure this is a valid resultset.
        eval { $self->{ SCHEMA }->resultset($table); };
        if ( $@ ) {
            return $self->{ TOLERANT }
                ? ( undef, Template::Constants::STATUS_DECLINED )
                : ( "'$table' is not a valid result set for the given schema",
                    Template::Constants::STATUS_ERROR )
            ;
        }
    }
    
    
    # Determine the path this template would be cached to.
    my $compiled_filename = $self->_compiled_filename(
        $self->{ DSN } . "/$table/$name"
    );
    
    my ( $data, $error, $slot );
    
    # Is caching enabled?
    my $size    = $self->{ SIZE };
    my $caching = !defined $size || $size;
    
    
    # If caching is enabled and an entry already exists, refresh its cache
    # slot and extract the data...
    if ( $caching && ($slot = $self->{ LOOKUP }->{ "$table/$name" }) ) {
        ( $data, $error ) = $self->_refresh($slot);
        $data = $slot->[ Template::Provider::DATA ] unless $error;
    }
    # ...otherwise if this template has already been compiled and cached (but
    # not by this object) try to load it from the disk, providing it hasn't
    # been modified...
    elsif ( $compiled_filename && -f $compiled_filename
         && !$self->_modified( "$table/$name", (stat(_))[9] ) ) {
        $data  = $self->_load_compiled($compiled_filename);
        $error = $self->error() unless $data;
        
        # Save the new data where caching is enabled.
        $self->store( "$table/$name", $data ) if $caching && !$error;
    }
    # ...else there is nothing already cached for this template so load it
    # from the database.
    else {
        ( $data, $error ) = $self->_load("$table/$name");
        if ( !$error ) {
            ( $data, $error ) = $self->_compile( $data, $compiled_filename );
        }
        
        # Save the new data where caching is enabled.
        if ( !$error ) {
            $data = $caching ? $self->_store( "$table/$name", $data )
                             : $data->{ data }
            ;
        }
    }
    
    return ( $data, $error );
}


sub _load {
    my ( $self, $name ) = @_;
    my ( $data, $error );
    
    my $table;
    if ( $name =~ m#^([^/]+)/(.+)$# ) {
        ( $table, $name ) = ( $1, $2 );
    }
    
    my $resultset = $self->{ RESULTSET }
                 || $self->{ SCHEMA }->resultset($table);
    
    # Try to retrieve the template from the database.
    my $template = $resultset->find(
        $name, { key => $self->{ COLUMN_NAME } }
    );
    if ( $template ) {
        $data = {
            name => "$table/$name",
            text => $template->get_column( $self->{ COLUMN_CONTENT } ),
            time => Date::Parse::str2time(
                        $template->get_column( $self->{ COLUMN_MODIFIED } )
                    ),
            load => time,
        };
    }
    elsif ( $self->{ TOLERANT } ) {
        ( $data, $error ) = ( undef, Template::Constants::STATUS_DECLINED );
    } else {
        ( $data, $error ) = (
            "Could not retrieve '$name' from the result set '$table'",
            Template::Constants::STATUS_ERROR
        );
    }
    
    return ( $data, $error );
}


sub _modified {
    my ( $self, $name, $time ) = @_;
    
    my $table;
    if ( $name =~ m#^([^/]+)/(.+)$# ) {
        ( $table, $name ) = ( $1, $2 );
    }
    
    my $resultset = $self->{ RESULTSET }
                 || $self->{ SCHEMA }->resultset($table);
                 
    # Try to retrieve the template from the database...
    my $template = $resultset->find(
        $name, { key => $self->{ COLUMN_NAME } }
    );
    
    require Date::Parse;
    my $modified = $template && Date::Parse::str2time(
                                    $template->{ COLUMN_MODIFIED }
                                )
                || return $time ? 1 : 0;
    
    return $time ? $modified > $time : $modified;
}


1; # End of the module code; everything from here is documentation...
__END__