SlideShare une entreprise Scribd logo
1  sur  103
Télécharger pour lire hors ligne
Perl web frameworks
   Catalyst & Mojolicious
     Curs avançat de Perl 2012
            10/03/2012
Perl web frameworks
          Hola!
      Diego Kuperman
      diegok | @freekey
Web frameworks
       ~
     MVC
Router
    ~
Dispatcher
Rutas
/
/prueba-curso
/event/23
/event/23/where
/event/23-una-prueba/picture/3
/event/23/picture/3
/event/una-prueba/pic/3
Rutas
/
/prueba-curso
/event/23
/event/23/where
/event/23-una-prueba/picture/3
/event/23/picture/3
/event/una-prueba/pic/3
Controller
Invocado por el dispatcher
Manipulación de capturas del router

Validaciones
Pegamento entre otros componentes:
modelos y vistas
Idealmente poco código: thin controller,
fat models.
Model
   ~
Storage
Model
Habitualmente base de datos
Lógica de negocio

Uso fuera de la app
Tests independientes de la app
Otros modelos: git, api-rest, ...
View
          ~
Templates / Serializers
View
Normalmente un motor de templates

MUCHAS opciones en CPAN
Template toolkit en Catalyst
EP en Mojolicious
Serialización: JSON, XML, YAML, ...
Install
  ~
CPAN
Catalyst
$   cpanm   -n   Catalyst::Runtime Catalyst::Devel
$   cpanm   -n   Catalyst::View::TT Catalyst::View::JSON
$   cpanm   -n   Catalyst::Plugin::Unicode::Encoding
$   cpanm   -n   Catalyst::Plugin::Session
$   cpanm   -n   Catalyst::Plugin::Session::Store::File
$   cpanm   -n   Catalyst::Plugin::Session::State::Cookie
$   cpanm   -n   Catalyst::Plugin::Authentication
$   cpanm   -n   Catalyst::Plugin::Authorization::Roles
$   cpanm   -n   Catalyst::Authentication::Store::DBIx::Class
$ cpanm -n HTML::FormHandler HTML::FormHandler::Model::DBIC
Mojolicious
                   http://mojolicio.us
                   The web in a box
$ cpanm -n Mojolicious
Catalyst vs Mojolicious
Catalyst
$ git clone git://github.com/diegok/dbic.curs.barcelona.pm.git
$ cd dbic.curs.barcelona.pm
$ dzil build; cpanm -n *.tar.gz; dzil clean
$ git clone git://github.com/diegok/app.curs.barcelona.pm.git
$ cd app.curs.barcelona.pm
$ cpanm -n --installdeps .
Catalyst
The elegant MVC framework
Catalyst
                   Crear nueva App
$ catalyst.pl MyCatApp
created "MyCatApp"
created "MyCatApp/script"
created "MyCatApp/lib"
created "MyCatApp/root"
created "MyCatApp/root/static"
...
created "MyCatApp/script/mycatapp_server.pl"
created "MyCatApp/script/mycatapp_test.pl"
created "MyCatApp/script/mycatapp_create.pl"
├──   Changes
├──   Makefile.PL
├──   README
├──   lib
│     └── Curs
|         ├── App
│         │   ├── Controller
│         │   │   └── Root.pm
│         │   ├── Model
│         |   └── View
│         └── App.pm
├──   curs_app.conf
├──   curs_app.psgi
├──   root
│     ├── favicon.ico
│     └── static
│         └── images
│             ├── ...
│             └── catalyst_logo.png
├──   script
│     ├── ...
│     ├── curs_app_create.pl
│     └── curs_app_server.pl
└──   t
      ├── 01app.t
      ├── 02pod.t
      └── 03podcoverage.t
package Curs::App;
use Moose;
use namespace::autoclean;
use Catalyst::Runtime 5.80;
use Catalyst qw/
    -Debug
    ConfigLoader
    Static::Simple
/;
extends 'Catalyst';
our $VERSION = '0.01';
__PACKAGE__->config(
    name => 'Curs::App',
    disable_component_resolution_regex_fallback
    enable_catalyst_header => 1, # Send X-Cataly
);
__PACKAGE__->setup();
package Curs::App;
use Moose;
use namespace::autoclean;
use Catalyst::Runtime 5.80;
use Catalyst qw/
    ConfigLoader
    Static::Simple
/;
extends 'Catalyst';
our $VERSION = '0.01';
__PACKAGE__->config(
    name => 'Curs::App',
    disable_component_resolution_regex_fallback
    enable_catalyst_header => 1, # Send X-Cataly
);
__PACKAGE__->setup();
$ ./script/curs_app_server.pl -r -d
[debug] Debug messages enabled
[debug] Statistics enabled
[debug] Loaded plugins:
.-------------------------------------------------------.
| Catalyst::Plugin::ConfigLoader 0.30                   |
'-------------------------------------------------------'

[debug] Loaded dispatcher "Catalyst::Dispatcher"
[debug] Loaded engine "Catalyst::Engine"
[debug] Found home "/.../Curs-App"
[debug] Loaded Config "/.../Curs-App/curs_app.conf"
[debug] Loaded components:
.--------------------------------------------+----------.
| Class                                      | Type     |
+--------------------------------------------+----------+
| Curs::App::Controller::Root                | instance |
'--------------------------------------------+----------'
[debug] Loaded Private actions:
.-------------+-----------------------------+------------.
| Private     | Class                       | Method     |
+-------------+-----------------------------+------------+
| /default    | Curs::App::Controller::Root | default    |
| /end        | Curs::App::Controller::Root | end        |
| /index      | Curs::App::Controller::Root | index      |
'-------------+-----------------------------+------------'
$ ./script/curs_app_server.pl -r -d

[debug] Loaded Path actions:
.--------------------------------+-----------------------.
| Path                           | Private               |
+--------------------------------+-----------------------+
| /                              | /index                |
| /...                           | /default              |
'--------------------------------+-----------------------'
[info] Curs::App powered by Catalyst 5.90010
HTTP::Server::PSGI: Accepting connections at http://0:3000/
package Curs::App::Controller::Root;
use Moose; use namespace::autoclean;
BEGIN { extends 'Catalyst::Controller' }
__PACKAGE__->config(namespace => '');

sub index :Path :Args(0) {
  my ( $self, $c ) = @_;
  $c->response->body($c->welcome_message);
}

sub default :Path {
  my ( $self, $c ) = @_;
  $c->response->body('Page not found');
  $c->response->status(404);
}
sub end : ActionClass('RenderView') {}
Tiene
Router + Dispatcher
Static::Simple

Controller Root
Acción por defecto
aún no tiene...
Vista/s
Modelo/s

+Controllers
Ninguna gracia!
Contexto
  ~
  $c
Catalyst::Request
$c->request
$c->req # alias

$c->req->params->{foo};
$c->req->cookies->{name};
$c->req->headers->content_type;
$c->req->base;
$c->req->uri_with( { page => 3 } );
Catalyst::Response
$c->response
$c->res # alias
$c->res->body('Hello World');
$c->res->status(404);
$c->res->redirect('http://barcelona.pm');
# CGI::Simple::Cookie
$c->res->cookies->{foo} = { value => '123' };
Catalyst::Log
$c->log
$c->log->debug('Something happened');
$c->log->info('Something you should know');
Stash
$c->stash( key => 'value' );
$c->stash( 'key' ); # 'value'

$c->stash->{key} = [1..10];
$c->stash->{key};   # [1..10]

     Dura un request-response completo
      Paso de datos entre componentes
Routes
        ~
Controller actions
Nuevo Controller
$ ./script/curs_app_create.pl controller Example
 exists ".../Curs-App/script/lib/Curs/App/Controller"
 exists ".../Curs-App/script/t"
created ".../Curs-App/lib/Curs/App/Controller/Example.pm"
created ".../Curs-App/t/controller_Example.t"
lib/Curs/App/Controller/Example.pm
package Curs::App::Controller::Example;
use Moose; use namespace::autoclean;
BEGIN {extends 'Catalyst::Controller'; }
# /example
sub index :Path :Args(0) {
    my ( $self, $c ) = @_;
    $c->res->body('Example index match!');
}
Controller Actions
Literal match (:Path)

Root-level (:Global) = Path('/...')
Namespace-prefixed (:Local) = Path('.../')
Restricción de argumentos (:Args)
/example/cero/...
sub cero :Local {
    my ( $self, $c, @args ) = @_;
    $c->res->body('Args: ' . join ', ', @args);
}


           /example/uno
sub uno :Local :Args(0) {
    my ( $self, $c ) = @_;
    $c->res->body(':Local :Args(0)');
}
/example/dos
sub dos :Path('dos') :Args(0) {
    my ( $self, $c ) = @_;
    $c->res->body(":Path('dos') :Args(0)");
}


           /example/tres
sub tres :Path('/example/tres') :Args(0) {
    my ( $self, $c ) = @_;
    $c->res->body(":Path('/example/tres') :Args(
}
/hola/mundo
sub cuatro :Path('/hola') :Args(1) {
    my ( $self, $c, $arg1 ) = @_;
    $c->res->body("Hola $arg1!");
}
Controller Actions
     Pattern-match
    :Regex() & :LocalRegex()
/item23/order32
sub cinco
  :Regex('^item(d+)/order(d+)$') {
    my ( $self, $c ) = @_;
    my $item = $c->req->captures->[0];
    my $order = $c->req->captures->[1];
    $c->res->body(
      "(cinco) Item: $item | Order: $order"
    );
}
/example/item23/order32
sub seis
  :LocalRegex('^item(d+)/order(d+)$') {
    my ( $self, $c ) = @_;
    my $item = $c->req->captures->[0];
    my $order = $c->req->captures->[1];
    $c->res->body(
      "(seis) Item: $item | Order: $order"
    );
}
Controller Actions
  Privadas & control flow
            :Private
      forward() & detach()
sub now :Local :Args(0) {
    my ( $self, $c ) = @_;
    $c->forward('stash_now');
    $c->detach('say_now');
    $c->log->debug('ouch!');
}

sub stash_now :Private {
    my ( $self, $c ) = @_;
    $c->stash( now => DateTime->now );
}

sub say_now :Private {
    my ( $self, $c ) = @_;
    $c->res->body($c->stash->{now});
}
Built-in special actions
Default controller action
sub default : Path {}

     Como default, con mas precedencia
sub index :Path Args(0) {}
Antes de la acción, solo una vez
sub begin :Private {}

     Despues de la acción, solo una vez
sub end :Private {}

Despues de begin, de menos especifico a mas
                especifico
sub auto :Private {}

     Si retorna false se salta hasta end()
Chained actions
    :Chained
sub with_now : PathPart('example/now')
    Chained( '/' ) CaptureArgs( 0 ) {
    my ( $self, $c ) = @_;
    $c->forward('stash_now');
}
sub show_now : PathPart('show')
    Chained( 'with_now' ) Args( 0 ) {
    my ( $self, $c ) = @_;
    $c->detach('say_now');
}
Chained es MUY potente,
 pero antes tenemos que
añadir algunas cosas mas...
Vistas
Template toolkit
      +
   JSON
$ script/curs_app_create.pl view Web TT
exists ".../Curs-App/script/../lib/Curs/App/View"
exists ".../Curs-App/script/../t"
created ".../Curs-App/script/../lib/Curs/App/View/Web.pm"
created ".../Curs-App/script/../t/view_Web.t"
lib/Curs/App/View/Web.pm
Curs::App::View::Web;
use Moose;
extends 'Catalyst::View::TT';

__PACKAGE__->config(
    TEMPLATE_EXTENSION   =>   '.tt',
    CATALYST_VAR         =>   'c',
    TIMER                =>   0,
    ENCODING             =>   'utf-8'
    WRAPPER              =>   'layout',
    render_die           =>   1,
);

1;
lib/Curs/App.pm
__PACKAGE__->config(
 # ...
 'View::Web' => {
  INCLUDE_PATH => [
    __PACKAGE__->path_to('root', 'src'),
    __PACKAGE__->path_to('root', 'lib'),
  ],
 },
);
root/lib/layout
<!DOCTYPE HTML>
<html lang="en-us">
  <head>
  <meta http-equiv="Content-type" content="text/
  <title>Curs avançat de Perl 2012</title>
  <link rel="stylesheet" href="/css/style.css" t
  </head>
  <body>
    [% content %]
  </body>
</html>
TT y layout en su sitio,
hora de cambiar la home
root/src/index.tt
<h1>[% message %]</h1>

       lib/Curs/App/Controller/Root.pm
sub index :Path :Args(0) {
    my ( $self, $c ) = @_;
    $c->stash(
        message => 'Hola mundo!',
        template => 'index.tt'
    );
}
$ ./script/curs_app_create.pl view JSON JSON
 exists "lib/Curs/App/View"
 exists "t/"
created "lib/Curs/App/View/JSON.pm"
created "t/view_JSON.t"
lib/Curs/App.pm
__PACKAGE__->config({
    ...
    'View::JSON' => {
        expose_stash => 'json', # defaults to ev
    },
    default_view => 'Web',
});
Uso de View::JSON
sub status :Path('/status') :Args(0) {
    my ( $self, $c ) = @_;
    $c->stash(
        json => { value => 'testing' }
    );
    $c->forward('View::JSON');
}
Modelo
 DBIC
Curs::Schema
$ script/curs_app_create.pl model DB DBIC::Schema Curs::Schema
exists ".../Curs-App/script/../lib/Curs/App/Model"
exists ".../Curs-App/script/../t"
created ".../Curs-App/script/../lib/Curs/App/Model/DB.pm"
created ".../Curs-App/script/../t/model_DB.t"
Config por defecto
                 curs_app.conf
name Curs::App
<Model::DB>
    connect_info   dbi:SQLite:dbname=curs_schema
    connect_info
    connect_info
    <connect_info>
        sqlite_unicode      1
        RaiseError          1
    </connect_info>
</Model::DB>
Deploy!
$ ./script/schema_deploy.pl
Creating sql/Curs-Schema-1-SQLite.sql => done.
Making initial deploy (ddbb has no version) => done.
Nuestro schema es un
    componente más ahora!
sub action :Local {
  my ( $self, $c ) = @_;

    $c->res->body(
      $c->model('DB::User')->first->email
    );
}
Authentication
     &
Authorization
Catalyst::Plugin::Authentication
                       &
   Catalyst::Plugin:Authorization::Roles
            + Catalyst::Plugin::Session
lib/Curs/App.pm
use Catalyst qw/
    ...

     Session
     Session::State::Cookie
     Session::Store::File

     Authentication
     Authorization::Roles
/;
__PACKAGE__->config(
    ...
    'Plugin::Authentication' => {
      default_realm => 'users',
      realms        => {
        users => {
          credential => {
            class          => 'Password',
            password_field => 'password',
            password_type => 'self_check',
          },
          store => {
            class      => 'DBIx::Class',
            user_model => 'DB::User',
            role_relation => 'roles',
            role_field => 'name',
            id_field   => 'email'
          }
        }
      }
}
       }
     },
);

Nuevos metodos en la app
$c->authenticate(
    email    => $email,
    password => $pwd
);
$c->user_exists;

$c->user;
Todo listo
Necesitamos un form para login
             :-(
HTML::FormHandler
  al rescate! :-)
lib/Curs/App/Form/Login.pm
package Curs::App::Form::Login;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
use Email::Valid;

has_field 'email' => (
    type => 'Text',
    required => 1,
    apply => [{
        check   => sub {
            Email::Valid->address( $_[0] )
        },
        message => 'Must be a valid email addres
    }]
);
lib/Curs/App/Form/Login.pm
has_field 'password' => (
    type => 'Password',
    required => 1
);

has_field 'submit'   => (
    type => 'Submit',
    value => 'Login'
);
Ahora sí!
Un controller nuevo para
             auth
$ ./script/curs_app_create.pl controller Auth
...
lib/Curs/App/Controller/Auth.pm
package Curs::App::Controller::Auth;
use Moose; use namespace::autoclean;
BEGIN {extends 'Catalyst::Controller'; }
use Curs::App::Form::Login;
sub login :Path(/login) Args(0) {
  my ( $self, $c ) = @_;
  my $form = Curs::App::Form::Login->new();
  my $creds = {
    email    => $form->value->{email},
    password => $form->value->{password} };
  if ( $form->process( params => $c->req->params
    if ( $c->authenticate( $creds ) ) {
      $c->detach('after_login_redirect');
    } else {
      $form->field('password')->add_error( 'Inva
    }
  }
  $c->stash(
    template => 'auth/login.tt',
    form     => $form
  );
}
root/src/auth/login.tt
<div id="login">
  [% form.render %]
</div>
=head2 need_login
 Ensure user exists on the chain.
=cut
sub need_login :PathPart( '' )
    Chained( '/' ) CaptureArgs( 0 ) {
  my ( $self, $c ) = @_;

    unless ( $c->user_exists ) {
      $c->session->{after_login_path} = '/' . $c->
      $c->res->redirect(
        $c->uri_for_action(
          $c->controller('Auth')
            ->action_for('login')
        )
      );
      $c->detach;
    }
}
=head2 need_role_admin
 Ensure user with the admin role.
=cut
sub need_role_admin :PathPart('admin')
    Chained('need_login') CaptureArgs(0) {
  my ( $self, $c ) = @_;
  unless ( $c->check_user_roles( 'admin' ) ) {
    $c->res->body('You need admin role for this
    $c->detach();
  }
}
En otro controller
... perdido en otra galaxia ...
=head2 element_chain
Base chain for actions related to one user
=cut
sub element_chain
    :PathPart('user')
     Chained('/auth/need_login')
     CaptureArgs(1) {

    my ( $self, $c, $user_id ) = @_;
    $c->stash(
      user => $c->model('DB::User')
                ->find( $user_id )
    );
    unless ( $c->stash->{user} ) {
      $c->detach( '/error/element_not_found', [ 'u
    }
}
sub view :PathPart()
    Chained('element_chain') Args(0) {
  my ( $self, $c ) = @_;
  $c->stash( template => 'user/view.tt' );
}

sub delete :PathPart()
    Chained('element_chain') Args(0) {
  my ( $self, $c ) = @_;
  $c->stash->{user}->delete;
  # ...
}
Plugin
   vs
TraitFor
Plugin global
        vs
Plugin for component
TraitFor Controller
  Role para el controller
package Catalyst::TraitFor::Controller::WithDate
use MooseX::MethodAttributes::Role;
use namespace::autoclean;
use DateTime;

has 'stash_key' => ( is => 'ro', default => 'dat
after 'auto' => sub {
    my ( $self, $c ) = @_;
    $c->stash( $self->stash_key => DateTime->now
};
sub auto : Private { 1 }
Trait's locales
package Curs::App::TraitFor::Controller::WithDBI
use MooseX::MethodAttributes::Role;
use namespace::autoclean;

require 'model_name';
require 'base_chain';
has stash_key => (
    is      => 'ro',
    default => sub {
        lc @{[split /::/, shift->model_name ]}[-
    }
);
...
sub item :PathPart('') Chained('base_chain') Cap
    my ( $self, $c, $id ) = @_;
    $c->stash->{ $self->stash_key }
        = $c->model( $self->model_name )->find($
        || $c->detach('missing');
}

sub missing {
    my ( $self, $c ) = @_;
    $c->res->code(404);
    $c->res->body('Not found!');
}
1;
A consumir!
package Curs::App::Controller::Event;
use Moose; use namespace::autoclean;
BEGIN {extends 'Catalyst::Controller'}
has model_name => ( is => 'ro', default => 'DB::
with 'Curs::App::TraitFor::Controller::WithDBIC'

sub base_chain :PathPart('event')
                Chained('/') CaptureArgs(1) {}

sub delete :PathPart('delete') Chained('item') A
    my ( $self, $c ) = @_;
    $c->stash->{event}->delete;
}
https://metacpan.org/search?q=catalyst

          896 results
Plack
(ya lo estamos usando)
$ cpanm -n Starman
...
$ starman curs_app.psgi
2012/03/10-11:25:36 Starman::Server
(type Net::Server::PreFork) starting! pid(73661)
Binding to TCP port 5000 on host *
Setting gid to "20 20 20 204 100 98 81 80 79 61
Más Catalyst
                  IRC
#catalyst en irc.perl.org.

#catalyst-dev en irc.perl.org (desarrollo).
              Mailing lists
http://lists.scsys.co.uk/mailman/listinfo/catalyst

http://lists.scsys.co.uk/mailman/listinfo/catalyst-
dev
Manual
           Ejercicios
https://metacpan.org/module/Catalyst::Manual
Añadir un metodo (API) que deje ver
datos de UN usuario en JSON:
/user/1/json
   Extra: vista json que devuelva array de
   usuarios (sin repetir codigo)

Añadir una vista que liste los eventos
(Creados en la práctica anterior)
Crear una acción (solo para admins), un
formulario y su plantilla para crear un
evento y otra para editarlo.

Contenu connexe

Tendances

Keeping it small: Getting to know the Slim micro framework
Keeping it small: Getting to know the Slim micro frameworkKeeping it small: Getting to know the Slim micro framework
Keeping it small: Getting to know the Slim micro framework
Jeremy Kendall
 
Operation Oriented Web Applications / Yokohama pm7
Operation Oriented Web Applications / Yokohama pm7Operation Oriented Web Applications / Yokohama pm7
Operation Oriented Web Applications / Yokohama pm7
Masahiro Nagano
 
YAPC::Asia 2010 Twitter解析サービス
YAPC::Asia 2010 Twitter解析サービスYAPC::Asia 2010 Twitter解析サービス
YAPC::Asia 2010 Twitter解析サービス
Yusuke Wada
 
Keeping it Small: Getting to know the Slim Micro Framework
Keeping it Small: Getting to know the Slim Micro FrameworkKeeping it Small: Getting to know the Slim Micro Framework
Keeping it Small: Getting to know the Slim Micro Framework
Jeremy Kendall
 
AnyMQ, Hippie, and the real-time web
AnyMQ, Hippie, and the real-time webAnyMQ, Hippie, and the real-time web
AnyMQ, Hippie, and the real-time web
clkao
 

Tendances (20)

Keeping it small: Getting to know the Slim micro framework
Keeping it small: Getting to know the Slim micro frameworkKeeping it small: Getting to know the Slim micro framework
Keeping it small: Getting to know the Slim micro framework
 
Keeping it small - Getting to know the Slim PHP micro framework
Keeping it small - Getting to know the Slim PHP micro frameworkKeeping it small - Getting to know the Slim PHP micro framework
Keeping it small - Getting to know the Slim PHP micro framework
 
Mojolicious - A new hope
Mojolicious - A new hopeMojolicious - A new hope
Mojolicious - A new hope
 
Perl: Hate it for the Right Reasons
Perl: Hate it for the Right ReasonsPerl: Hate it for the Right Reasons
Perl: Hate it for the Right Reasons
 
Mojolicious. Веб в коробке!
Mojolicious. Веб в коробке!Mojolicious. Веб в коробке!
Mojolicious. Веб в коробке!
 
Any event intro
Any event introAny event intro
Any event intro
 
Building Modern and Secure PHP Applications – Codementor Office Hours with Be...
Building Modern and Secure PHP Applications – Codementor Office Hours with Be...Building Modern and Secure PHP Applications – Codementor Office Hours with Be...
Building Modern and Secure PHP Applications – Codementor Office Hours with Be...
 
Slim RedBeanPHP and Knockout
Slim RedBeanPHP and KnockoutSlim RedBeanPHP and Knockout
Slim RedBeanPHP and Knockout
 
Perl5i
Perl5iPerl5i
Perl5i
 
Mojolicious - Perl Framework for the Real-Time Web (Lightning Talk)
Mojolicious - Perl Framework for the Real-Time Web (Lightning Talk)Mojolicious - Perl Framework for the Real-Time Web (Lightning Talk)
Mojolicious - Perl Framework for the Real-Time Web (Lightning Talk)
 
A reviravolta do desenvolvimento web
A reviravolta do desenvolvimento webA reviravolta do desenvolvimento web
A reviravolta do desenvolvimento web
 
Operation Oriented Web Applications / Yokohama pm7
Operation Oriented Web Applications / Yokohama pm7Operation Oriented Web Applications / Yokohama pm7
Operation Oriented Web Applications / Yokohama pm7
 
Mojolicious: what works and what doesn't
Mojolicious: what works and what doesn'tMojolicious: what works and what doesn't
Mojolicious: what works and what doesn't
 
YAPC::Asia 2010 Twitter解析サービス
YAPC::Asia 2010 Twitter解析サービスYAPC::Asia 2010 Twitter解析サービス
YAPC::Asia 2010 Twitter解析サービス
 
PHP5.5 is Here
PHP5.5 is HerePHP5.5 is Here
PHP5.5 is Here
 
Keeping it Small: Getting to know the Slim Micro Framework
Keeping it Small: Getting to know the Slim Micro FrameworkKeeping it Small: Getting to know the Slim Micro Framework
Keeping it Small: Getting to know the Slim Micro Framework
 
The promise of asynchronous PHP
The promise of asynchronous PHPThe promise of asynchronous PHP
The promise of asynchronous PHP
 
AnyMQ, Hippie, and the real-time web
AnyMQ, Hippie, and the real-time webAnyMQ, Hippie, and the real-time web
AnyMQ, Hippie, and the real-time web
 
Perl Web Client
Perl Web ClientPerl Web Client
Perl Web Client
 
Blog Hacks 2011
Blog Hacks 2011Blog Hacks 2011
Blog Hacks 2011
 

Similaire à Curscatalyst

Railsconf2011 deployment tips_for_slideshare
Railsconf2011 deployment tips_for_slideshareRailsconf2011 deployment tips_for_slideshare
Railsconf2011 deployment tips_for_slideshare
tomcopeland
 
Web applications with Catalyst
Web applications with CatalystWeb applications with Catalyst
Web applications with Catalyst
svilen.ivanov
 
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQueryRemedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Tatsuhiko Miyagawa
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overview
Yehuda Katz
 
Yapc::Asia 2008 Tokyo - Easy system administration programming with a framewo...
Yapc::Asia 2008 Tokyo - Easy system administration programming with a framewo...Yapc::Asia 2008 Tokyo - Easy system administration programming with a framewo...
Yapc::Asia 2008 Tokyo - Easy system administration programming with a framewo...
Gosuke Miyashita
 
4069180 Caching Performance Lessons From Facebook
4069180 Caching Performance Lessons From Facebook4069180 Caching Performance Lessons From Facebook
4069180 Caching Performance Lessons From Facebook
guoqing75
 

Similaire à Curscatalyst (20)

Bag Of Tricks From Iusethis
Bag Of Tricks From IusethisBag Of Tricks From Iusethis
Bag Of Tricks From Iusethis
 
Burn down the silos! Helping dev and ops gel on high availability websites
Burn down the silos! Helping dev and ops gel on high availability websitesBurn down the silos! Helping dev and ops gel on high availability websites
Burn down the silos! Helping dev and ops gel on high availability websites
 
Catalyst MVC
Catalyst MVCCatalyst MVC
Catalyst MVC
 
Railsconf2011 deployment tips_for_slideshare
Railsconf2011 deployment tips_for_slideshareRailsconf2011 deployment tips_for_slideshare
Railsconf2011 deployment tips_for_slideshare
 
Api Design
Api DesignApi Design
Api Design
 
Web applications with Catalyst
Web applications with CatalystWeb applications with Catalyst
Web applications with Catalyst
 
Writing Pluggable Software
Writing Pluggable SoftwareWriting Pluggable Software
Writing Pluggable Software
 
WordPress 運用を支える Perl
WordPress 運用を支える PerlWordPress 運用を支える Perl
WordPress 運用を支える Perl
 
Hacking Movable Type
Hacking Movable TypeHacking Movable Type
Hacking Movable Type
 
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQueryRemedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
 
Getting Started with Capistrano
Getting Started with CapistranoGetting Started with Capistrano
Getting Started with Capistrano
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overview
 
Yapc::Asia 2008 Tokyo - Easy system administration programming with a framewo...
Yapc::Asia 2008 Tokyo - Easy system administration programming with a framewo...Yapc::Asia 2008 Tokyo - Easy system administration programming with a framewo...
Yapc::Asia 2008 Tokyo - Easy system administration programming with a framewo...
 
Keep it simple web development stack
Keep it simple web development stackKeep it simple web development stack
Keep it simple web development stack
 
関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい
 
4069180 Caching Performance Lessons From Facebook
4069180 Caching Performance Lessons From Facebook4069180 Caching Performance Lessons From Facebook
4069180 Caching Performance Lessons From Facebook
 
Quality Use Of Plugin
Quality Use Of PluginQuality Use Of Plugin
Quality Use Of Plugin
 
PSGI and Plack from first principles
PSGI and Plack from first principlesPSGI and Plack from first principles
PSGI and Plack from first principles
 
Catalyst patterns-yapc-eu-2016
Catalyst patterns-yapc-eu-2016Catalyst patterns-yapc-eu-2016
Catalyst patterns-yapc-eu-2016
 
PerlDancer for Perlers (FOSDEM 2011)
PerlDancer for Perlers (FOSDEM 2011)PerlDancer for Perlers (FOSDEM 2011)
PerlDancer for Perlers (FOSDEM 2011)
 

Dernier

Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
WSO2
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Victor Rentea
 

Dernier (20)

Exploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusExploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with Milvus
 
Artificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : UncertaintyArtificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : Uncertainty
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
MS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsMS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectors
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
 
[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf
 
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
 
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 AmsterdamDEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
 
FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024
 
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
 
Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..
 
WSO2's API Vision: Unifying Control, Empowering Developers
WSO2's API Vision: Unifying Control, Empowering DevelopersWSO2's API Vision: Unifying Control, Empowering Developers
WSO2's API Vision: Unifying Control, Empowering Developers
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
DBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor Presentation
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 

Curscatalyst

  • 1. Perl web frameworks Catalyst & Mojolicious Curs avançat de Perl 2012 10/03/2012
  • 2. Perl web frameworks Hola! Diego Kuperman diegok | @freekey
  • 4.
  • 5. Router ~ Dispatcher
  • 9. Invocado por el dispatcher Manipulación de capturas del router Validaciones Pegamento entre otros componentes: modelos y vistas Idealmente poco código: thin controller, fat models.
  • 10. Model ~ Storage
  • 11. Model Habitualmente base de datos Lógica de negocio Uso fuera de la app Tests independientes de la app Otros modelos: git, api-rest, ...
  • 12. View ~ Templates / Serializers
  • 13. View Normalmente un motor de templates MUCHAS opciones en CPAN Template toolkit en Catalyst EP en Mojolicious Serialización: JSON, XML, YAML, ...
  • 15. Catalyst $ cpanm -n Catalyst::Runtime Catalyst::Devel $ cpanm -n Catalyst::View::TT Catalyst::View::JSON $ cpanm -n Catalyst::Plugin::Unicode::Encoding $ cpanm -n Catalyst::Plugin::Session $ cpanm -n Catalyst::Plugin::Session::Store::File $ cpanm -n Catalyst::Plugin::Session::State::Cookie $ cpanm -n Catalyst::Plugin::Authentication $ cpanm -n Catalyst::Plugin::Authorization::Roles $ cpanm -n Catalyst::Authentication::Store::DBIx::Class $ cpanm -n HTML::FormHandler HTML::FormHandler::Model::DBIC
  • 16. Mojolicious http://mojolicio.us The web in a box $ cpanm -n Mojolicious
  • 19. $ git clone git://github.com/diegok/dbic.curs.barcelona.pm.git $ cd dbic.curs.barcelona.pm $ dzil build; cpanm -n *.tar.gz; dzil clean $ git clone git://github.com/diegok/app.curs.barcelona.pm.git $ cd app.curs.barcelona.pm $ cpanm -n --installdeps .
  • 21. Catalyst Crear nueva App $ catalyst.pl MyCatApp created "MyCatApp" created "MyCatApp/script" created "MyCatApp/lib" created "MyCatApp/root" created "MyCatApp/root/static" ... created "MyCatApp/script/mycatapp_server.pl" created "MyCatApp/script/mycatapp_test.pl" created "MyCatApp/script/mycatapp_create.pl"
  • 22. ├── Changes ├── Makefile.PL ├── README ├── lib │ └── Curs | ├── App │ │ ├── Controller │ │ │ └── Root.pm │ │ ├── Model │ | └── View │ └── App.pm ├── curs_app.conf ├── curs_app.psgi
  • 23. ├── root │ ├── favicon.ico │ └── static │ └── images │ ├── ... │ └── catalyst_logo.png ├── script │ ├── ... │ ├── curs_app_create.pl │ └── curs_app_server.pl └── t ├── 01app.t ├── 02pod.t └── 03podcoverage.t
  • 24. package Curs::App; use Moose; use namespace::autoclean; use Catalyst::Runtime 5.80; use Catalyst qw/ -Debug ConfigLoader Static::Simple /; extends 'Catalyst'; our $VERSION = '0.01'; __PACKAGE__->config( name => 'Curs::App', disable_component_resolution_regex_fallback enable_catalyst_header => 1, # Send X-Cataly ); __PACKAGE__->setup();
  • 25. package Curs::App; use Moose; use namespace::autoclean; use Catalyst::Runtime 5.80; use Catalyst qw/ ConfigLoader Static::Simple /; extends 'Catalyst'; our $VERSION = '0.01'; __PACKAGE__->config( name => 'Curs::App', disable_component_resolution_regex_fallback enable_catalyst_header => 1, # Send X-Cataly ); __PACKAGE__->setup();
  • 26. $ ./script/curs_app_server.pl -r -d [debug] Debug messages enabled [debug] Statistics enabled [debug] Loaded plugins: .-------------------------------------------------------. | Catalyst::Plugin::ConfigLoader 0.30 | '-------------------------------------------------------' [debug] Loaded dispatcher "Catalyst::Dispatcher" [debug] Loaded engine "Catalyst::Engine" [debug] Found home "/.../Curs-App" [debug] Loaded Config "/.../Curs-App/curs_app.conf" [debug] Loaded components: .--------------------------------------------+----------. | Class | Type | +--------------------------------------------+----------+ | Curs::App::Controller::Root | instance | '--------------------------------------------+----------' [debug] Loaded Private actions: .-------------+-----------------------------+------------. | Private | Class | Method | +-------------+-----------------------------+------------+ | /default | Curs::App::Controller::Root | default | | /end | Curs::App::Controller::Root | end | | /index | Curs::App::Controller::Root | index | '-------------+-----------------------------+------------'
  • 27. $ ./script/curs_app_server.pl -r -d [debug] Loaded Path actions: .--------------------------------+-----------------------. | Path | Private | +--------------------------------+-----------------------+ | / | /index | | /... | /default | '--------------------------------+-----------------------' [info] Curs::App powered by Catalyst 5.90010 HTTP::Server::PSGI: Accepting connections at http://0:3000/
  • 28.
  • 29. package Curs::App::Controller::Root; use Moose; use namespace::autoclean; BEGIN { extends 'Catalyst::Controller' } __PACKAGE__->config(namespace => ''); sub index :Path :Args(0) { my ( $self, $c ) = @_; $c->response->body($c->welcome_message); } sub default :Path { my ( $self, $c ) = @_; $c->response->body('Page not found'); $c->response->status(404); } sub end : ActionClass('RenderView') {}
  • 34. Catalyst::Response $c->response $c->res # alias $c->res->body('Hello World'); $c->res->status(404); $c->res->redirect('http://barcelona.pm'); # CGI::Simple::Cookie $c->res->cookies->{foo} = { value => '123' };
  • 36. Stash $c->stash( key => 'value' ); $c->stash( 'key' ); # 'value' $c->stash->{key} = [1..10]; $c->stash->{key}; # [1..10] Dura un request-response completo Paso de datos entre componentes
  • 37. Routes ~ Controller actions
  • 38. Nuevo Controller $ ./script/curs_app_create.pl controller Example exists ".../Curs-App/script/lib/Curs/App/Controller" exists ".../Curs-App/script/t" created ".../Curs-App/lib/Curs/App/Controller/Example.pm" created ".../Curs-App/t/controller_Example.t"
  • 39. lib/Curs/App/Controller/Example.pm package Curs::App::Controller::Example; use Moose; use namespace::autoclean; BEGIN {extends 'Catalyst::Controller'; } # /example sub index :Path :Args(0) { my ( $self, $c ) = @_; $c->res->body('Example index match!'); }
  • 40. Controller Actions Literal match (:Path) Root-level (:Global) = Path('/...') Namespace-prefixed (:Local) = Path('.../') Restricción de argumentos (:Args)
  • 41. /example/cero/... sub cero :Local { my ( $self, $c, @args ) = @_; $c->res->body('Args: ' . join ', ', @args); } /example/uno sub uno :Local :Args(0) { my ( $self, $c ) = @_; $c->res->body(':Local :Args(0)'); }
  • 42. /example/dos sub dos :Path('dos') :Args(0) { my ( $self, $c ) = @_; $c->res->body(":Path('dos') :Args(0)"); } /example/tres sub tres :Path('/example/tres') :Args(0) { my ( $self, $c ) = @_; $c->res->body(":Path('/example/tres') :Args( }
  • 43. /hola/mundo sub cuatro :Path('/hola') :Args(1) { my ( $self, $c, $arg1 ) = @_; $c->res->body("Hola $arg1!"); }
  • 44. Controller Actions Pattern-match :Regex() & :LocalRegex()
  • 45. /item23/order32 sub cinco :Regex('^item(d+)/order(d+)$') { my ( $self, $c ) = @_; my $item = $c->req->captures->[0]; my $order = $c->req->captures->[1]; $c->res->body( "(cinco) Item: $item | Order: $order" ); }
  • 46. /example/item23/order32 sub seis :LocalRegex('^item(d+)/order(d+)$') { my ( $self, $c ) = @_; my $item = $c->req->captures->[0]; my $order = $c->req->captures->[1]; $c->res->body( "(seis) Item: $item | Order: $order" ); }
  • 47. Controller Actions Privadas & control flow :Private forward() & detach()
  • 48. sub now :Local :Args(0) { my ( $self, $c ) = @_; $c->forward('stash_now'); $c->detach('say_now'); $c->log->debug('ouch!'); } sub stash_now :Private { my ( $self, $c ) = @_; $c->stash( now => DateTime->now ); } sub say_now :Private { my ( $self, $c ) = @_; $c->res->body($c->stash->{now}); }
  • 50. Default controller action sub default : Path {} Como default, con mas precedencia sub index :Path Args(0) {}
  • 51. Antes de la acción, solo una vez sub begin :Private {} Despues de la acción, solo una vez sub end :Private {} Despues de begin, de menos especifico a mas especifico sub auto :Private {} Si retorna false se salta hasta end()
  • 52. Chained actions :Chained
  • 53. sub with_now : PathPart('example/now') Chained( '/' ) CaptureArgs( 0 ) { my ( $self, $c ) = @_; $c->forward('stash_now'); } sub show_now : PathPart('show') Chained( 'with_now' ) Args( 0 ) { my ( $self, $c ) = @_; $c->detach('say_now'); }
  • 54. Chained es MUY potente, pero antes tenemos que añadir algunas cosas mas...
  • 56. $ script/curs_app_create.pl view Web TT exists ".../Curs-App/script/../lib/Curs/App/View" exists ".../Curs-App/script/../t" created ".../Curs-App/script/../lib/Curs/App/View/Web.pm" created ".../Curs-App/script/../t/view_Web.t"
  • 57. lib/Curs/App/View/Web.pm Curs::App::View::Web; use Moose; extends 'Catalyst::View::TT'; __PACKAGE__->config( TEMPLATE_EXTENSION => '.tt', CATALYST_VAR => 'c', TIMER => 0, ENCODING => 'utf-8' WRAPPER => 'layout', render_die => 1, ); 1;
  • 58. lib/Curs/App.pm __PACKAGE__->config( # ... 'View::Web' => { INCLUDE_PATH => [ __PACKAGE__->path_to('root', 'src'), __PACKAGE__->path_to('root', 'lib'), ], }, );
  • 59. root/lib/layout <!DOCTYPE HTML> <html lang="en-us"> <head> <meta http-equiv="Content-type" content="text/ <title>Curs avançat de Perl 2012</title> <link rel="stylesheet" href="/css/style.css" t </head> <body> [% content %] </body> </html>
  • 60. TT y layout en su sitio, hora de cambiar la home
  • 61. root/src/index.tt <h1>[% message %]</h1> lib/Curs/App/Controller/Root.pm sub index :Path :Args(0) { my ( $self, $c ) = @_; $c->stash( message => 'Hola mundo!', template => 'index.tt' ); }
  • 62.
  • 63. $ ./script/curs_app_create.pl view JSON JSON exists "lib/Curs/App/View" exists "t/" created "lib/Curs/App/View/JSON.pm" created "t/view_JSON.t"
  • 64. lib/Curs/App.pm __PACKAGE__->config({ ... 'View::JSON' => { expose_stash => 'json', # defaults to ev }, default_view => 'Web', });
  • 65. Uso de View::JSON sub status :Path('/status') :Args(0) { my ( $self, $c ) = @_; $c->stash( json => { value => 'testing' } ); $c->forward('View::JSON'); }
  • 67. Curs::Schema $ script/curs_app_create.pl model DB DBIC::Schema Curs::Schema exists ".../Curs-App/script/../lib/Curs/App/Model" exists ".../Curs-App/script/../t" created ".../Curs-App/script/../lib/Curs/App/Model/DB.pm" created ".../Curs-App/script/../t/model_DB.t"
  • 68. Config por defecto curs_app.conf name Curs::App <Model::DB> connect_info dbi:SQLite:dbname=curs_schema connect_info connect_info <connect_info> sqlite_unicode 1 RaiseError 1 </connect_info> </Model::DB>
  • 69. Deploy! $ ./script/schema_deploy.pl Creating sql/Curs-Schema-1-SQLite.sql => done. Making initial deploy (ddbb has no version) => done.
  • 70. Nuestro schema es un componente más ahora! sub action :Local { my ( $self, $c ) = @_; $c->res->body( $c->model('DB::User')->first->email ); }
  • 71. Authentication & Authorization
  • 72. Catalyst::Plugin::Authentication & Catalyst::Plugin:Authorization::Roles + Catalyst::Plugin::Session
  • 73. lib/Curs/App.pm use Catalyst qw/ ... Session Session::State::Cookie Session::Store::File Authentication Authorization::Roles /;
  • 74. __PACKAGE__->config( ... 'Plugin::Authentication' => { default_realm => 'users', realms => { users => { credential => { class => 'Password', password_field => 'password', password_type => 'self_check', }, store => { class => 'DBIx::Class', user_model => 'DB::User', role_relation => 'roles', role_field => 'name', id_field => 'email' } } }
  • 75. } } }, ); Nuevos metodos en la app $c->authenticate( email => $email, password => $pwd ); $c->user_exists; $c->user;
  • 76. Todo listo Necesitamos un form para login :-(
  • 77. HTML::FormHandler al rescate! :-)
  • 78. lib/Curs/App/Form/Login.pm package Curs::App::Form::Login; use HTML::FormHandler::Moose; extends 'HTML::FormHandler'; use Email::Valid; has_field 'email' => ( type => 'Text', required => 1, apply => [{ check => sub { Email::Valid->address( $_[0] ) }, message => 'Must be a valid email addres }] );
  • 79. lib/Curs/App/Form/Login.pm has_field 'password' => ( type => 'Password', required => 1 ); has_field 'submit' => ( type => 'Submit', value => 'Login' );
  • 81. Un controller nuevo para auth $ ./script/curs_app_create.pl controller Auth ...
  • 82. lib/Curs/App/Controller/Auth.pm package Curs::App::Controller::Auth; use Moose; use namespace::autoclean; BEGIN {extends 'Catalyst::Controller'; } use Curs::App::Form::Login;
  • 83. sub login :Path(/login) Args(0) { my ( $self, $c ) = @_; my $form = Curs::App::Form::Login->new(); my $creds = { email => $form->value->{email}, password => $form->value->{password} }; if ( $form->process( params => $c->req->params if ( $c->authenticate( $creds ) ) { $c->detach('after_login_redirect'); } else { $form->field('password')->add_error( 'Inva } } $c->stash( template => 'auth/login.tt', form => $form ); }
  • 84. root/src/auth/login.tt <div id="login"> [% form.render %] </div>
  • 85. =head2 need_login Ensure user exists on the chain. =cut sub need_login :PathPart( '' ) Chained( '/' ) CaptureArgs( 0 ) { my ( $self, $c ) = @_; unless ( $c->user_exists ) { $c->session->{after_login_path} = '/' . $c-> $c->res->redirect( $c->uri_for_action( $c->controller('Auth') ->action_for('login') ) ); $c->detach; } }
  • 86. =head2 need_role_admin Ensure user with the admin role. =cut sub need_role_admin :PathPart('admin') Chained('need_login') CaptureArgs(0) { my ( $self, $c ) = @_; unless ( $c->check_user_roles( 'admin' ) ) { $c->res->body('You need admin role for this $c->detach(); } }
  • 87. En otro controller ... perdido en otra galaxia ...
  • 88. =head2 element_chain Base chain for actions related to one user =cut sub element_chain :PathPart('user') Chained('/auth/need_login') CaptureArgs(1) { my ( $self, $c, $user_id ) = @_; $c->stash( user => $c->model('DB::User') ->find( $user_id ) ); unless ( $c->stash->{user} ) { $c->detach( '/error/element_not_found', [ 'u } }
  • 89. sub view :PathPart() Chained('element_chain') Args(0) { my ( $self, $c ) = @_; $c->stash( template => 'user/view.tt' ); } sub delete :PathPart() Chained('element_chain') Args(0) { my ( $self, $c ) = @_; $c->stash->{user}->delete; # ... }
  • 90. Plugin vs TraitFor
  • 91. Plugin global vs Plugin for component
  • 92. TraitFor Controller Role para el controller
  • 93. package Catalyst::TraitFor::Controller::WithDate use MooseX::MethodAttributes::Role; use namespace::autoclean; use DateTime; has 'stash_key' => ( is => 'ro', default => 'dat after 'auto' => sub { my ( $self, $c ) = @_; $c->stash( $self->stash_key => DateTime->now }; sub auto : Private { 1 }
  • 95. package Curs::App::TraitFor::Controller::WithDBI use MooseX::MethodAttributes::Role; use namespace::autoclean; require 'model_name'; require 'base_chain'; has stash_key => ( is => 'ro', default => sub { lc @{[split /::/, shift->model_name ]}[- } );
  • 96. ... sub item :PathPart('') Chained('base_chain') Cap my ( $self, $c, $id ) = @_; $c->stash->{ $self->stash_key } = $c->model( $self->model_name )->find($ || $c->detach('missing'); } sub missing { my ( $self, $c ) = @_; $c->res->code(404); $c->res->body('Not found!'); } 1;
  • 98. package Curs::App::Controller::Event; use Moose; use namespace::autoclean; BEGIN {extends 'Catalyst::Controller'} has model_name => ( is => 'ro', default => 'DB:: with 'Curs::App::TraitFor::Controller::WithDBIC' sub base_chain :PathPart('event') Chained('/') CaptureArgs(1) {} sub delete :PathPart('delete') Chained('item') A my ( $self, $c ) = @_; $c->stash->{event}->delete; }
  • 101. $ cpanm -n Starman ... $ starman curs_app.psgi 2012/03/10-11:25:36 Starman::Server (type Net::Server::PreFork) starting! pid(73661) Binding to TCP port 5000 on host * Setting gid to "20 20 20 204 100 98 81 80 79 61
  • 102. Más Catalyst IRC #catalyst en irc.perl.org. #catalyst-dev en irc.perl.org (desarrollo). Mailing lists http://lists.scsys.co.uk/mailman/listinfo/catalyst http://lists.scsys.co.uk/mailman/listinfo/catalyst- dev
  • 103. Manual Ejercicios https://metacpan.org/module/Catalyst::Manual Añadir un metodo (API) que deje ver datos de UN usuario en JSON: /user/1/json Extra: vista json que devuelva array de usuarios (sin repetir codigo) Añadir una vista que liste los eventos (Creados en la práctica anterior) Crear una acción (solo para admins), un formulario y su plantilla para crear un evento y otra para editarlo.