Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Intro to Mojolicious - Perl

Intro to Mojolicious - Perl

An overview of what Perl's Mojolicious has to offer; a presentation given at YAPC::NA 2012, in Madison, Wisconsin

http://mojolicio.us
http://perl.org

3bd926f51fd711cc99e6b846dc4bc56f?s=128

Glen Hinkle

June 17, 2012
Tweet

Transcript

  1. mojolicious next-generation real-time web framework intro to

  2. Full framework not a micro-framework client and server

  3. No Dependencies* Full framework *IO::Socket::SSL, IO::Socket::INET6 not a micro-framework client

    and server
  4. No Dependencies* Full framework *IO::Socket::SSL, IO::Socket::INET6 not a micro-framework client

    and server Bleeding edge websockets, non-blocking events, concurrent requests, IPv6
  5. No Dependencies* Full framework Pretty not a micro-framework client and

    server *IO::Socket::SSL, IO::Socket::INET6 API, presentation, docs, installation Bleeding edge websockets, non-blocking events, concurrent requests, IPv6
  6. No Dependencies* Full framework *IO::Socket::SSL, IO::Socket::INET6 not a micro-framework client

    and server Bleeding edge websockets, non-blocking events, concurrent requests, IPv6 Pretty API, presentation, docs, installation
  7. client

  8. client Mojo::UserAgent

  9. my $ua = Mojo::UserAgent->new; print $ua->get('http://mojolicio.us') ->res->body; DOM Parsing /

    CSS Selectors
  10. my $ua = Mojo::UserAgent->new; print $ua->get('http://mojolicio.us') ->res->dom; DOM Parsing /

    CSS Selectors
  11. my $ua = Mojo::UserAgent->new; print $ua->get('http://mojolicio.us') ->res->dom-> nd('#introduction ul li:

    rst-child'); DOM Parsing / CSS Selectors
  12. my $ua = Mojo::UserAgent->new; print $ua->get('http://mojolicio.us') ->res->dom-> nd('#introduction ul li:

    rst-child'); <div id=”introduction”> <ul> <li> An amazing real-time web framework supporting a simpli ed single le mode through <a href="perldoc/Mojolicious/Lite">Mojolicious::Lite</a>. <blockquote> ... </li> <li> Very clean, portable and Object Oriented pure Perl API without any hidden magic and no requirements besides Perl 5.10.1 (although 5.12+ ... DOM Parsing / CSS Selectors
  13. my $ua = Mojo::UserAgent->new; print $ua->get('http://mojolicio.us') ->res->dom-> nd('#introduction ul li:

    rst-child'); DOM Parsing / CSS Selectors <div id=”introduction”> <ul> <li> An amazing real-time web framework supporting a simpli ed single le mode through <a href="perldoc/Mojolicious/Lite">Mojolicious::Lite</a>. <blockquote> ... </li> <li> Very clean, portable and Object Oriented pure Perl API without any hidden magic and no requirements besides Perl 5.10.1 (although 5.12+ ...
  14. my $ua = Mojo::UserAgent->new; print $ua->get('search.twitter.com/search.json?q=mojolicious') ->res->json; { "completed_in":0.04, "max_id":146706880847294465,

    "max_id_str":"146706880847294465", "page":1, "query":"mojocasts", "refresh_url":"?since_id=146706880847294465&q=mojocasts", "results":[ { "text":"If you're thinking about #Perl on the web, you should check out Mojolicious: http://t.co/Wisop9X0" "created_at":"Tue, 13 Dec 2011 21:43:40 +0000", "from_user":"reyjrar", ... JSON Parsing / JSON Pointers
  15. my $ua = Mojo::UserAgent->new; print $ua->get('search.twitter.com/search.json?q=mojolicious') ->res->json->{results}->[0]->{text}; { "completed_in":0.04, "max_id":146706880847294465,

    "max_id_str":"146706880847294465", "page":1, "query":"mojocasts", "refresh_url":"?since_id=146706880847294465&q=mojocasts", "results":[ { "text":"If you're thinking about #Perl on the web, you should check out Mojolicious: http://t.co/Wisop9X0" "created_at":"Tue, 13 Dec 2011 21:43:40 +0000", "from_user":"reyjrar", ... JSON Parsing / JSON Pointers
  16. my $ua = Mojo::UserAgent->new; print $ua->get('search.twitter.com/search.json?q=mojolicious') ->res->json->{results}->[0]->{text}; JSON Parsing /

    JSON Pointers { "completed_in":0.04, "max_id":146706880847294465, "max_id_str":"146706880847294465", "page":1, "query":"mojocasts", "refresh_url":"?since_id=146706880847294465&q=mojocasts", "results":[ { "text":"If you're thinking about #Perl on the web, you should check out Mojolicious: http://t.co/Wisop9X0" "created_at":"Tue, 13 Dec 2011 21:43:40 +0000", "from_user":"reyjrar", ...
  17. my $ua = Mojo::UserAgent->new; print $ua->get('search.twitter.com/search.json?q=mojolicious') ->res->json(‘/results/0/text’); JSON Parsing /

    JSON Pointers { "completed_in":0.04, "max_id":146706880847294465, "max_id_str":"146706880847294465", "page":1, "query":"mojocasts", "refresh_url":"?since_id=146706880847294465&q=mojocasts", "results":[ { "text":"If you're thinking about #Perl on the web, you should check out Mojolicious: http://t.co/Wisop9X0" "created_at":"Tue, 13 Dec 2011 21:43:40 +0000", "from_user":"reyjrar", ...
  18. Easy debugging my $ua = Mojo::UserAgent->new; print $ua->get('search.twitter.com/search.json?q=mojolicious') ->res->json(‘/results/0/text’);

  19. Easy debugging }; get '/' => sub { shift->render('home/index'); };

    get '/blogs' => sub { shift->redirect_to('/blogs/tag/personal'); }; get '/blogs/tag/(*tags)' => sub { my $self = shift; # Specified tags to search for: /tag/one/tag/two/tag/three my @tags = grep $_ ne 'tag' => split '/' => $self->param('tags'); my @blogs = $self->db->resultset('Blog')->by_tags(@tags) or return $self->redirect_to('/blogs'); $self->render('blogs/index', blogs => [@blogs],); }; get '/blogs/(:name)' => sub { my $self = shift; my $param = $self->stash('name'); my $blog = $self->db->resultset('Blog')->by_id_or_name($param) or return $self->redirect_to('/blogs'); $self->render('blogs/show', blog => $blog); }; get '/photos' => sub { my $self = shift; $self->render( 'photos/index', sets => [$self->db->resultset('Photoset')->search], photo_count => $self->db->resultset('Photo')->count ); }; get '/photos/:id' => (photoset => '') => sub { my $self = shift; my $id = $self->param('id'); my $set = $self->db->resultset('Photoset')->by_id_or_name($id) or $self->redirect_to("/photos"), return; $self->render(template => 'photos/show_set', set => $set); }; get '/photos/:id' => sub { my $self = shift; my $id = $self->param('id'); my $photo = $self->db->resultset('Photo')->find($id) or $self->redirect_to("/photos"), return; $self->render(template => 'photos/show', photo => $photo); }; app->start; __DATA__ @@ layouts/default.html.ep <!doctype html> <html> <head> <link rel="stylesheet" type="text/css" media="screen" href="/css/main.css" /> <script type="text/javascript"> var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-29402272-1']); _gaq.push(['_trackPageview']); (function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); </script> </head> <body> <div id="header"> <img class="profile" src="/images/bender_promo.png" /> <a class="home" href="/" alt="Home"></a> % if ($self->req->url ne '/') { <div class="fb_status"> <span class="load statii"></span> <span class="time_since"></span> <span class="source"></span> </div> % } <ul class="toc"> <li><a href="/blogs">Blog</a></li> <li><a href="/photos">Photos</a></li> </ul> </div> <div id="main"> <div id="box-top"></div> <div id="content"> <%= content %> </div> # Requested id is a photoset? app->routes->add_condition( photoset => sub { my ($r, $c, $captures, $pattern) = @_; my $id = $captures->{id}; return 1 if $id !~ /^\d+$/ or $id =~ /^\d+$/ and length $id == 17; } ); helper db => sub { return Schema->connect('dbi:SQLite:' . ($ENV{TEST_DB} || 'test.db')); }; get '/' => sub { shift->render('home/index'); }; get '/blogs' => sub { shift->redirect_to('/blogs/tag/personal'); }; get '/blogs/tag/(*tags)' => sub { my $self = shift; # Specified tags to search for: /tag/one/tag/two/tag/three my @tags = grep $_ ne 'tag' => split '/' => $self->param('tags'); my @blogs = $self->db->resultset('Blog')->by_tags(@tags) or return $self->redirect_to('/blogs'); $self->render('blogs/index', blogs => [@blogs],); }; get '/blogs/(:name)' => sub { my $self = shift; my $param = $self->stash('name'); my $blog = $self->db->resultset('Blog')->by_id_or_name($param) or return $self->redirect_to('/blogs'); $self->render('blogs/show', blog => $blog); }; get '/photos' => sub { my $self = shift; $self->render( 'photos/index', sets => [$self->db->resultset('Photoset')->search], photo_count => $self->db->resultset('Photo')->count ); };#!/usr/bin/env perl use Mojolicious::Lite; #!/usr/bin/env perl use Mojolicious::Lite; use lib 'lib'; use Schema; # Requested id is a photoset? app->routes->add_condition( photoset => sub { my ($r, $c, $captures, $pattern) = @_; my $id = $captures->{id}; return 1 if $id !~ /^\d+$/ or $id =~ /^\d+$/ and length $id == 17; } ); my $ua = Mojo::UserAgent->new; my $res = $ua->get('search.twitter.com/search.json?q=mojolicious') ->res->json(‘/results/0/text’); get '/' => sub { shift->render('home/index'); }; get '/blogs' => sub { shift->redirect_to('/blogs/tag/personal'); }; get '/blogs/tag/(*tags)' => sub { my $self = shift; # Specified tags to search for: /tag/one/tag/two/tag/three my @tags = grep $_ ne 'tag' => split '/' => $self->param('tags'); my @blogs = $self->db->resultset('Blog')->by_tags(@tags) or return $self->redirect_to('/blogs'); $self->render('blogs/index', blogs => [@blogs],); }; get '/blogs/(:name)' => sub { my $self = shift; my $param = $self->stash('name'); my $blog = $self->db->resultset('Blog')->by_id_or_name($param) or return $self->redirect_to('/blogs'); $self->render('blogs/show', blog => $blog); }; get '/photos' => sub { my $self = shift; get '/blogs/tag/(*tags)' => sub { my $self = shift; # Specified tags to search for: /tag/one/tag/two/tag/three my @tags = grep $_ ne 'tag' => split '/' => $self->param('tags'); my @blogs = $self->db->resultset('Blog')->by_tags(@tags) or return $self->redirect_to('/blogs'); $self->render('blogs/index', blogs => [@blogs],); }; get '/blogs/(:name)' => sub { my $self = shift; my $param = $self->stash('name'); my $blog = $self->db->resultset('Blog')->by_id_or_name($param) or return $self->redirect_to('/blogs'); $self->render('blogs/show', blog => $blog); }; get '/photos' => sub { my $self = shift; $self->render( 'photos/index', sets => [$self->db->resultset('Photoset')->search], photo_count => $self->db->resultset('Photo')->count ); }; get '/photos/:id' => (photoset => '') => sub { my $self = shift; my $id = $self->param('id'); my $set = $self->db->resultset('Photoset')->by_id_or_name($id) or $self->redirect_to("/photos"), return; $self->render(template => 'photos/show_set', set => $set); }; get '/photos/:id' => sub { my $self = shift; my $id = $self->param('id'); my $photo = $self->db->resultset('Photo')->find($id) or $self->redirect_to("/photos"), return; $self->render(template => 'photos/show', photo => $photo); }; app->start; __DATA__ @@ layouts/default.html.ep <!doctype html> <html> <head> <link rel="stylesheet" type="text/css" media="screen" href="/css/main.css" /> <script type="text/javascript"> var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-29402272-1']); _gaq.push(['_trackPageview']); (function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); </script> </head> <body> <div id="header"> <img class="profile" src="/images/bender_promo.png" /> <a class="home" href="/" alt="Home"></a> % if ($self->req->url ne '/') { <div class="fb_status"> <span class="load statii"></span> <span class="time_since"></span> <span class="source"></span> </div> % } <ul class="toc"> <li><a href="/blogs">Blog</a></li> <li><a href="/photos">Photos</a></li> </ul> </div> <div id="main"> <div id="box-top"></div> <div id="content"> <%= content %> </div> <div id="box-bottom"></div> </div> <div id="footer"> <ul> <li><a href="http://mojolicio.us">Mojolicious</a></li> <li><a href="http://mojocasts.com">Mojocasts</a></li> <li><a href="http://github.com/tempire/mojoexample">Mojo Example</a></li> <li><a href="http://github.com/kraih/mojo">Github</a></li> my $ua = Mojo::UserAgent->new; my $res = $ua->get('search.twitter.com/search.json?q=mojolicious') ->res->json(‘/results/0/text’); use Schema; # Requested id is a photoset? app->routes->add_condition( photoset => sub { my ($r, $c, $captures, $pattern) = @_; my $id = $captures->{id}; return 1 if $id !~ /^\d+$/ or $id =~ /^\d+$/ and length $id == 17; } ); helper db => sub { return Schema->connect('dbi:SQLite:' . ($ENV{TEST_DB} || 'test.db')); }; get '/' => sub { shift->render('home/index'); }; get '/blogs' => sub { shift->redirect_to('/blogs/tag/personal'); }; get '/blogs/tag/(*tags)' => sub { my $self = shift; # Specified tags to search for: /tag/one/tag/two/tag/three my @tags = grep $_ ne 'tag' => split '/' => $self->param('tags'); my @blogs = $self->db->resultset('Blog')->by_tags(@tags) or return $self->redirect_to('/blogs'); $self->render('blogs/index', blogs => [@blogs],); }; get '/blogs/(:name)' => sub { my $self = shift; my $param = $self->stash('name'); my $blog = $self->db->resultset('Blog')->by_id_or_name($param) or return $self->redirect_to('/blogs'); $self->render('blogs/show', blog => $blog); }; get '/photos' => sub { my $self = shift; $self->render( 'photos/index', sets => [$self->db->resultset('Photoset')->search], photo_count => $self->db->resultset('Photo')->count ); };#!/usr/bin/env perl use Mojolicious::Lite; #!/usr/bin/env perl use Mojolicious::Lite; use lib 'lib'; use Schema; # Requested id is a photoset? app->routes->add_condition( photoset => sub { my ($r, $c, $captures, $pattern) = @_; my $id = $captures->{id}; return 1 if $id !~ /^\d+$/ or $id =~ /^\d+$/ and length $id == 17; } ); helper db => sub { return Schema->connect('dbi:SQLite:' . ($ENV{TEST_DB} || 'test.db')); }; get '/' => sub { shift->render('home/index'); }; get '/blogs' => sub { shift->redirect_to('/blogs/tag/personal'); }; get '/blogs/tag/(*tags)' => sub { my $self = shift; # Specified tags to search for: /tag/one/tag/two/tag/three my @tags = grep $_ ne 'tag' => split '/' => $self->param('tags'); my @blogs = $self->db->resultset('Blog')->by_tags(@tags) or return $self->redirect_to('/blogs'); $self->render('blogs/index', blogs => [@blogs],); }; get '/blogs/(:name)' => sub { my $self = shift; my $param = $self->stash('name'); my $blog = $self->db->resultset('Blog')->by_id_or_name($param) or return $self->redirect_to('/blogs'); $self->render('blogs/show', blog => $blog); }; get '/photos' => sub { my $self = shift;
  20. Easy debugging }; get '/' => sub { shift->render('home/index'); };

    get '/blogs' => sub { shift->redirect_to('/blogs/tag/personal'); }; get '/blogs/tag/(*tags)' => sub { my $self = shift; # Specified tags to search for: /tag/one/tag/two/tag/three my @tags = grep $_ ne 'tag' => split '/' => $self->param('tags'); my @blogs = $self->db->resultset('Blog')->by_tags(@tags) or return $self->redirect_to('/blogs'); $self->render('blogs/index', blogs => [@blogs],); }; get '/blogs/(:name)' => sub { my $self = shift; my $param = $self->stash('name'); my $blog = $self->db->resultset('Blog')->by_id_or_name($param) or return $self->redirect_to('/blogs'); $self->render('blogs/show', blog => $blog); }; get '/photos' => sub { my $self = shift; $self->render( 'photos/index', sets => [$self->db->resultset('Photoset')->search], photo_count => $self->db->resultset('Photo')->count ); }; get '/photos/:id' => (photoset => '') => sub { my $self = shift; my $id = $self->param('id'); my $set = $self->db->resultset('Photoset')->by_id_or_name($id) or $self->redirect_to("/photos"), return; $self->render(template => 'photos/show_set', set => $set); }; get '/photos/:id' => sub { my $self = shift; my $id = $self->param('id'); my $photo = $self->db->resultset('Photo')->find($id) or $self->redirect_to("/photos"), return; $self->render(template => 'photos/show', photo => $photo); }; app->start; __DATA__ @@ layouts/default.html.ep <!doctype html> <html> <head> <link rel="stylesheet" type="text/css" media="screen" href="/css/main.css" /> <script type="text/javascript"> var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-29402272-1']); _gaq.push(['_trackPageview']); (function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); </script> </head> <body> <div id="header"> <img class="profile" src="/images/bender_promo.png" /> <a class="home" href="/" alt="Home"></a> % if ($self->req->url ne '/') { <div class="fb_status"> <span class="load statii"></span> <span class="time_since"></span> <span class="source"></span> </div> % } <ul class="toc"> <li><a href="/blogs">Blog</a></li> <li><a href="/photos">Photos</a></li> </ul> </div> <div id="main"> <div id="box-top"></div> <div id="content"> <%= content %> </div> # Requested id is a photoset? app->routes->add_condition( photoset => sub { my ($r, $c, $captures, $pattern) = @_; my $id = $captures->{id}; return 1 if $id !~ /^\d+$/ or $id =~ /^\d+$/ and length $id == 17; } ); helper db => sub { return Schema->connect('dbi:SQLite:' . ($ENV{TEST_DB} || 'test.db')); }; get '/' => sub { shift->render('home/index'); }; get '/blogs' => sub { shift->redirect_to('/blogs/tag/personal'); }; get '/blogs/tag/(*tags)' => sub { my $self = shift; # Specified tags to search for: /tag/one/tag/two/tag/three my @tags = grep $_ ne 'tag' => split '/' => $self->param('tags'); my @blogs = $self->db->resultset('Blog')->by_tags(@tags) or return $self->redirect_to('/blogs'); $self->render('blogs/index', blogs => [@blogs],); }; get '/blogs/(:name)' => sub { my $self = shift; my $param = $self->stash('name'); my $blog = $self->db->resultset('Blog')->by_id_or_name($param) or return $self->redirect_to('/blogs'); $self->render('blogs/show', blog => $blog); }; get '/photos' => sub { my $self = shift; $self->render( 'photos/index', sets => [$self->db->resultset('Photoset')->search], photo_count => $self->db->resultset('Photo')->count ); };#!/usr/bin/env perl use Mojolicious::Lite; #!/usr/bin/env perl use Mojolicious::Lite; use lib 'lib'; use Schema; # Requested id is a photoset? app->routes->add_condition( photoset => sub { my ($r, $c, $captures, $pattern) = @_; my $id = $captures->{id}; return 1 if $id !~ /^\d+$/ or $id =~ /^\d+$/ and length $id == 17; } ); my $ua = Mojo::UserAgent->new; my $res = $ua->get('search.twitter.com/search.json?q=mojolicious') ->res->json(‘/results/0/text’); get '/' => sub { shift->render('home/index'); }; get '/blogs' => sub { shift->redirect_to('/blogs/tag/personal'); }; get '/blogs/tag/(*tags)' => sub { my $self = shift; # Specified tags to search for: /tag/one/tag/two/tag/three my @tags = grep $_ ne 'tag' => split '/' => $self->param('tags'); my @blogs = $self->db->resultset('Blog')->by_tags(@tags) or return $self->redirect_to('/blogs'); $self->render('blogs/index', blogs => [@blogs],); }; get '/blogs/(:name)' => sub { my $self = shift; my $param = $self->stash('name'); my $blog = $self->db->resultset('Blog')->by_id_or_name($param) or return $self->redirect_to('/blogs'); $self->render('blogs/show', blog => $blog); }; get '/photos' => sub { my $self = shift; get '/blogs/tag/(*tags)' => sub { my $self = shift; # Specified tags to search for: /tag/one/tag/two/tag/three my @tags = grep $_ ne 'tag' => split '/' => $self->param('tags'); my @blogs = $self->db->resultset('Blog')->by_tags(@tags) or return $self->redirect_to('/blogs'); $self->render('blogs/index', blogs => [@blogs],); }; get '/blogs/(:name)' => sub { my $self = shift; my $param = $self->stash('name'); my $blog = $self->db->resultset('Blog')->by_id_or_name($param) or return $self->redirect_to('/blogs'); $self->render('blogs/show', blog => $blog); }; get '/photos' => sub { my $self = shift; $self->render( 'photos/index', sets => [$self->db->resultset('Photoset')->search], photo_count => $self->db->resultset('Photo')->count ); }; get '/photos/:id' => (photoset => '') => sub { my $self = shift; my $id = $self->param('id'); my $set = $self->db->resultset('Photoset')->by_id_or_name($id) or $self->redirect_to("/photos"), return; $self->render(template => 'photos/show_set', set => $set); }; get '/photos/:id' => sub { my $self = shift; my $id = $self->param('id'); my $photo = $self->db->resultset('Photo')->find($id) or $self->redirect_to("/photos"), return; $self->render(template => 'photos/show', photo => $photo); }; app->start; __DATA__ @@ layouts/default.html.ep <!doctype html> <html> <head> <link rel="stylesheet" type="text/css" media="screen" href="/css/main.css" /> <script type="text/javascript"> var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-29402272-1']); _gaq.push(['_trackPageview']); (function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); </script> </head> <body> <div id="header"> <img class="profile" src="/images/bender_promo.png" /> <a class="home" href="/" alt="Home"></a> % if ($self->req->url ne '/') { <div class="fb_status"> <span class="load statii"></span> <span class="time_since"></span> <span class="source"></span> </div> % } <ul class="toc"> <li><a href="/blogs">Blog</a></li> <li><a href="/photos">Photos</a></li> </ul> </div> <div id="main"> <div id="box-top"></div> <div id="content"> <%= content %> </div> <div id="box-bottom"></div> </div> <div id="footer"> <ul> <li><a href="http://mojolicio.us">Mojolicious</a></li> <li><a href="http://mojocasts.com">Mojocasts</a></li> <li><a href="http://github.com/tempire/mojoexample">Mojo Example</a></li> <li><a href="http://github.com/kraih/mojo">Github</a></li> use Schema; # Requested id is a photoset? app->routes->add_condition( photoset => sub { my ($r, $c, $captures, $pattern) = @_; my $id = $captures->{id}; return 1 if $id !~ /^\d+$/ or $id =~ /^\d+$/ and length $id == 17; } ); helper db => sub { return Schema->connect('dbi:SQLite:' . ($ENV{TEST_DB} || 'test.db')); }; get '/' => sub { shift->render('home/index'); }; get '/blogs' => sub { shift->redirect_to('/blogs/tag/personal'); }; get '/blogs/tag/(*tags)' => sub { my $self = shift; # Specified tags to search for: /tag/one/tag/two/tag/three my @tags = grep $_ ne 'tag' => split '/' => $self->param('tags'); my @blogs = $self->db->resultset('Blog')->by_tags(@tags) or return $self->redirect_to('/blogs'); $self->render('blogs/index', blogs => [@blogs],); }; get '/blogs/(:name)' => sub { my $self = shift; my $param = $self->stash('name'); my $blog = $self->db->resultset('Blog')->by_id_or_name($param) or return $self->redirect_to('/blogs'); $self->render('blogs/show', blog => $blog); }; get '/photos' => sub { my $self = shift; $self->render( 'photos/index', sets => [$self->db->resultset('Photoset')->search], photo_count => $self->db->resultset('Photo')->count ); };#!/usr/bin/env perl use Mojolicious::Lite; #!/usr/bin/env perl use Mojolicious::Lite; use lib 'lib'; use Schema; # Requested id is a photoset? app->routes->add_condition( photoset => sub { my ($r, $c, $captures, $pattern) = @_; my $id = $captures->{id}; return 1 if $id !~ /^\d+$/ or $id =~ /^\d+$/ and length $id == 17; } ); helper db => sub { return Schema->connect('dbi:SQLite:' . ($ENV{TEST_DB} || 'test.db')); }; get '/' => sub { shift->render('home/index'); }; get '/blogs' => sub { shift->redirect_to('/blogs/tag/personal'); }; get '/blogs/tag/(*tags)' => sub { my $self = shift; # Specified tags to search for: /tag/one/tag/two/tag/three my @tags = grep $_ ne 'tag' => split '/' => $self->param('tags'); my @blogs = $self->db->resultset('Blog')->by_tags(@tags) or return $self->redirect_to('/blogs'); $self->render('blogs/index', blogs => [@blogs],); }; get '/blogs/(:name)' => sub { my $self = shift; my $param = $self->stash('name'); my $blog = $self->db->resultset('Blog')->by_id_or_name($param) or return $self->redirect_to('/blogs'); $self->render('blogs/show', blog => $blog); }; get '/photos' => sub { my $self = shift; $ perl useragent_script.pl
  21. Easy debugging $ MOJO_USERAGENT_DEBUG=1 perl useragent_script.pl -- Blocking request (http://mojolicio.us)

    -- Connect (http:mojolicio.us:80) -- Client >>> Server (http://mojolicio.us) GET / HTTP/1.1 User-Agent: Mojolicious (Perl) Connection: keep-alive Content-Length: 0 Host: mojolicio.us -- Client <<< Server (http://mojolicio.us) HTTP/1.1 200 OK Connection: keep-alive Content-Type: text/html;charset=UTF-8 X-Powered-By: Mojolicious (Perl) Date: Tue, 29 May 2012 06:11:18 GMT Content-Length: 10101 Server: Mojolicious (Perl) { "completed_in":0.04, "max_id":146706880847294465, "max_id_str":"146706880847294465", "page":1, "query":"mojocasts", "refresh_url":"?since_id=146706880847294465&q=mojocasts", "results":[ { "text":"If you're thinking about #Perl on the web, ... Request Response
  22. Moar websocket requests non-blocking concurrent requests ipv6 requests SSL authentication

    events collections
  23. server

  24. Routes get$‘/’

  25. Routes get$‘/’

  26. Routes get$‘/:placeholder’ get$‘/’

  27. Routes get$‘/:placeholder’ get$‘/’ Stores$“hello”$in$placeholder

  28. Routes get$‘/:placeholder’ get$‘/’ get$‘/#relaxed’

  29. Routes get$‘/:placeholder’ Stores$“url.with.dots”$in$relaxed get$‘/’ get$‘/#relaxed’

  30. Routes get$‘/:placeholder’ get$‘/’ get$‘/*wildcard’ get$‘/#relaxed’

  31. Routes get$‘/:placeholder’ Stores$“captures/slashes”$in$wildcard get$‘/’ get$‘/*wildcard’ get$‘/#relaxed’

  32. Routes get$‘/:normal’ get$‘/’ get$‘/*wildcard’ get$‘/#relaxed’

  33. server Mojolicious::Lite

  34. server Mojolicious::Lite One le

  35. server Mojolicious::Lite One le Getting started

  36. server Mojolicious::Lite One le Getting started Prototype apps

  37. server Mojolicious::Lite One le Getting started All the functionality of

    Mojolicious Prototype apps
  38. Mojolicious::Lite $$mojo$generate$lite_app$

  39. Mojolicious::Lite $$mojo$generate$lite_app$hello

  40. Mojolicious::Lite use$Mojolicious::Lite;

  41. Mojolicious::Lite get$‘/’ use$Mojolicious::Lite; $$$$$$$$=>$sub${ $$my$$self$=$shift; $$$selfJ>render(text$=>$‘Hello!’); };

  42. Mojolicious::Lite get$‘/’$=>$sub${ $$my$$self$=$shift; $$$selfJ>render(text$=>$‘Hello!’); }; appJ>start; use$Mojolicious::Lite;

  43. Mojolicious::Lite $$morbo$hello$ [Wed%May%30%12:58:51%2012]%[info]%Listening%at%"http://*:3000". Server%available%at%http://127.0.0.1:3000.

  44. Mojolicious::Lite $$morbo$hello$ [Wed%May%30%12:58:51%2012]%[info]%Listening%at%"http://*:3000". Server%available%at%http://127.0.0.1:3000. http://127.0.0.1:3000

  45. Mojolicious::Lite get$‘/’$=>$sub${ $$my$$self$=$shift; $$$selfJ>render(text$=>$‘Hello!’); }; appJ>start; use$Mojolicious::Lite;

  46. Mojolicious::Lite get$‘/’$=>$sub${ $$my$$self$=$shift; $$$selfJ>render(‘mytemplate’); }; appJ>start; use$Mojolicious::Lite;

  47. Mojolicious::Lite get$‘/’$=>$sub${ $$my$$self$=$shift; $$$selfJ>render(‘mytemplate’); }; appJ>start; use$Mojolicious::Lite; __DATA__ @@$mytemplate.html.ep Hello!

  48. Mojolicious::Lite get$‘/:myplaceholder’$=>$sub${ $$my$$self$=$shift; $$$selfJ>render(‘mytemplate’); }; appJ>start; use$Mojolicious::Lite; __DATA__ @@$mytemplate.html.ep Hello!

  49. Mojolicious::Lite get$‘/:myplaceholder’$=>$sub${ $$my$$self$=$shift; $$$selfJ>render(‘mytemplate’); }; appJ>start; use$Mojolicious::Lite; __DATA__ @@$mytemplate.html.ep Hello$<%=$myplaceholder%>!

  50. __DATA__ @@$mytemplate.html.ep Hello$<%=$myplaceholder%>! Mojolicious::Lite get$‘/:myplaceholder’$=>$sub${ $$my$$self$=$shift; $$$selfJ>render(‘mytemplate’); }; appJ>start; use$Mojolicious::Lite;

    YAPC YAPC
  51. __DATA__ @@$mytemplate.html.ep Hello$<%=$myplaceholder%>! Mojolicious::Lite get$‘/:myplaceholder’$=>$sub${ $$my$$self$=$shift; $$$selfJ>render(‘mytemplate’); }; appJ>start; use$Mojolicious::Lite;

    whatever YAPC whatever
  52. Mojolicious::Lite get$‘/:myplaceholder’$=>$sub${ $$my$$self$=$shift; $$$selfJ>render(‘mytemplate’); }; appJ>start; use$Mojolicious::Lite; __DATA__ @@$mytemplate.html.ep Hello$<%=$$myplaceholder$%>!

    YAPC YAPC
  53. Mojolicious::Lite __DATA__ @@$mytemplate.html.ep Hello$<%=$myplaceholder%>! get$‘/:myplaceholder’$=>$sub${ $$my$$self$=$shift; $$$selfJ>render(‘mytemplate’); }; appJ>start; use$Mojolicious::Lite;

    YAPC ?year=2012 YAPC
  54. Mojolicious::Lite get$‘/:myplaceholder’$=>$sub${ $$my$$self$=$shift; $$$selfJ>render(‘mytemplate’); }; appJ>start; use$Mojolicious::Lite; __DATA__ @@$mytemplate.html.ep Hello$<%=$myplaceholder%>

    YAPC YAPC ?year=2012 2012! <%=param$‘year’%>!
  55. Mojolicious::Lite get$‘/:myplaceholder’$=>$sub${ $$my$$self$=$shift; $$$selfJ>render(‘mytemplate’); }; appJ>start; use$Mojolicious::Lite; __DATA__ @@$mytemplate.html.ep Hello$<%=$myplaceholder%>

    <%=param$‘year’%>!
  56. EP Templates %$for$(@$foo)${ $$$$... %$} %=$$avariable;

  57. EP Templates %$for$(@$foo)${ $$$$... %$} %=$$avariable; <%$for$(@$foo)${$%> $$$... <%$}$%> <%=$$avariable$%>

  58. EP Templates %$for$(@$foo)${ $$$$... %$} %=$$avariable; <div><%$for$(@$foo)${$%> $$$... <%$}$%></div> <li><%=$$avariable$%></li>

  59. EP Templates <!DOCTYPE$html> <html> <head>...</head> <body> </body> </html>

  60. EP Templates <!DOCTYPE$html> <html> <head>...</head> <body> <ul> %$for$(@$foo)${ %$} </ul>

    </body> </html>
  61. EP Templates <!DOCTYPE$html> <html> <head>...</head> <body> <ul> %$for$(@$foo)${ $$<li><%=$$_$%></li> %$}

    </ul> </body> </html>
  62. Tag Helpers <input$type=”submit”$value=”Go!”$/> %=$submit_button$‘Go!’

  63. Tag Helpers <input$type=”submit”$value=”Go!”$/> %=$submit_button$‘Go!’ <select$name=”language”> $$<option$value=”de”>de</option> $$<option$value=”en”>en</option> $$... </select> %=$select_field$language$=>$[qw/$de$en$/];

  64. Tag Helpers <li><%=$$_$%></li> <input$type=”submit”$value=”Go!”$/> %=$submit_button$‘Go!’ <select$name=”language”> $$<option$value=”de”>de</option> $$<option$value=”en”>en</option> $$... </select>

    %=$select_field$language$=>$[qw/$de$en$/];
  65. Tag Helpers %=$t$li$=>$$_ <li><%=$$_$%></li> <input$type=”submit”$value=”Go!”$/> %=$submit_button$‘Go!’ <select$name=”language”> $$<option$value=”de”>de</option> $$<option$value=”en”>en</option> $$...

    </select> %=$select_field$language$=>$[qw/$de$en$/];
  66. Tag Helpers %= t ul => id => blogs =>

    begin % for my $blog (@$blogs) { %= t li => id => $blog->url_title, class => blog => begin %= t a => name => 'id' . $blog->id %= t a => name => $blog->url_title %= t h2 => begin %= link_to $blog->title => ‘/blogs/’ . $blog->url_title), class => 'more' % end % end % } % end
  67. Tag Helpers %= t ul => id => blogs =>

    begin % for my $blog (@$blogs) { %= t li => id => $blog->url_title, class => blog => begin %= t h2 => begin %= link_to $blog->title => ‘/blogs/’ . $blog->url_title), class => 'more' % end % end % } % end <a name=”id<%= $blog->id %>” /> <a name=”<%= $blog->url_title %>” />
  68. Helpers stash config content content_for extends flash include layout memorize

    session param title url_for url_with javascript link_to form_for stylesheet hidden_field radio_button password_field check_box ...
  69. Helpers helper$dumper$=>$sub${ $$return$Data::DumperJ>new([@_])J>Dump }; %=$dumper$$foo

  70. Helpers $selfJ>dumper($foo); helper$dumper$=>$sub${ $$return$Data::DumperJ>new([@_])J>Dump }; %=$dumper$$foo

  71. Helpers $selfJ>dumper($foo); get$‘/’$=>$sub${ $$my$$self$=$shift; $$$selfJ>render_text($$$$$$$$$$$$$$$$$$$); }; helper$dumper$=>$sub${ $$return$Data::DumperJ>new([@_])J>Dump }; %=$dumper$$foo

  72. get$‘/’$=>$sub${ $$my$$self$=$shift; $$$selfJ>render_text($$$$$$$$$$$$$$$$$$$); }; $$$$$$$$$$$$$$$$$$$$$$selfJ>dumper($foo) Helpers $selfJ>dumper($foo); helper$dumper$=>$sub${ $$return$Data::DumperJ>new([@_])J>Dump };

    %=$dumper$$foo $selfJ>dumper($foo);
  73. Plugins

  74. Plugins $$cpanm$Mojolicious::Plugin::ZombieApocalypse ~

  75. $$cpanm$Mojolicious::Plugin:: ~ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$ZombieApocalypse ZombieApocalypse Plugins plugin$‘ZombieApocalypse’;

  76. get$‘/’$=>$sub${ $$my$$self$=$shift; $$$selfJ>render(text$=>$‘Hello!’); }; Plugins appJ>start; use$Mojolicious::Lite; plugin$‘ZombieApocalypse’;

  77. server Mojolicious

  78. server Mojolicious Spread among several les

  79. server Mojolicious Spread among several les Teams

  80. server Mojolicious Spread among several les Teams Larger apps

  81. Full App %%[exist]%/Users/glen/my_app/script %%[write]%/Users/glen/my_app/script/my_app %%[chmod]%my_app/script/my_app%744 %%[exist]%/Users/glen/my_app/lib %%[write]%/Users/glen/my_app/lib/MyApp.pm %%[exist]%/Users/glen/my_app/lib/MyApp %%[write]%/Users/glen/my_app/lib/MyApp/Example.pm %%[exist]%/Users/glen/my_app/t

    %%[write]%/Users/glen/my_app/t/basic.t %%[exist]%/Users/glen/my_app/log %%[exist]%/Users/glen/my_app/public %%[write]%/Users/glen/my_app/public/index.html %%[exist]%/Users/glen/my_app/templates/layouts %%[write]%/Users/glen/my_app/templates/layouts/default.html.ep %%[exist]%/Users/glen/my_app/templates/example %%[write]%/Users/glen/my_app/templates/example/welcome.html.ep $$mojo$generate$app$MyApp
  82. Full App Executables $$mojo$generate$app$MyApp App%Code Tests Logs Public Templates %%[exist]%/Users/glen/my_app/script

    %%[write]%/Users/glen/my_app/script/my_app %%[chmod]%my_app/script/my_app%744 %%[exist]%/Users/glen/my_app/lib %%[write]%/Users/glen/my_app/lib/MyApp.pm %%[exist]%/Users/glen/my_app/lib/MyApp %%[write]%/Users/glen/my_app/lib/MyApp/Example.pm %%[exist]%/Users/glen/my_app/t %%[write]%/Users/glen/my_app/t/basic.t %%[exist]%/Users/glen/my_app/log %%[exist]%/Users/glen/my_app/public %%[write]%/Users/glen/my_app/public/index.html %%[exist]%/Users/glen/my_app/templates/layouts %%[write]%/Users/glen/my_app/templates/layouts/default.html.ep %%[exist]%/Users/glen/my_app/templates/example %%[write]%/Users/glen/my_app/templates/example/welcome.html.ep
  83. Mojolicious Mojolicious::Lite

  84. Command Line ojo )

  85. Command Line perl -Mojo -E ‘a(“/” => {text => “Hello!”})->start’

    daemon get$‘/’$=>$sub${ $$my$$self$=$shift; $$$selfJ>render(text$=>$“Hello!”); }; appJ>start; use$Mojolicious::Lite;
  86. None
  87. websockets event-driven user agent embedded apps hot code reloading non-blocking

    parallel requests concurrent requests restful routes IPv6 PSGI support JSON parsing DOM parsing CSS3 selectors Multipart support SSL certi cate auth Tag Helpers
  88. websockets event-driven user agent embedded apps hot code reloading non-blocking

    parallel requests concurrent requests restful routes IPv6 PSGI support JSON parsing DOM parsing CSS3 selectors Multipart support SSL certi cate auth Tag Helpers Code < 10k lines
  89. Code ~ 17k lines websockets event-driven user agent embedded apps

    hot code reloading non-blocking parallel requests concurrent requests restful routes IPv6 PSGI support JSON parsing DOM parsing CSS3 selectors Multipart support SSL certi cate auth < 10k lines Tests ~ 9k unit tests Tag Helpers
  90. ~%cpanm%Mojolicious Code < 10k lines Tests ~ 9k unit tests

    cpanm Mojolicious websockets event-driven user agent embedded apps hot code reloading non-blocking parallel requests concurrent requests restful routes IPv6 PSGI support JSON parsing DOM parsing CSS3 selectors Multipart support SSL certi cate auth Code < 10k lines ~ 17k lines Tests ~ 9k unit tests Tag Helpers
  91. Code ~ 17k lines < 10k lines Tests ~ 9k

    unit tests ~%cpanm%Mojolicious TT>%Working%on%Mojolicious Fetching%http://www.cpan.org/authors/id/S/SR/SRI/MojoliciousT2.97.tar.gz% Configuring%MojoliciousT2.97%...%OK Building%and%testing%MojoliciousT2.97%... %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OK Successfully%installed%MojoliciousT2.97 1%distribution%installed %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OK ~
  92. Code ~ 17k lines < 10k lines Tests ~ 9k

    unit tests TT>%Working%on%Mojolicious Fetching%http://www.cpan.org/authors/id/S/SR/SRI/MojoliciousT2.97.tar.gz% Configuring%MojoliciousT2.97%...%OK Building%and%testing%MojoliciousT2.97%...%OK Successfully%installed%MojoliciousT2.97 1%distribution%installed ~ %%HARNESS_OPTIONS=j9 %%cpanm%Mojolicious 00:00:50;22 ~
  93. Code ~ 17k lines < 10k lines Tests ~ 9k

    unit tests ~%HARNESS_OPTIONS=j9%cpanm%Mojolicious TT>%Working%on%Mojolicious Fetching%http://www.cpan.org/authors/id/S/SR/SRI/MojoliciousT2.97.tar.gz% Configuring%MojoliciousT2.97%...%OK Building%and%testing%MojoliciousT2.97%...%OK Successfully%installed%MojoliciousT2.97 1%distribution%installed 00:00:15;41 00:00:50;22
  94. server Deployment

  95. server Deployment PSGI CGI Daemon

  96. Daemon

  97. Daemon Morbo Development Server

  98. Morbo Development Server Hypnotoad Hot-code reloading production server Daemon

  99. $$hypnotoad hello Hot-code reloading production server Hypnotoad

  100. $$hypnotoad$hello Run$&$Reload: Hot-code reloading production server Hypnotoad

  101. $$hypnotoad$hello Run$&$Reload: Stop: $$hypnotoad$Js$hello Hot-code reloading production server Hypnotoad

  102. None
  103. None
  104. server Deployment

  105. None
  106. $$cpanm$Mojolicious::Command::deploy::heroku $

  107. $$cpanm$Mojolicious::Command::deploy::heroku $ ✔

  108. $$./hello$deploy$heroku$JJcreate $ $$mojo$generate$lite_app$hello

  109. $$./hello$deploy$heroku $ $$mojo$generate$lite_app$hello $Jn$hellomojo

  110. 1

  111. 1 2 $$cpanm$Mojolicious::Command::deploy::heroku

  112. 1 2 $$cpanm$Mojolicious::Command::deploy::heroku mojo$generate$lite_app$hello 3

  113. 1 2 $$cpanm$Mojolicious::Command::deploy::heroku hello$deploy$heroku 4 mojo$generate$lite_app$hello 3

  114. what’s next?

  115. mojocasts.com

  116. mojocasts.com

  117. mojocasts.com

  118. mojocasts.com aggregation of links that are not collected anywhere else.

    it’s good a good place to know what the latest links are in the mojolicious community.
  119. Mojolicious Guides

  120. Mojo Example http://git.io/mojoexample lite app

  121. Mojo Example lite app full app http://git.io/mojoexample

  122. Mojo Example Live on Heroku http://git.io/mojoexample

  123. irc://irc.perl.org/#mojo mailing list mojolicious@googlegroups.com irc

  124. irc://irc.perl.org/#mojo mailing list mojolicious@googlegroups.com irc :D

  125. http://mojocasts.com/yapc resources

  126. @tempire http://tempi.re Glen Hinkle

  127. mojolicious next-generation real-time web framework intro to @tempire http://tempi.re/ Glen

    Hinkle http://mojocasts.com/yapc resources