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
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 onlyMoose
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