Catalyst-ControllerRole-At
view release on metacpan or search on metacpan
lib/Catalyst/ControllerRole/At.pm view on Meta::CPAN
{
my $cnt = 0;
foreach my $name (@named_fields) {
if(defined($name)) {
$extra_proto{Field} = $extra_proto{Field} ?
"$extra_proto{Field},$name=>\$args[$cnt]" : "$name=>\$args[$cnt]"
}
$cnt++;
}
}
if(
my ($key, $value) = map { $_ =~ /^(.*?)(?:\(\s*['"]?(.+?)['"]?\s*\))?$/ } grep { $_ =~m/^Via\(.+\)$/ }
@{$self->meta->get_method($action_subname)->attributes||[]})
{
$chained = join '/', grep { defined $_ } map { $expansions{$_} ? $expansions{$_} : $_ } split('\/',$value);
$chained =~s[//][/]g;
}
$path_part = join('/', @path_parts);
$path_part =~s/^\///;
my %attributes = (
Chained => $chained,
PathPart => $path_part,
Does => [qw/NamedFields QueryParameter/],
$arg_type => (@arg_proto ? (join(',',@arg_proto)) : $args),
%extra_proto,
);
return %attributes;
}
1;
=head1 NAME
Catalyst::ControllerRole::At - A new approach to building Catalyst actions
=head1 SYNOPSIS
package MyApp::Controller::User;
use Moose;
use MooseX::MethodAttributes;
use Types::Standard qw/Int Str/;
extends 'Catalyst::Controller';
with 'Catalyst::ControllerRole::At';
# Define your actions, for example:
sub global :At(/global/{}/{}) { ... } # http://localhost/global/$arg/$arg
sub list :At($action?{q:Str}) { ... } # http://localhost/user/list?q=$string
sub find :At($controller/{id:Int}) { ... } # http://localhost/user/$integer
# Define an action with an HTTP Method match at the same time
sub update :Get($controller/{id:Int}) { ... } # GET http://localhost/user/$integer
__PACKAGE__->meta->make_immutable;
=head1 DESCRIPTION
The way L<Catalyst> uses method attributes to annote a subroutine with meta
information used to map that action to an incoming request has sometimes been difficult
for newcomers to the framework. Partly this is due to how the system evolved and was
augmented, with more care towards backwards compatibility (for example with L<Maypole>, its
architectural anscestor) than with designing a forward system that is easy to grasp.
Additionally aspects of the system such as chained dispatch are very useful in the
hands of an expert but the interface leaves a lot to be desired. For example it is
possible to craft actions that mix chaining syntax with 'classic' syntax in ways that
are confusing. And having more than one way to do the same thing without clear and
obvious benefits is confusing to newcomers.
Lastly, the core L<Catalyst::Controller> syntax has confusing defaults that are not readily guessed.
For example do you know the difference (if any) between Args and Args()? Or the difference
between Path, Path(''), and Path()? In many cases defaults are applied that were not
intended and things that you might think are the same turn out to have different effects. All
this conspires to worsen the learning curve.
This role defines an alternative syntax that we hope is easier to understand and for the most
part eliminates defaults and guessed intentions. It only defines two method attributes, "At()"
and "Via()", which have no defaults and one of which is always required. It also smooths
over differences between 'classic' route matching using :Local and :Path and the newer
syntax based on Chaining by providing a single approach that bridges between the two
styles. One can mix and match the two without being required to learn a new syntax or to
rearchitect the system.
The "At()" syntax more closely resembles the type of URL you are trying to match, which should
make code creation and maintainance easier by reducing the mental mismatch that happens with
the core syntax.
Ultimately this ControllerRole is an attempt to layer some sugar on top of the existing
interface with the hope to establishing a normalized, easy approach that doesn't have the
learning curve or confusion of the existing system.
I also recommend reading L<Catalyst::RouteMatching> for general notes and details on
how dispatching and matching works.
=head1 URL Templating
The following are examples and specification for how to map a URL to an action or to
a chain of actions in L<Catalyst>. All examples assume the application is running at
the root of your website domain (https://localhost/, not https://localhost/somepath)
=head2 Matching a Literal Path
The action 'global_path' will respond to 'https://localhost/foo/bar/baz'.
package MyApp::Controller::Example;
use Moose;
use MooseX::MethodAttributes;
extends 'Catalyst::Controller';
with 'Catalyst::ControllerRole::At';
sub global_path :At(/foo/bar/baz) { ... }
lib/Catalyst/ControllerRole/At.pm view on Meta::CPAN
B<NOTE> For the purposes of executing code, we treat 'At' and 'At()' as the same. However
We highly recommend At() as a best practice since it more clearly represents the idea
of 'no match template'.
=head2 Chaining Actions across Controllers
The method attributes 'Via()' contains a pointer to the action being continued. In
standard practice this is almost always the name of an action in the same controller
as the one declaring it. This could be said to be a 'relative' (as in relative to
the current controller) action. However you don't have to use a relative name. You
can use any action's absolute private name, as long as it is an action that declares itself
to be a link in a chain.
However in practice it is not alway a good idea to spread your chained acions across
across controllers in a manner that is not easy to follow. We recommend you try
to limit youself to chains that follow the controller hierarchy, which should be
easier for your code maintainers.
For this common, best practice case when you are continuing your chained actions across
controllers, following a controller hierarchy, we provide some template expansions you can
use in the 'Via' attribute. These are useful to enforce this best practice as well as
promote reusability by decoupling hard coded private action namespaces from your controller.
$up: The controller whose namespace contains the current controller
$name The name of the current actions subroutine
$parent: Expands to $up/$subname
For example:
package MyApp::Controller::ThingsTodo;
use Moose;
use MooseX::MethodAttributes;
extends 'Catalyst::Controller';
with 'Catalyst::ControllerRole::At';
sub init :At($controller/...) {
my ($self, $c) = @_;
}
sub list :Via(init) At($name) {
my ($self, $c) = @_;
}
__PACKAGE__->meta->make_immutable;
package MyApp::Controller::ThingsTodo::Item;
use Moose;
use MooseX::MethodAttributes;
extends 'Catalyst::Controller';
with 'Catalyst::ControllerRole::At';
sub init :Via($parent) At({id:Int}/...) {
my ($self, $c) = @_;
}
sub show :Via(init) At($name) { ... }
sub update :Via(init) At($name) { ... }
sub delete :Via(init) At($name) { ... }
__PACKAGE__->meta->make_immutable;
This creates four (4) URL templates:
https://localhost/thingstodo/list
https://localhost/thingstodo/:id/show
https://localhost/thingstodo/:id/update
https://localhost/thingstodo/:id/delete
With an action execution flow as follows:
https://localhost/thingstodo/list =>
/thingstodo/init
/thingstodo/list
https://localhost/thingstodo/:id/show
/thingstodo/init
/thingstodo/item/init
/thingstodo/item/show
https://localhost/thingstodo/:id/update
/thingstodo/init
/thingstodo/item/init
/thingstodo/item/update
https://localhost/thingstodo/:id/delete
/thingstodo/init
/thingstodo/item/init
/thingstodo/item/delete
=head2 Method Shortcuts
Its common today to want to be able to match a URL to a specific HTTP method. For example
you might want to match a GET request to one action and a POST request to another. L<Catalyst>
offers the C<Method> attribute as well as shortcuts: C<GET>, C<POST>, C<PUT>, C<DELETE>, C<HEAD>,
C<OPTIONS>. To tidy your method declarations you can use C<Get>, C<Post>, C<Put>, C<Delete>, C<Head>,
C<Options> in place of C<At>:
package MyApp::Controller::Example;
use Moose;
use MooseX::MethodAttributes;
extends 'Catalyst::Controller';
with 'Catalyst::ControllerRole::At';
sub get :Get($controller/...) { ... }
sub post :Post($controller/...) { ... }
sub put :Put($controller/...) { ... }
sub delete :Delete($controller/...) { ... }
sub head :Head($controller/...) { ... }
sub options :Options($controller/...) { ... }
__PACKAGE__->meta->make_immutable;
Basically:
sub get :Get($controller/...) { ... }
( run in 0.827 second using v1.01-cache-2.11-cpan-df04353d9ac )