Catalyst-Manual

 view release on metacpan or  search on metacpan

lib/Catalyst/Manual/Cookbook.pod  view on Meta::CPAN


A graphical flowchart of how the dispatcher works can be found on the wiki at
L<https://web.archive.org/web/20190919010727/http://dev.catalystframework.org/attachment/wiki/WikiStart/catalyst-flow.png>.

=head2 DRY Controllers with Chained actions

Imagine that you would like the following paths in your application:

=over

=item B<< /cd/<ID>/track/<ID> >>

Displays info on a particular track.

In the case of a multi-volume CD, this is the track sequence.

=item B<< /cd/<ID>/volume/<ID>/track/<ID> >>

Displays info on a track on a specific volume.

=back

Here is some example code, showing how to do this with chained controllers:

    package CD::Controller;
    use base qw/Catalyst::Controller/;

    sub root : Chained('/') PathPart('/cd') CaptureArgs(1) {
        my ($self, $c, $cd_id) = @_;
        $c->stash->{cd_id} = $cd_id;
        $c->stash->{cd} = $self->model('CD')->find_by_id($cd_id);
    }

    sub trackinfo : Chained('track') PathPart('') Args(0) RenderView {
        my ($self, $c) = @_;
    }

    package CD::Controller::ByTrackSeq;
    use base qw/CD::Controller/;

    sub track : Chained('root') PathPart('track') CaptureArgs(1) {
        my ($self, $c, $track_seq) = @_;
        $c->stash->{track} = $self->stash->{cd}->find_track_by_seq($track_seq);
    }

    package CD::Controller::ByTrackVolNo;
    use base qw/CD::Controller/;

    sub volume : Chained('root') PathPart('volume') CaptureArgs(1) {
        my ($self, $c, $volume) = @_;
        $c->stash->{volume} = $volume;
    }

    sub track : Chained('volume') PathPart('track') CaptureArgs(1) {
        my ($self, $c, $track_no) = @_;
        $c->stash->{track} = $self->stash->{cd}->find_track_by_vol_and_track_no(
            $c->stash->{volume}, $track_no
        );
    }

Note that adding other actions (i.e. chain endpoints) which operate on a track
is simply a matter of adding a new sub to CD::Controller - no code is duplicated,
even though there are two different methods of looking up a track.

This technique can be expanded as needed to fulfil your requirements - for example,
if you inherit the first action of a chain from a base class, then mixing in a
different base class can be used to duplicate an entire URL hierarchy at a different
point within your application.

=head2 Component-based Subrequests

See L<Catalyst::Plugin::SubRequest>.

=head2 File uploads

=head3 Single file upload with Catalyst

To implement uploads in Catalyst, you need to have a HTML form similar to
this:

    <form action="/upload" method="post" enctype="multipart/form-data">
        <input type="hidden" name="form_submit" value="yes">
        <input type="file" name="my_file">
        <input type="submit" value="Send">
    </form>

It's very important not to forget C<enctype="multipart/form-data"> in
the form.

Catalyst Controller module 'upload' action:

    sub upload : Global {
        my ($self, $c) = @_;

        if ( $c->request->parameters->{form_submit} eq 'yes' ) {

            if ( my $upload = $c->request->upload('my_file') ) {

                my $filename = $upload->filename;
                my $target   = "/tmp/upload/$filename";

                unless ( $upload->link_to($target) || $upload->copy_to($target) ) {
                    die( "Failed to copy '$filename' to '$target': $!" );
                }
            }
        }

        $c->stash->{template} = 'file_upload.html';
    }

=head3 Multiple file upload with Catalyst

Code for uploading multiple files from one form needs a few changes:

The form should have this basic structure:

    <form action="/upload" method="post" enctype="multipart/form-data">
        <input type="hidden" name="form_submit" value="yes">
        <input type="file" name="file1" size="50"><br>
        <input type="file" name="file2" size="50"><br>
        <input type="file" name="file3" size="50"><br>



( run in 0.615 second using v1.01-cache-2.11-cpan-524268b4103 )