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. 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
  2. 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
  3. 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
  4. 7.
  5. 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
  6. 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+ ...
  7. 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
  8. 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
  9. 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", ...
  10. 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", ...
  11. 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;
  12. 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
  13. 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
  14. 23.
  15. 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
  16. 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 %>” />
  17. 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 ...
  18. 73.
  19. 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
  20. 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
  21. 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;
  22. 86.
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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 ~
  28. 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 ~
  29. 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
  30. 96.
  31. 102.
  32. 103.
  33. 105.
  34. 110.

    1

  35. 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.