NAME

Template::Plugin::Heritable - OO dispatching and inheritance for templates

SYNOPSIS

[% USE Heritable %]

[%# searches providers for a "view" template method on
    class (which should be a metamodel object, eg
    someobj.meta in Perl 6) %]
[% Heritable.include(class, "view", { self = object }) %]

[%# return list of paths it would look %]
[% paths = Heritable.dispatch_paths(class, "view") %]

[%# if you don't have the class of the object handy, then
    use 'invoke' instead %]
[% Heritable.invoke(object, "method", { self = object } %]

[%# call the next method in the inheritance tree from
    inside a template method %]
[% next_template() %]

DESCRIPTION

Template::Plugin::Heritable provides support for selecting an appropriate template based on the class of an object. It is also possible to call the next template in the inheritance heirarchy/chain.

This provides a form of inheritance for template display.

The core of this is the template dispatch mechanism, which deals in terms of a suitable metamodel class. The module currently deals in the following metamodels; but no doubt you could fool it with modules which encapsulate other metamodels (such as Perl 5, NEXT, Class::C3, DBIx::Class::Schema, etc) with minimal effort by conforming to one of their APIs.

Eventually, no doubt these will be plugins.

T2::Class

T2 is the Tangram MetaModel that also drives Class::Tangram

Class::MOP

Initial support for Class::MOP classes. Note that this is currently only tested with Moose; in particular it assumes Moose-like type constraints. If you want support for plain Class::MOP, please send a test case.

CONSTRUCTION

Basic use:

[% USE Heritable %]

Specifying all options:

[% USE Heritable({ prefix = "mypath",
                   suffix = ".tt",
                   class2path = somefunc,
                   class_attr2path = somefunc,
                   schema = myschema
                 }) %]

Here all dispatch paths returned by Heritable will be prepended with mypath/. Also, a custom method is specified to convert from "Foo::Bar"-style class names to a Template::Provider path.

There is also a schema object; this object is responsible for converting objects to classes. If you are using Class::MOP, you don't need to supply this; the metaclass is found via $object->meta.

Normally, you wouldn't specify most of this this - and indeed there is the issue there that this configuration information perhaps doesn't belong every place you make a Heritable object dispatch.

For this reason, it is recommended that you have a single template for object dispatching, and to pass through self appropriately.

[% PROCESS invoke
     object = SomeObject
     method = "foo"
%]

the invoke template might look like:

[% USE Heritable({ suffix = ".tt"
                 });
   Heritable.include(object.meta, method, { self = object }) -%]

METHODS

.dispatch_paths

.include

.invoke

[% paths = Heritable.dispatch_paths( what, "name" ) %]

[% Heritable.include( what, "name", { ... } ) %]
[% Heritable.invoke( object, "name", { ... } ) %]

.dispatch_paths returns a list of dispatch paths for what. what is a metamodel object (see DESCRIPTION).

.include calls the first one that actually exists in the available template providers. It throws a (trappable) not found error if it was not found.

.invoke assumes that the metamodel object is either available as object.meta or via $schema->class(ref $object). Convenient modules to make this Just Work™ with standard Perl 6 objects/classes are yet to be written, but for T2 and Class::MOP this should work fine.

new in 0.03: now supports 5.9.5+ 'mro' - if the symbol &mro::get_linear_isa is defined at runtime (for instance, you used the mro pragma or MRO::Compat for earlier Perl versions), then this will work. However, mro does not cover types of attributes, so only invoke with a method name (no attribute name) is currently supported.

new in 0.03: also supports DBIx::Class - pass DBIx::Class::ResultSource objects to .include, and DBIx::Class::Row objects to .invoke.

DISPATCH ALGORITHM

To figure out which template should be called to perform a function, the class names are turned into Template::Provider paths, with the template to call ("view" in the example in the synopsis) appended to them.

For example, if the "class" object in the synopsis represents the "Foo::Bar" class, which has superclass "Foo", the following locations would be searched for a template (assuming you specified TEMPLATE_EXTENSION = ".tt" during your Template object construction):

foo/bar/view.tt
foo/view.tt
object/view.tt

It is also possible to dispatch based on attribute or association types, by calling "attribute methods". In this case, the dispatch order also includes templates for the types of the attribute or association.

So, if you were using T2 classes and wrote:

[% Heritable.include(class.attribute("baz"), "show") %]

Then the first of these templates found would be called (assuming baz is a property of the Foo class, of type set):

foo/baz/show.tt
object/baz/show.tt
foo/types/set/show.tt
object/types/set/show.tt

Note that foo/bar/baz/show.tt was not searched for, even though class is actually Foo::Bar. If you wanted to do that, you should use a 'multiple invocant' include:

[% Heritable.include([class, class.attribute("baz")],
                     "show", { ... }) %]

or simply

[% Heritable.include([class, "baz"], "show", { ... }) %]

Either of these would then search for:

foo/bar/baz/show.tt
foo/baz/show.tt
object/baz/show.tt
foo/bar/types/set/show.tt
foo/types/set/show.tt
object/types/set/show.tt

Using Class::MOP, if an attribute's type is itself a type with an inheritance chain, that those extra templates will also be added to the list of checked template locations.

For instance, if you have two classes A and B, A having an attribute "att" of type "Str", and you write:

[% Heritable.invoke([ my_b, "att"], "show") %]

Then you get this dispatch path:

b/att/show.tt
a/att/show.tt
moose/object/att/show.tt
object/att/show.tt

b/types/str/show.tt
a/types/str/show.tt
moose/object/types/str/show.tt
object/types/str/show.tt

b/types/value/show.tt
a/types/value/show.tt
moose/object/types/value/show.tt
object/types/value/show.tt

b/types/defined/show.tt
a/types/defined/show.tt
moose/object/types/defined/show.tt
object/types/defined/show.tt

b/types/item/show.tt
a/types/item/show.tt
moose/object/types/item/show.tt
object/types/item/show.tt

New in Template::Heritable 0.03: for convenience, the standard method for converting classes to "paths" can be customised, for instance if deep directory structures is inconvenient, and you like to be able to combine blocks into a single file, you can use:

[% USE Heritable({ path_delim = "_",
                   use_blocks = 1    }) %]

This would make the first part of the above dispatch list look like this:

b_att_show.tt
a_att_show.tt
moose_object_att_show.tt
object_att_show.tt
...

Not only that, but as use_blocks is set, if by some strange co-incidence the module can find similarly named blocks, it will just call those instead;

[% b_att_show = BLOCK %]
  Here we show some B attributes.  But we don't want to
  miss out on showing the [% next_template() %]
[% END %]
[% a_att_show = BLOCK -%]
  A attributes.
[% END %]

With the above block definitions in scope, calling

[% Heritable.invoke([ my_b, "att"], "show") %]

Would print:

Here we show some B attributes.  But we don't want to
miss out on showing the A attributes.

And calling:

[% Heritable.invoke([ my_a, "att"], "show") %]

Would print:

A attributes

DEFINED VARIABLES

next_template

These methods let you find the next template to display in the inheritance chain.

The next template is [% next_template %]

[% next_template.include({ ... }) %]

Note that if there is no next template you will get a nasty error.

SEE ALSO

T2, Template.

AUTHOR

Sam Vilain, <samv@cpan.org>

LICENSE

Copyright (c) 2005, 2006, Catalyst IT (NZ) Ltd. This program is free software; you may use it and/or redistribute it under the same terms as Perl itself.

CHANGELOG

0.02, 25 May 2006

Add support for Class::MOP, though only Moose classes are currently tested; new test cases welcome.

2 POD Errors

The following errors were encountered while parsing the POD:

Around line 65:

You forgot a '=back' before '=head1'

Around line 142:

Non-ASCII character seen before =encoding in 'Work™'. Assuming UTF-8