=pod

=encoding utf8

=head1 NAME

Kelp::Manual::Cookbook - Recipes for Kelp dishes

=head1 DESCRIPTION

This document lists solutions to common problems you may encounter while
developing your own Kelp web application. Since Kelp leaves a lot for you to
figure out yourself (also known as I<not getting in your way>) many of these
will be just a proposed solutions, not an official way of solving a problem.

=head1 RECIPES

=head2 Setting up a common layout for all templates

Kelp does not implement template layouts by itself, so it's up to templating
engine or contributed module to deliver that behavior. For example,
L<Template::Toolkit> allows for C<WRAPPER> directive, which can be used like
this (with L<Kelp::Module::Template::Toolkit>):

    # in config
    modules => [qw(Template::Toolkit)],
    modules_init => {
        'Template::Toolkit' => {
            WRAPPER => 'layouts/main.tt',
        },
    },

=head2 Connecting to DBI

There are multiple ways to do it, like the one below:

    # Private attribute holding DBI handle
    # anonymous sub is a default value builder
    attr _dbh => sub {
        shift->_dbi_connect;
    };

    # Private sub to connect to DBI
    sub _dbi_connect {
        my $self = shift;

        my @config = @{ $self->config('dbi') };
        return DBI->connect(@config);
    }

    # Public method to use when you need dbh
    sub dbh {
        my $self = shift;

        # ping is likely not required, but just in case...
        if (!$self->_dbh->ping) {
            # reload the dbh, since ping failed
            $self->_dbh($self->_dbi_connect);
        }

        $self->_dbh;
    }

    # Use $self->dbh from here on ...

    sub some_route {
        my $self = shift;

        $self->dbh->selectrow_array(q[
            SELECT * FROM users
            WHERE clue > 0
        ]);
    }

A slightly shorter version with state variables and no ping:

    # Public method to use when you need dbh
    sub dbh {
        my ($self, $reconnect) = @_;

        state $handle;
        if (!defined $handle || $reconnect) {
            my @config = @{ $self->config('dbi') };
            $handle = DBI->connect(@config);
        }

        return $handle;
    }

    # Use $self->dbh from here on ...

    sub some_route {
        my $self = shift;

        $self->dbh->selectrow_array(q[
            SELECT * FROM users
            WHERE clue > 0
        ]);
    }

Same methods can be used for accessing the schema of L<DBIx::Class>.

=head2 Custom 404 and 500 error pages

=head3 Error templates

The easiest way to set up custom error pages is to create templates in
I<views/error/> with the code of the error. For example: I<views/error/404.tt> and
I<views/error/500.tt>. You can render those manually using C<< $self->res->render_404 >>
and C<< $self->res->render_500 >>. To render another error code, you can use
C<< $self->res->render_error >>.

=head3 Within the route

You can set the response headers and content within the route:

    sub some_route {
        my $self = shift;
        $self->res->set_code(404)->template('my_404_template');
    }

=head3 By overriding the Kelp::Response class

To make custom 500, 404 and other error pages, you will have to subclass the
L<Kelp::Response> module and override the I<render_404> and I<render_500> subroutines.
Let's say your app's name is Foo and its class is in I<lib/Foo.pm>. Now create a
file I<lib/Foo/Response.pm>:

    package Foo::Response;
    use Kelp::Base 'Kelp::Response';

    sub render_404 {
        my $self = shift;
        $self->template('my_custom_404');
    }

    sub render_500 {
        my $self = shift;
        $self->template('my_custom_500');
    }

Then, in I<lib/Foo.pm>, you have to tell Kelp to use your custom response class
like this:

    sub response {
        my $self = shift;
        return Foo::Response->new( app => $self );
    }

Don't forget you need to create I<views/my_custom_404.tt> and
I<views/my_custom_500.tt>. You can add other error rendering subroutines too, for
example:

    sub render_401 {
        # Render your custom 401 error here
    }

=head2 Altering the behavior of a Kelp class method

The easiest solution would be to use L<KelpX::Hooks> module available on CPAN:

    use KelpX::Hooks;
    use parent "Kelp";

    # Change how template rendering function is called
    hook "template" => sub {
        my ($orig, $self, @args) = @_;

        # $args[0] is template name
        # $args[1] is a list of template variables
        $args[1] = {
            (defined $args[1] ? %{$args[1]} : ()),
            "my_var" => $self->do_something,
        };

        # call the original $self->template again
        # with modified arguments
        return $self->$orig(@args);
    };

=head2 Handling websocket connections

Since Kelp is a Plack-based project, its support for websockets is very
limited. First of all, you would need a Plack server with support for the psgi
I<streaming>, I<io> and I<nonblocking>, like L<Twiggy>. Then, you could
integrate Kelp application with a websocket application via
L<Kelp::Module::Websocket::AnyEvent> CPAN module (if the server implementation
is compatible with L<AnyEvent>):

    sub build {
        my ($self) = @_;

        my $ws = $self->websocket;
        $ws->add(message => sub {
            my ($conn, $msg) = @_;

            $conn->send({echo => $msg});
        });

        $self->symbiosis->mount("/ws" => $ws);
    }

Keep in mind that Plack websockets are a burden because of lack of preforking
server implementations capable of running them. If you want to use them heavily
you're better off using L<Mojolicious> instead or integrating a
L<Mojo::Server::Hypnotoad> with a small Mojo application alongside Kelp as a
websocket handler.

=head2 Deploying

Deploying a Kelp application is done the same way any other Plack application is
deployed:

    > plackup -E deployment -s Gazelle app.psgi

In production environments, it is usually a good idea to set up a proxy between
the PSGI server and the World Wide Web. Popular choices are I<apache2> and
I<nginx>. To get full information about incoming requests, you'll also need to
use L<Plack::Middleware::ReverseProxy>.

    # app.psgi

    builder {
        enable_if { ! $_[0]->{REMOTE_ADDR} || $_[0]->{REMOTE_ADDR} =~ /127\.0\.0\.1/ }
        "Plack::Middleware::ReverseProxy";
        $app->run;
    };

(REMOTE_ADDR is not set at all when using the proxy via filesocket).

=head2 Changing the default access logging

Access logs reported by Kelp through C<logger> can be modified or disabled by
writing your own customized L<Kelp/before_dispatch> method (not calling the
parent version).

    sub before_dispatch {} # enough to disable the access logs

=head2 Using sessions

In order to have access to L<Kelp::Request/session> a
L<Plack::Middleware::Session> middleware must be initialized. In your config
file:

    middleware => ['Session'],
    middleware_init => {
        Session => {
            store => 'File'
        }
    }

Note that you pretty much need to choose a C<store> right away, as otherwise it
will store data in memory, which is both volatile and does not work with
multi-process servers.

=head2 Responding in the same charset as request

Kelp usually uses its own L<Kelp/charset> as response encoding, but makes it
easy to use the same charset in response as the one you got in request:

    use utf8;

    $self->add_route('/copy_charset' => sub {
        my $self = shift;

        $self->res->charset($self->req->charset);
        return 'et voilà!';
    });

Note that request charset is only actually used if the C<Content-Type> of the
request is either C<text/*> or C<application/*>.

=head2 Custom encodings in requests and responses

It is trivial to extend L<Kelp::Request> and L<Kelp::Response> to make it
seamlessly handle other serialization schemes, for example YAML (through
L<Kelp::Module::YAML>):

(Note that there is C<kelp_extensions> flag in
L<Kelp::Module::YAML/CONFIGURATION>, which will install this logic
automatically right into base Kelp packages, so this is just an example for
custom encodings)

=head3 Extending Request

    package YAML::Request;
    use Kelp::Base 'Kelp::Request';
    use Try::Tiny;

    sub is_yaml {
        my $self = shift;
        return 0 unless $self->content_type;
        return $self->content_type =~ m{^text/yaml}i;
    }

    sub yaml_content {
        my $self = shift;
        return undef unless $self->is_yaml;

        return try {
            $self->app->get_encoder(yaml => 'internal')->decode($self->content);
        }
        catch {
            undef;
        };
    }

=head3 Extending Response

    package YAML::Response;
    use Kelp::Base 'Kelp::Response';

    sub yaml {
        my $self = shift;
        $self->set_content_type('text/yaml', $self->charset || $self->app->charset);
        return $self;
    }

    sub _render_ref {
        my ($self, $body) = @_;

        if ($self->content_type =~ m{^text/yaml}i) {
            return $self->app->get_encoder(yaml => 'internal')->encode($body);
        }
        else {
            return $self->SUPER::_render_ref($body);
        }
    }

=head3 Using it in an app

    use Kelp::Exception;

    sub build {
        my $self = shift;

        $self->load_module('YAML');
        $self->request_obj('YAML::Request');
        $self->response_obj('YAML::Response');

        $self->add_route('/yaml' => 'handler');
    }

    sub handler {
        my $self = shift;
        my $yaml_document = $self->req->yaml_content;

        Kelp::Exception->throw(400)
            unless defined $yaml_document;

        # ... do something with $yaml_document

        $self->res->yaml;
        return $yaml_document;
    }

=head2 Controller fields disappearing after each request

Kelp uses reblessing of the main app to implement controllers. The reblessing
usually happens once per request, per controller. This way changes in the main
app state are always propagating down to controllers.

If your main app has no changing state and you'd like to just keep the
controller's state between requests, you can set the configuration value
C<persistent_controllers> to a true value. This way your controllers will be
reblessed just once and then reused indefinetly and no changes in app's state will
propagade to controllers.

Note that by default, Kelp main class has no top-level state which may change
between requests so it should be pretty safe to enable this configuration as
long as you don't instantiate your controllers before app building is over.

=head1 SEE ALSO

L<Kelp::Manual>

L<Kelp>

L<Plack>

=head1 SUPPORT

=over

=item * GitHub: L<https://github.com/Kelp-framework/Kelp>

=item * Mailing list: L<https://groups.google.com/g/perl-kelp>

=back

