Perl web frameworks
- 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.
- 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/
- 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') {}
- 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"
- 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(
}
- 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"
);
}
- 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});
}
- 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()
- 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"
- 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>
- 70. Nuestro schema es un
componente más ahora!
sub action :Local {
my ( $self, $c ) = @_;
$c->res->body(
$c->model('DB::User')->first->email
);
}
- 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;
- 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
);
}
- 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();
}
}
- 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;
# ...
}
- 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.