From b311bcbe3578926e66145816dd01b05f18f813db Mon Sep 17 00:00:00 2001 From: "Jason A. Crome" Date: Thu, 21 Nov 2024 22:41:08 -0500 Subject: [PATCH] WIP: Cookbook review --- lib/Dancer2/Manual/Cookbook.pod | 381 +++++++++++++++++--------------- 1 file changed, 197 insertions(+), 184 deletions(-) diff --git a/lib/Dancer2/Manual/Cookbook.pod b/lib/Dancer2/Manual/Cookbook.pod index 6152ea4a9..53946c025 100644 --- a/lib/Dancer2/Manual/Cookbook.pod +++ b/lib/Dancer2/Manual/Cookbook.pod @@ -1,56 +1,50 @@ -package Dancer2::Cookbook; +package Dancer2::Manual::Cookbook; # ABSTRACT: Example-driven quick-start to the Dancer2 web framework =pod =encoding utf8 -=head1 DESCRIPTION +=head1 Description A quick-start guide with examples to get you up and running with the Dancer2 web framework. This document will be twice as useful if you finish reading the manual (L) first, but that is not required... :-) -=head1 BEGINNER'S DANCE +=head1 Route Recipes -=head2 A simple Dancer2 web app +=head2 The simplest Dancer2 web app Dancer2 has been designed to be easy to work with - it's trivial to write a simple web app, but still has the power to work with larger projects. To start with, let's make an incredibly simple "Hello World" example: #!/usr/bin/env perl - + package MyApp; use Dancer2; - get '/hello/:name' => sub { - return "Why, hello there " . route_parameters->get('name'); + get '/' => sub { + return "Hello, world!"; }; - dance; - -Yes - the above is a fully-functioning web app; running that script will -launch a webserver listening on the default port (3000). Now you can make a -request: + MyApp->to_app(); - $ curl http://localhost:3000/hello/Bob - Why, hello there Bob +Yes - the above is a fully-functioning web app; running that script with +C will launch a webserver listening on the default +port (5000). Now you can make a request: -and it will say hello. The C<:name> part is a named parameter within the -route specification, whose value is made available through C. + $ curl http://localhost:5000/ + Hello, world! Note that you don't need to use the C and C pragmas; they are already loaded by Dancer2. - =head2 Default Route -In case you want to avoid a I<404 error>, or handle multiple routes in the -same way and you don't feel like configuring all of them, you can set up a -default route handler. - -The default route handler will handle any request that doesn't get served by -any other route. +You can set up a default route to help trap C<404> errors in a +consistent way, or to handle multiple routes in a consistent +manner. A default route handler will handle any request that doesn't get +served by any other route. All you need to do is set up the following route as the B route: @@ -59,7 +53,7 @@ All you need to do is set up the following route as the B route: template 'special_404', { path => request->path }; }; -Then you can set up the template like so: +Then you can set up the F template like so: You tried to reach [% path %], but it is unavailable at the moment. @@ -116,109 +110,9 @@ In your main route controller: require_module( $module_base . $_ ) for @required_modules; -Now your app will expose C but requests to C will get a +Now your app will expose C but requests to C will return a 404 response. - -=head2 Simplifying AJAX queries with the Ajax plugin - -As an AJAX query is just an HTTP query, it's similar to a GET or POST route. -You may ask yourself why you may want to use the C keyword (from the -L plugin) instead of a simple C. - -Let's say you have a path like C in your application. You may -want to be able to serve this page with a layout and HTML content. But you -may also want to be able to call this same url from a javascript query using -AJAX. - -So, instead of having the following code: - - get '/user/:user' => sub { - if ( request->is_ajax ) { - # create xml, set headers to text/xml, blablabla - header( 'Content-Type' => 'text/xml' ); - header( 'Cache-Control' => 'no-store, no-cache, must-revalidate' ); - to_xml({...}) - } else { - template users => {...} - } - }; - -you can have - - ajax '/user/:user' => sub { - to_xml( {...}, RootName => undef ); - } - -and - - get '/user/:user' => sub { - template users => {...} - } - -Because it's an AJAX query, you know you need to return XML content, so -the content type of the response is set for you. - -=head3 Example: Feeding graph data through AJAX - -Let us assume we are building an application that uses a plotting library -to generate a graph and expects to get its data, which is in the form -of word count from an AJAX call. - -For the graph, we need the url I to return a JSON representation -of the word count data. Dancer in fact has a C function that takes -care of the JSON encapsulation. - - get '/data' => sub { - open my $fh, '<', $count_file; - - my %contestant; - while (<$fh>) { - chomp; - my ( $date, $who, $count ) = split '\s*,\s*'; - - my $epoch = DateTime::Format::Flexible->parse_datetime($date)->epoch; - my $time = 1000 * $epoch; - $contestant{$who}{$time} = $count; - } - - my @json; # data structure that is going to be JSONified - - while ( my ( $peep, $data ) = each %contestant ) { - push @json, { - label => $peep, - hoverable => \1, # so that it becomes JavaScript's 'true' - data => [ map { [ $_, $data->{$_} ] } - sort { $a <=> $b } - keys %$data ], - }; - } - - my $beginning = DateTime::Format::Flexible->parse_datetime( "2010-11-01")->epoch; - my $end = DateTime::Format::Flexible->parse_datetime( "2010-12-01")->epoch; - - push @json, { - label => 'de par', - data => [ - [$beginning * 1000, 0], - [ DateTime->now->epoch * 1_000, - 50_000 - * (DateTime->now->epoch - $beginning) - / ($end - $beginning) - ] - ], - - }; - - encode_json( \@json ); - }; - -For more serious AJAX interaction, there's also L -that adds an I route handler to the mix. - -Because it's an AJAX query, you know you need to return XML content, so -the content type of the response is set for you. - =head2 Using the prefix feature to split your application For better maintainability, you may want to separate some of your application @@ -259,7 +153,59 @@ adds the routes from C to the routes of the app C. When using multiple applications please ensure that your path definitions do not overlap. For example, if using a default route as described above, once a request is matched to the default route then no further routes (or -applications) would be reached. +applications) will be reached. + +=head1 Template Recipes + +=head2 UsingTemplate Toolkit's WRAPPER directive in Dancer2 + +Dancer2 already provides a WRAPPER-like ability, which we call a "layout". +The reason we don't use Template Toolkit's WRAPPER (which also makes us +incompatible with it) is because not all template systems support it. +In fact, most don't. + +However, you might want to use it to be able to define C and +regular L variables. + +These few steps will get you there: + +=over 4 + +=item * Disable the layout in Dancer2 + +You can do this by simply commenting (or removing) the C +configuration in the config file. + +=item * Use the Template Toolkit template engine + +Change the configuration of the template to Template Toolkit: + + # in config.yml + template: "template_toolkit" + +=item * Tell the Template Toolkit engine which wrapper to use + + # in config.yml + # ... + engines: + template: + template_toolkit: + WRAPPER: layouts/main.tt + +=back + +Done! Everything will work fine out of the box, including variables and +C variables. + +Please note that disabling the internal layout it will also disable the +hooks C and C. + +=head2 Customizing Template Toolkit in Dancer2 + +Please see L +for more details. + +=head1 Error Recipes =head2 Delivering custom error pages @@ -291,7 +237,7 @@ Or, if we want to use the response object already present in the C<$app> This populates the status code of the response, sets its content, and throws a I in the dispatch process. -=head3 What it will look like +=head3 What it will look like? The error object has quite a few ways to generate its content. @@ -301,14 +247,14 @@ First, it can be explicitly given content => '

OMG

', ); -If the C<$context> was given, the error will check if there is a +If the C<$content> was not given, the error will check if there is a template by the name of the status code (so, say you're using Template -Toolkit, I<418.tt>) and will use it to generate the content, passing it +Toolkit, F<418.tt>) and will use it to generate the content, passing it the error's C<$message>, C<$status> code and C<$title> (which, if not -specified, will be the standard http error definition for the status code). +specified, will be the standard HTTP error definition for the status code). If there is no template, the error will then look for a static page (to -continue with our example, I<418.html>) in the I directory. +continue with our example, F<418.html>) in the F directory. And finally, if all of that failed, the error object will fall back on an internal template. @@ -344,61 +290,16 @@ And if total control is needed: ...; }; -=head2 Template Toolkit's WRAPPER directive in Dancer2 - -Dancer2 already provides a WRAPPER-like ability, which we call a "layout". -The reason we don't use Template Toolkit's WRAPPER (which also makes us -incompatible with it) is because not all template systems support it. -In fact, most don't. - -However, you might want to use it, and be able to define META variables and -regular L variables. - -These few steps will get you there: - -=over 4 - -=item * Disable the layout in Dancer2 - -You can do this by simply commenting (or removing) the C -configuration in the config file. - -=item * Use the Template Toolkit template engine - -Change the configuration of the template to Template Toolkit: - - # in config.yml - template: "template_toolkit" - -=item * Tell the Template Toolkit engine which wrapper to use - - # in config.yml - # ... - engines: - template: - template_toolkit: - WRAPPER: layouts/main.tt - -=back - -Done! Everything will work fine out of the box, including variables and META -variables. - -However, disabling the internal layout it will also disable the hooks C and C. - -=head2 Customizing Template Toolkit in Dancer2 - -Please see L -for more details. +=head1 Configuration Recipes =head2 Accessing configuration information from a separate script You may want to access your webapp's configuration from outside your webapp. You could, of course, use the YAML module of your choice and load -your webapps's C, but chances are that this is not convenient. +your webapps's F, but chances are that this is not convenient. -Use Dancer2 instead. You can simply use -the values from C and some additional default values: +Use Dancer2 instead. You can simply use the values from F +and some additional default values: # bin/show_app_config.pl use Dancer2; @@ -407,10 +308,10 @@ the values from C and some additional default values: Note that C<< config->{log} >> should result in an uninitialized warning on a default scaffold since the environment isn't loaded and -log is defined in the environment and not in C. Hence C. +log is defined in the environment but not in F. Hence C. -Dancer2 will load your C configuration file along with the -correct environment file located in your C directory. +Dancer2 will load your F configuration file along with the +correct environment file located in your F directory. The environment is determined by two environment variables in the following order: @@ -440,6 +341,10 @@ Or you can override them directly in the script (less recommended): ... +=head1 Database Recipes + +TODO: Review this + =head2 Using DBIx::Class L, also known as DBIC, is one of the many Perl ORM @@ -661,6 +566,10 @@ Our bookstore lookup application can now be started using the built-in server: =back +=head1 Authentication Recipes + +TODO: Review content + =head2 Authentication Writing a form for authentication is simple: we check the user credentials @@ -763,6 +672,10 @@ We can now add the B method for verifying that username and password: template login => { error => 'Invalid username or password' }; }; +=head1 JSON/Serialization/API Recipes + +TODO: Review + =head2 Writing a REST application With Dancer2, it's easy to write REST applications. Dancer2 provides helpers @@ -1011,8 +924,108 @@ All that is left now is to render it: template index => { metrics => $metrics }; +=head2 Simplifying AJAX queries with the Ajax plugin + +As an AJAX query is just an HTTP query, it's similar to a GET or POST route. +You may ask yourself why you may want to use the C keyword (from the +L plugin) instead of a simple C. + +Let's say you have a path like C in your application. You may +want to be able to serve this page with a layout and HTML content. But you +may also want to be able to call this same url from a javascript query using +AJAX. + +So, instead of having the following code: + + get '/user/:user' => sub { + if ( request->is_ajax ) { + # create xml, set headers to text/xml, blablabla + header( 'Content-Type' => 'text/xml' ); + header( 'Cache-Control' => 'no-store, no-cache, must-revalidate' ); + to_xml({...}) + } else { + template users => {...} + } + }; + +you can have + + ajax '/user/:user' => sub { + to_xml( {...}, RootName => undef ); + } + +and + + get '/user/:user' => sub { + template users => {...} + } + +Because it's an AJAX query, you know you need to return XML content, so +the content type of the response is set for you. + +=head3 Example: Feeding graph data through AJAX + +Let us assume we are building an application that uses a plotting library +to generate a graph and expects to get its data, which is in the form +of word count from an AJAX call. + +For the graph, we need the url I to return a JSON representation +of the word count data. Dancer in fact has a C function that takes +care of the JSON encapsulation. + + get '/data' => sub { + open my $fh, '<', $count_file; + + my %contestant; + while (<$fh>) { + chomp; + my ( $date, $who, $count ) = split '\s*,\s*'; + + my $epoch = DateTime::Format::Flexible->parse_datetime($date)->epoch; + my $time = 1000 * $epoch; + $contestant{$who}{$time} = $count; + } + + my @json; # data structure that is going to be JSONified + + while ( my ( $peep, $data ) = each %contestant ) { + push @json, { + label => $peep, + hoverable => \1, # so that it becomes JavaScript's 'true' + data => [ map { [ $_, $data->{$_} ] } + sort { $a <=> $b } + keys %$data ], + }; + } + + my $beginning = DateTime::Format::Flexible->parse_datetime( "2010-11-01")->epoch; + my $end = DateTime::Format::Flexible->parse_datetime( "2010-12-01")->epoch; + + push @json, { + label => 'de par', + data => [ + [$beginning * 1000, 0], + [ DateTime->now->epoch * 1_000, + 50_000 + * (DateTime->now->epoch - $beginning) + / ($end - $beginning) + ] + ], + + }; + + encode_json( \@json ); + }; + +For more serious AJAX interaction, there's also L +that adds an I route handler to the mix. + +Because it's an AJAX query, you know you need to return XML content, so +the content type of the response is set for you. + +=head1 Dessert Recipes -=head1 NON-STANDARD STEPS +(these are tasty tips and tricks that don't fit anywhere else) =head2 Turning off warnings