Overview of the Mojolicious Toolkit

by Pete 'Zoffix Znet' Evstratov

This presentation is available at TPM2015.zoffix.com

Question Policy

If you did not understand what I said or what I'm talking about, simply raise your hand.

This presentation is available at TPM2015.zoffix.com

It's not just a Web Framework

  • Mojo::Collection—Item collection
  • Mojo::DOM—HTML Parser
  • Mojo::URL—Uniform Resource Locator
  • Mojo::UserAgent—Non-blocking UA
  • Mojo::Util - Portable utility functions
  • Mojo::ByteStream—ByteStream
  • Mojo::Base—Minimal base class
  • ...and several more

All obtained by cpanm Mojolicious
(https://metacpan.org/release/Mojolicious)

Mojo::Collection

A collection of "things".

You can think of it as an "array object":
regular Perl array, but with methods

1:  my $c = Mojo::Collection->new(qw/foo bar baz baz boor ber/);
2:
3:  $c->grep(qr/^b/)->join(', ')->say;       # prints "bar, baz, baz, boor, ber"
4:  say join ',', grep /^b/, @$c;            # same thing
5:                                           # (note, we used $c as arrayref)
6:
7:  $c->grep(qr/^b/)->uniq->join(', ')->say; # prints "bar, baz, boor, ber"
8:
9:  use List::MoreUtils qw/uniq/;
10: say join ',', uniq grep /^b/, @$c;       # same thing

Mojo::Collection

List::Util, List::MoreUtils, List::UtilBy (or was it List::UtilsBy)

Mojo::Collection provides many of those functions as methods: compact, each, first, flatten, grep, join, last, map, reduce, reverse, slice, shuffle, size, sort, uniq

Mojo::DOM

"The BEST HTML Parser on CPAN"

Reference elements you want with CSS Selectors:

1:   my $dom = Mojo::DOM->new( $html_code );
2:   say $dom->at('title')->all_text; # print the <title> of the page
3:
4:   say for map $_->{href}, $dom->find('a[href]')->each; # print all links
5:
6:   # Put a <div> around each <p>
7:   $dom->find('p')->each(sub{ $_->wrap('<div class="panel-body"/>') });
8:   say $dom;
9:
10:  $dom->find('td.name')->map('all_text')->grep(qr/^B/)->uniq->join(', ')->say;

Mojo::DOM

1:   $dom->find('td.name')  # Returns Mojo::Collection of Mojo::DOM objects
2:      ->map('all_text')   # Returns Mojo::Collection of plain text items
3:      ->grep(qr/^B/)      # ---"---
4:      ->uniq              # ---"---
5:      ->join(', ')        # Returns Mojo::ByteStream
6:      ->say;

Mojo::DOM

Many methods to manipulate the DOM. Many return either Mojo::DOM or Mojo::Collection objects: all_text, ancestors, append, append_content, at, attr, child_nodes, children, content, descendant_nodes, find, following, following_nodes, matches, namespace, next, next_node, parent, parse, preceding, preceding_nodes, prepend, prepend_content, previous, previous_node, remove, replace, root, strip, tag, tap, text, to_string, tree, type, val, wrap, wrap_content, xml

Mojo::UserAgent

"Non-blocking I/O HTTP and WebSocket user agent"

 1:    my $ua = Mojo::UserAgent->new;
 2:
 3:    # Regular, blocking GET
 4:    $ua->get('mojolicio.us');
 5:
 6:    # Custom "Accept" header
 7:    $ua->get( 'http://api.travis-ci.org'
 8:        => { Accept => 'application/vnd.travis-ci.2+json' }
 9:    );
10:
11:    # Blocking POST a form
12:    $ua->post( 'https://metacpan.org/search' => form => { q => 'mojo' } );
13:
14:    # JSON POST (application/json)
15:    $ua->post('https://example.com' => json => {top => 'secret'});

Mojo::UserAgent

Error checking:

 1:    my $tx = $ua->post(
 2:        'https://metacpan.org/search' => form => { q => 'mojo' }
 3:    ); # returns Mojo::Transaction::HTTP object
 4:
 5:    # Error checking
 6:    if ( $tx->success ) { say $tx->res->body }
 7:    else {
 8:        my $err = $tx->error;
 9:        die "$err->{code} response: $err->{message}" if $err->{code};
10:        die "Connection error: $err->{message}";
11:    }

Mojo::UserAgent

Error checking (sometimes this is OK):

 1:    say $ua->get('mojolicidsdso.us')->res->dom->at('title')->text;
 2:
 3:    my $title = eval {
 4:        $ua->get('mojolicidsdso.us')->res->dom->at('title')->text;
 5:    } or die "Huston, we've got a problem!\n";

P.S.: be sure to install IO::Socket::SSL

Mojo::UserAgent

Let's look at return values:

 1:    $ua->get('http://example.com/')->res->dom->find('td.name')
 2:        ->map('all_text')->grep(qr/^B/)->uniq->join(', ')->say

Mojo::UserAgent

Let's look at return values:

 1:    $ua->get('http://example.com/')  # returns Mojo::Transaction::HTTP object
 2:        ->res                        # returns Mojo::Message::Response object
 3:        ->dom                        # returns Mojo::DOM object
 4:        ->find('td.name')            # returns Mojo::Collection of Mojo::DOMs
 5:        ->map('all_text')            # Same as earlier: Manipulate
 6:        ->grep(qr/^B/)               # Mojo::Collection. Then transform it to
 7:        ->uniq->join(', ')->say      # Mojo::ByteStream and print

Mojo::UserAgent

How about JSON? Just call ->json instead of ->dom!

 1:    say "Buy: $_"
 2:        for $ua->get('http://api.example.com/')->res->json->{products}->@*;

Mojo::UserAgent

How about JSON? Just call ->json instead of ->dom!

 1:    say "Buy: $_"
 2:        for $ua->get('http://api.example.com/') # Mojo::Transaction::HTTP
 3:            ->res                               # Mojo::Message::Response
 4:            ->json                              # decode JSON
 5:            ->{products}                        # Hashref->{products}
 6:            ->@*                                # Postfix dereference

Pop Quiz Time!

Read a complete file into a variable?

 1:    # Eww
 2:    open my $fh, '<', 'file.txt' or die $!;
 3:    my $data = do { local $/; <$fh>; };
 4:
 5:    # Nope, this module is broken:
 6:    use File::Slurp;
 7:    my $data = read_file 'file.txt';
 8:
 9:    # Better...
10:    use Path::Tiny;
11:    my $data = path('file.txt')->slurp;

Dump a structure while debugging?

 1:    # This
 2:    use Data::Dumper;
 3:    say Dumper $data;
 4:
 5:    # Or this
 6:    use Data::Dump;
 7:    dd $data;
 8:
 9:    # Or this
10:    use Acme::Dump::And::Dumper;
11:    say DnD $data;

Trim whitespace at start/end

 1:    $string =~ s/^\s+|\s+$//g;
 2:
 3:    # Or
 4:    $string =~ s/^\s+//;
 5:    $string =~ s/\s+$//;

Escaping stuff

 1:    # URLs:
 2:    use URI::Encode qw/uri_encode/;
 3:    say uri_encode 'foo&bar&baz';
 4:
 5:    # Or
 6:    use URL::Encode qw/url_encode/;
 7:    say url_encode 'foo&bar&baz';
 8:
 9:    # HTML:
10:    use HTML::Entities;
11:    say encode_entities '<foo>';
12:
13:    # Or
14:    use HTML::Escape qw/escape_html/;
15:    say escape_html '<foo>';

Mojo::Util

Portable utility functions.

 1:    use Mojo::Util qw/slurp  dumper  trim  url_escape  xml_escape/;
 2:
 3:    my $data = slurp 'file.txt';
 4:    dumper $data;
 5:    say trim $string;
 6:    say url_encode 'foo&bar&baz';
 7:    say xml_escape '<foo>';

Mojo::Util

Portable utility functions.

b64_decode, b64_encode, camelize, class_to_file, class_to_path, decamelize, decode, deprecated, dumper, encode, hmac_sha1_sum, html_unescape, md5_bytes, md5_sum, monkey_patch, punycode_decode, punycode_encode, quote, secure_compare, sha1_bytes, sha1_sum, slurp, split_cookie_header, split_header, spurt, squish, steady_time, tablify, term_escape, trim, unindent, unquote, url_escape, url_unescape, xml_escape, xor_encode, xss_escape

Mojo::ByteStream

Bytes with Mojo::Util functions as methods (more or less)

 1:    my $stream = Mojo::ByteStream->new('foo bar baz')->quote;
 2:    say $stream; # prints "foo bar baz"
 3:
 4:    # prints Zm9vIGJhciBiYXo=
 5:    $stream->unquote->encode('UTF-8')->b64_encode('')->say;

Mojo::URL

Handling of URLs

 1:    my $url
 2:    = Mojo::URL->new('http://zoffix:pass@example.com:3000/foo?foo=bar#23');
 3:
 4:    say $url->scheme;   # http
 5:    say $url->userinfo; # zoffix:pass
 6:    say $url->host;     # example.com
 7:    say $url->port;     # 3000
 8:    say $url->path;     # /foo
 9:    say $url->query;    # foo=bar
10:    say $url->fragment; # 23

Mojo::Base

Minimal base class

 1:    package Cat;
 2:    use Mojo::Base -base;
 3:
 4:    has name => 'Nyan';
 5:    has [qw(age weight)] => 4;
 6:
 7:    #############################
 8:    package main;
 9:    use Mojo::Base -strict;
10:
11:    my $mew = Cat->new(name => 'Longcat');
12:    say $mew->age; # prints 4;
13:
14:    # Set age to 3, weight to 5, and then print the new age
15:    say $mew->age(3)->weight(5)->age;

Mojolicious::Plugin::AssetPack

Simple way to handle SASS, LESS, CSS, JS, CoffeeScript and even SPRITES!

 1:    $self->plugin('AssetPack');
 2:
 3:    $self->asset(
 4:        'app.js' => qw{
 5:            https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js
 6:            /js/foo.js
 7:            /js/baz.coffee
 8:            /js/many-files/*
 9:        },
10:    );
11:
12:    # In your template:
13:    % asset 'app.js'
14:
15:    # Which turns into something like
16:    <script src="/packed/app-92bc075894ba75d5e33cf9e7d1f7f3ad.min.js"></script>

Mojolicious::Plugin::AssetPack

 1:    $self->asset(
 2:        'app.css' => qw{
 3:            https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css
 4:            /css/foo.css
 5:            /css/baz.sass
 6:            /css/many-files/*
 7:            sprites:///images/ui
 8:        },
 9:    );
10:
11:    # In your template:
12:    % asset 'app.css'
13:
14:    # Which turns into something like
15:      <link href="/packed/app-c6d1dccb38089e32837cafc728bb3bba.min.css"
16:        rel="stylesheet">

Mojolicious::Plugin::AssetPack

Sprite: a collection of many images stored as a single image to reduce the number of HTTP requests:

Mojolicious::Plugin::AssetPack

AssetPack automatically loads all images referenced by sprites:/// URL (e.g. sprites:///images/ui), combines them into a single image, and prepares the CSS file that looks something like this:

 1:    .ui { background: url(ui-5200164c30fb8660952969caf0cefa3d.png)
 2:        no-repeat; display: inline-block; }
 3:    .ui.social-rss { background-position: 0px -0px;
 4:        width: 34px; height: 30px; }
 5:    .ui.social-github { background-position: 0px -30px;
 6:        width: 40px; height: 40px; }
 7:    .ui.social-chrome { background-position: 0px -70px;
 8:        width: 32px; height: 32px; }

Mojolicious::Plugin::AssetPack

That means to use sprites, all we need to do is:

  1. Save images into a directory
  2. Use markup like <a href="http://github.com" class="ui social-github"> to reference an image in the sprite

Mojolicious::Plugin::AssetPack

BTW, AssetPack will automatically minify your CSS/JS, when your app is running in production mode

Thank You!

Questions?