Curscatalyst
Upcoming SlideShare
Loading in...5
×
 

Curscatalyst

on

  • 1,306 views

 

Statistics

Views

Total Views
1,306
Views on SlideShare
1,303
Embed Views
3

Actions

Likes
0
Downloads
3
Comments
0

2 Embeds 3

http://www.linkedin.com 2
https://www.linkedin.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Curscatalyst Curscatalyst Presentation Transcript

  • 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 dispatcherManipulación de capturas del routerValidacionesPegamento entre otros componentes:modelos y vistasIdealmente poco código: thin controller,fat models.
  • Model ~Storage
  • ModelHabitualmente base de datosLógica de negocioUso fuera de la appTests independientes de la appOtros modelos: git, api-rest, ...
  • View ~Templates / Serializers
  • ViewNormalmente un motor de templatesMUCHAS opciones en CPANTemplate toolkit en CatalystEP en MojoliciousSerializació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 .
  • CatalystThe elegant MVC framework
  • Catalyst Crear nueva App$ catalyst.pl MyCatAppcreated "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.90010HTTP::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) {}
  • TieneRouter + DispatcherStatic::SimpleController RootAcción por defecto
  • aún no tiene...Vista/sModelo/s+ControllersNinguna 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.pmpackage Curs::App::Controller::Example;use Moose; use namespace::autoclean;BEGIN {extends Catalyst::Controller; }# /examplesub index :Path :Args(0) { my ( $self, $c ) = @_; $c->res->body(Example index match!);}
  • Controller ActionsLiteral 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/unosub uno :Local :Args(0) { my ( $self, $c ) = @_; $c->res->body(:Local :Args(0));}
  • /example/dossub dos :Path(dos) :Args(0) { my ( $self, $c ) = @_; $c->res->body(":Path(dos) :Args(0)");} /example/tressub tres :Path(/example/tres) :Args(0) { my ( $self, $c ) = @_; $c->res->body(":Path(/example/tres) :Args(}
  • /hola/mundosub cuatro :Path(/hola) :Args(1) { my ( $self, $c, $arg1 ) = @_; $c->res->body("Hola $arg1!");}
  • Controller Actions Pattern-match :Regex() & :LocalRegex()
  • /item23/order32sub 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/order32sub 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 actionsub default : Path {} Como default, con mas precedenciasub index :Path Args(0) {}
  • Antes de la acción, solo una vezsub begin :Private {} Despues de la acción, solo una vezsub end :Private {}Despues de begin, de menos especifico a mas especificosub 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 queañadir algunas cosas mas...
  • VistasTemplate toolkit + JSON
  • $ script/curs_app_create.pl view Web TTexists ".../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.pmCurs::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.pmsub 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::JSONsub 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::Schemaexists ".../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.confname 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.plCreating 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.pmuse 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 listoNecesitamos un form para login :-(
  • HTML::FormHandler al rescate! :-)
  • lib/Curs/App/Form/Login.pmpackage 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.pmhas_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.pmpackage 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.=cutsub 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.=cutsub 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_chainBase chain for actions related to one user=cutsub 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 vsTraitFor
  • Plugin global vsPlugin for component
  • TraitFor Controller Role para el controller
  • package Catalyst::TraitFor::Controller::WithDateuse MooseX::MethodAttributes::Role;use namespace::autoclean;use DateTime;has stash_key => ( is => ro, default => datafter auto => sub { my ( $self, $c ) = @_; $c->stash( $self->stash_key => DateTime->now};sub auto : Private { 1 }
  • Traits locales
  • package Curs::App::TraitFor::Controller::WithDBIuse 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::WithDBICsub 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.psgi2012/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 listshttp://lists.scsys.co.uk/mailman/listinfo/catalysthttp://lists.scsys.co.uk/mailman/listinfo/catalyst-dev
  • Manual Ejercicioshttps://metacpan.org/module/Catalyst::ManualAñadir un metodo (API) que deje verdatos 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), unformulario y su plantilla para crear unevento y otra para editarlo.