$30 off During Our Annual Pro Sale. View Details »

Herokuで学ぶ、初めてのPerl

akiym
September 20, 2013

 Herokuで学ぶ、初めてのPerl

YAPC::Asia 2013

akiym

September 20, 2013
Tweet

More Decks by akiym

Other Decks in Technology

Transcript

  1. )FSPLVͰֶͿɺॳΊͯͷ1FSM
    !BLJZN
    :"1$"TJB

    View Slide

  2. NF
    w!BLJZN
    wIUUQBLJZNDPN
    wେֶੜ
    w๺ւಓ͔Βདྷ·ͨ͠
    w4,:"3$༷
    ͋Γ͕ͱ͏͍͟͝·͢

    View Slide

  3. TRMJLBUTVEPOPSH

    View Slide

  4. ର৅
    wॳ৺ऀ޲͚ηογϣϯ
    wͰ͖Δ͚ͩஸೡʹղઆ͠·͢
    w΢ΣϒΞϓϦΛॻ͖ͨͯ͘1FSMΛ࢝Ίͨ

    View Slide

  5. ໨ࢦ͢΋ͷ
    wϑΟʔϦϯάͰΞϓϦΛ࡞ͬͯΈΔ
    wखݩͰಈ͔͢
    w)FSPLVʹσϓϩΠɺ֎෦΁ެ։
    wྲྀΕΛ֮͑Δ

    View Slide

  6. ݸਓͰ࡞Δ
    ΢ΣϒΞϓϦͷߏ੒

    View Slide

  7. 4FSWFS

    View Slide

  8. "QQMJDBUJPO4FSWFS
    4UBSNBO 4UBSMFU

    OHJOY
    w JQUBCMFT
    w 42-4FSWFS
    w
    4FSWFS

    View Slide

  9. "QQMJDBUJPO4FSWFS
    4UBSNBO 4UBSMFU

    OHJOY
    w JQUBCMFT
    w 42-4FSWFS
    w
    8FC
    "QQMJDBUJPO
    4FSWFS

    View Slide

  10. ࣗ෼Ͱॻ͍ͨ΢ΣϒΞϓϦΛ
    ެ։͍͚ͨͩ͠ͳͷʹ

    View Slide

  11. Ұํɺ)FSPLVͰ͸

    View Slide

  12. 8FC
    "QQMJDBUJPO
    )FSPLV

    View Slide

  13. ΢ΣϒΞϓϦ͚ͩΛ
    ॻ͚ͩ͘

    View Slide

  14. ར఺
    wඞཁͳͱ͜Ζ͚ͩ
    wແྉͰ͋Δఔ౓ͷΞϓϦΛެ։͢Δ͜ͱ
    ͕Ͱ͖Δ

    View Slide

  15. ΢ΣϒΞϓϦΛॻ͘

    View Slide

  16. 8"'
    w8FC"QQMJDBUJPO'SBNFXPSL
    w$BUBMZTU .PKPMJDJPVT
    w͜͜Ͱ͸"NPOΛ࢖͍·͢
    w֮͑Δ͜ͱ͕গͳ͍ɺίʔυ͕খ͍͞

    View Slide

  17. "NPOͷಛ௃
    wͱͯ΋ܰྔ
    wηΩϡϦςΟ
    wσϑΥϧτͰ$43'ରࡦ
    wσϑΥϧτͰ࠾༻͍ͯ͠ΔςϯϓϨʔτ
    Τϯδϯ͕5FYU9TMBUF

    View Slide

  18. ؀ڥߏங

    View Slide

  19. νϡʔτϦΞϧ
    wͦΕͰ͸΢ΣϒΞϓϦΛ࡞Γ·͠ΐ͏

    View Slide

  20. DQBONΛΠϯετʔϧ͢Δ
    $ curl -L http://cpanmin.us | ↩
    perl - App::cpanminus
    ΋͘͠͸
    $ curl -L http://cpanmin.us | ↩
    perl - --sudo App::cpanminus

    View Slide

  21. DBSUPOΛΠϯετʔϧ͢Δ
    $ cpanm Carton

    View Slide

  22. $ cpanm Amon2
    $ amon2-setup.pl MyApp
    $ carton install
    $ carton exec -- plackup app.psgi

    View Slide

  23. $ cpanm Amon2
    $ amon2-setup.pl MyApp
    $ carton install
    $ carton exec -- plackup app.psgi
    εέϧτϯΛ࡞੒͢Δ

    View Slide

  24. $ cpanm Amon2
    $ amon2-setup.pl MyApp
    $ carton install
    $ carton exec -- plackup app.psgi
    ґଘϞδϡʔϧΛΠϯετʔϧ

    View Slide

  25. $ cpanm Amon2
    $ amon2-setup.pl MyApp
    $ carton install
    $ carton exec -- plackup app.psgi
    ΞϓϦΛىಈ

    View Slide

  26. w"NPOಉࠝͷεΫϦϓτ
    wεέϧτϯΛ࡞੒͢Δ͜ͱ͕Ͱ͖Δ
    wجຊతʹ͸͜ͷεΫϦϓτΛར༻ͯ͠Ξ
    ϓϦΛ࡞Γ࢝ΊΔ
    BNPOTFUVQQM

    View Slide

  27. ΞϓϦͷߏ੒

    View Slide

  28. .
    ├── Build.PL
    ├── app.psgi
    ├── config
    │ ├── development.pl
    │ ├── production.pl
    │ └── test.pl
    ├── cpanfile
    ├── db
    ├── lib
    │ ├── MyApp
    │ │ ├── DB
    │ │ │ ├── Row.pm
    │ │ │ └── Schema.pm
    │ │ ├── DB.pm
    │ │ ├── Web
    │ │ │ ├── Dispatcher.pm
    │ │ │ ├── View.pm
    │ │ │ └── ViewFunctions.pm
    │ │ └── Web.pm
    │ └── MyApp.pm
    ├── sql
    │ ├── mysql.sql
    │ └── sqlite.sql
    ├── static
    │ ├── 404.html
    │ ├── 500.html
    │ ├── 502.html
    │ ├── 503.html
    │ ├── 504.html
    │ ├── bootstrap
    │ ├── css
    │ │ └── main.css
    │ ├── img
    │ ├── js
    │ │ ├── es5-shim.min.js
    │ │ ├── jquery-1.10.0.min.js
    │ │ ├── main.js
    │ │ ├── micro-location.js
    │ │ ├── micro_dispatcher.js
    │ │ ├── micro_template.js
    │ │ ├── sprintf-0.7-beta1.js
    │ │ └── strftime.js
    │ └── robots.txt
    ├── t
    │ ├── 00_compile.t
    │ ├── 01_root.t
    │ ├── 02_mech.t
    │ ├── 03_assets.t
    │ ├── 06_jslint.t
    │ └── Util.pm
    ├── tmpl
    │ ├── include
    │ │ ├── layout.tt
    │ │ └── pager.tt
    │ └── index.tt
    └── xt
    19 directories, 52 files

    View Slide

  29. .
    ├── Build.PL
    ├── app.psgi
    ├── config
    │ ├── development.pl
    │ ├── production.pl
    │ └── test.pl
    ├── cpanfile
    ├── db
    ├── lib
    │ ├── MyApp
    │ │ ├── DB
    │ │ │ ├── Row.pm
    │ │ │ └── Schema.pm
    │ │ ├── DB.pm
    │ │ ├── Web
    │ │ │ ├── Dispatcher.pm
    │ │ │ ├── View.pm
    │ │ │ └── ViewFunctions.pm
    │ │ └── Web.pm
    │ └── MyApp.pm
    ├── sql
    │ ├── mysql.sql
    │ └── sqlite.sql
    ├── static
    │ ├── 404.html
    │ ├── 500.html
    │ ├── 502.html
    │ ├── 503.html
    │ ├── 504.html
    │ ├── bootstrap
    │ ├── css
    │ │ └── main.css
    │ ├── img
    │ ├── js
    │ │ ├── es5-shim.min.js
    │ │ ├── jquery-1.10.0.min.js
    │ │ ├── main.js
    │ │ ├── micro-location.js
    │ │ ├── micro_dispatcher.js
    │ │ ├── micro_template.js
    │ │ ├── sprintf-0.7-beta1.js
    │ │ └── strftime.js
    │ └── robots.txt
    ├── t
    │ ├── 00_compile.t
    │ ├── 01_root.t
    │ ├── 02_mech.t
    │ ├── 03_assets.t
    │ ├── 06_jslint.t
    │ └── Util.pm
    ├── tmpl
    │ ├── include
    │ │ ├── layout.tt
    │ │ └── pager.tt
    │ └── index.tt
    └── xt
    19 directories, 52 files
    MJC.Z"QQ8FC%JTQBUDIFSQN
    σΟεύονϟʔ

    View Slide

  30. package MyApp::Web::Dispatcher;
    use strict;
    use warnings;
    use utf8;
    use Amon2::Web::Dispatcher::Lite;
    any '/' => sub {
    my ($c) = @_;
    return $c->render('index.tt');
    };
    post '/account/logout' => sub {
    my ($c) = @_;
    $c->session->expire();
    return $c->redirect('/');
    };
    1;

    View Slide

  31. .
    ├── Build.PL
    ├── app.psgi
    ├── config
    │ ├── development.pl
    │ ├── production.pl
    │ └── test.pl
    ├── cpanfile
    ├── db
    ├── lib
    │ ├── MyApp
    │ │ ├── DB
    │ │ │ ├── Row.pm
    │ │ │ └── Schema.pm
    │ │ ├── DB.pm
    │ │ ├── Web
    │ │ │ ├── Dispatcher.pm
    │ │ │ ├── View.pm
    │ │ │ └── ViewFunctions.pm
    │ │ └── Web.pm
    │ └── MyApp.pm
    ├── sql
    │ ├── mysql.sql
    │ └── sqlite.sql
    ├── static
    │ ├── 404.html
    │ ├── 500.html
    │ ├── 502.html
    │ ├── 503.html
    │ ├── 504.html
    │ ├── bootstrap
    │ ├── css
    │ │ └── main.css
    │ ├── img
    │ ├── js
    │ │ ├── es5-shim.min.js
    │ │ ├── jquery-1.10.0.min.js
    │ │ ├── main.js
    │ │ ├── micro-location.js
    │ │ ├── micro_dispatcher.js
    │ │ ├── micro_template.js
    │ │ ├── sprintf-0.7-beta1.js
    │ │ └── strftime.js
    │ └── robots.txt
    ├── t
    │ ├── 00_compile.t
    │ ├── 01_root.t
    │ ├── 02_mech.t
    │ ├── 03_assets.t
    │ ├── 06_jslint.t
    │ └── Util.pm
    ├── tmpl
    │ ├── include
    │ │ ├── layout.tt
    │ │ └── pager.tt
    │ └── index.tt
    └── xt
    19 directories, 52 files
    DPOpHQM
    1-"$,@&/7؀ڥม਺ʹΑͬͯDPOpH͕
    ੾ΓସΘΔ
    w खݩͰQMBDLVQͨ͠ͱ͖ʹ͸
    EFWFMPQNFOU
    w σϓϩΠͯ͠ಈ͔͍ͯ͠Δͱ͖ʹ͸
    QSPEVDUJPO

    View Slide

  32. use File::Spec;
    use File::Basename qw(dirname);
    my $basedir = File::Spec->rel2abs(File::Spec->catdir(dirname(__FILE__),
    '..'));
    my $dbpath = File::Spec->catfile($basedir, 'db', 'development.db');
    +{
    'DBI' => [
    "dbi:SQLite:dbname=$dbpath", '', '',
    +{
    sqlite_unicode => 1,
    }
    ],
    };

    View Slide

  33. .
    ├── Build.PL
    ├── app.psgi
    ├── config
    │ ├── development.pl
    │ ├── production.pl
    │ └── test.pl
    ├── cpanfile
    ├── db
    ├── lib
    │ ├── MyApp
    │ │ ├── DB
    │ │ │ ├── Row.pm
    │ │ │ └── Schema.pm
    │ │ ├── DB.pm
    │ │ ├── Web
    │ │ │ ├── Dispatcher.pm
    │ │ │ ├── View.pm
    │ │ │ └── ViewFunctions.pm
    │ │ └── Web.pm
    │ └── MyApp.pm
    ├── sql
    │ ├── mysql.sql
    │ └── sqlite.sql
    ├── static
    │ ├── 404.html
    │ ├── 500.html
    │ ├── 502.html
    │ ├── 503.html
    │ ├── 504.html
    │ ├── bootstrap
    │ ├── css
    │ │ └── main.css
    │ ├── img
    │ ├── js
    │ │ ├── es5-shim.min.js
    │ │ ├── jquery-1.10.0.min.js
    │ │ ├── main.js
    │ │ ├── micro-location.js
    │ │ ├── micro_dispatcher.js
    │ │ ├── micro_template.js
    │ │ ├── sprintf-0.7-beta1.js
    │ │ └── strftime.js
    │ └── robots.txt
    ├── t
    │ ├── 00_compile.t
    │ ├── 01_root.t
    │ ├── 02_mech.t
    │ ├── 03_assets.t
    │ ├── 06_jslint.t
    │ └── Util.pm
    ├── tmpl
    │ ├── include
    │ │ ├── layout.tt
    │ │ └── pager.tt
    │ └── index.tt
    └── xt
    19 directories, 52 files
    UNQM
    ςϯϓϨʔτ
    $c->render('index.tt');

    View Slide

  34. "NPO૯͟Β͍

    View Slide

  35. ஌͓ͬͯ͘ͱྑ͍΋ͷ
    wίϯςΩετ
    •$c
    •use Amon2::Declare;
    •c()

    View Slide

  36. SFRVFTU
    •$c->req
    w"NPO8FC3FRVFTU
    •$c->req->param('foo')

    View Slide

  37. SFTQPOTF
    •$c->render()
    •$c->create_response()
    •$c->res_404()
    •$c->redirect()

    View Slide

  38. package MyApp::Web::Dispatcher;
    use strict;
    use warnings;
    use utf8;
    use Amon2::Web::Dispatcher::Lite;
    any '/' => sub {
    my ($c) = @_;
    return $c->render('index.tt');
    };
    post '/account/logout' => sub {
    my ($c) = @_;
    $c->session->expire();
    return $c->redirect('/');
    };
    1;
    SFTQPOTFΛฦ͢

    View Slide

  39. SFRVFTUΛड͚औͬͯ
    SFTQPOTFΛฦ͢

    View Slide

  40. ͜Ε͚͓͚֮ͩ͑ͯ͹
    ͳΜͱ͔ͳΓ·͢

    View Slide

  41. View Slide

  42. ϑΟʔϦϯάͰ
    ΢ΣϒΞϓϦΛॻ͘

    View Slide

  43. (ZB[P

    View Slide

  44. (ZB[P
    wHZB[PDPN
    wը૾ڞ༗αʔϏε
    wʮॳΊͯʯʹ͸ͱͯ΋ྑ͍୊ࡐ

    View Slide

  45. $ amon2-setup.pl Gyazo
    εέϧτϯΛ࡞੒͢Δ

    View Slide

  46. wεΩʔϚఆٛ
    wTRMTRMJUFTRM
    wTRMQHTRM

    View Slide

  47. CREATE TABLE IF NOT EXISTS image (
    id INTEGER NOT NULL PRIMARY KEY,
    filename VARCHAR(64),
    src TEXT,
    ctime INT UNSIGNED NOT NULL,
    UNIQUE (filename)
    );
    TRMTRMJUFTRM

    View Slide

  48. CREATE TABLE IF NOT EXISTS image (
    id SERIAL PRIMARY KEY,
    filename VARCHAR(64),
    src BYTEA,
    ctime INT NOT NULL,
    UNIQUE (filename)
    );
    TRMQHTRM

    View Slide

  49. package Gyazo::DB::Schema;
    use strict;
    use warnings;
    use utf8;
    use Teng::Schema::Declare;
    base_row_class 'Gyazo::DB::Row';
    table {
    name 'image';
    pk 'id';
    columns qw(id filename src ctime);
    };
    1;
    MJC(ZB[P%#4DIFNBQN

    View Slide

  50. wVQMPBEʹΞΫηε
    wJNBHFEBUBͱͯ͠ը૾Λड͚औΔ
    wը૾ͷϑΥʔϚοτ͸1/(ͷΈରԠ
    wΞοϓϩʔυͨ͠ը૾ͷ63-Λฦ͢

    View Slide

  51. MJC(ZB[P8FC%JTQBUDIFSQN

    View Slide

  52. use DBD::Pg;
    use Digest::MD5 qw/md5_hex/;
    post '/upload' => sub {
    my ($c) = @_;
    my $imagedata = $c->req->upload('imagedata') or die;
    # アップロードできる画像は1MB未満とする
    die unless $imagedata->size < 1024 * 1024;
    # 画像データを読み込む
    my $src = do {
    open my $fh, '<:raw', $imagedata->path or die $!;
    local $/; <$fh>;
    };
    # PNGであることをシグネチャで判定する
    die unless $src =~ /^\x89PNG\x0d\x0a\x1a\x0a/;
    my $filename = md5_hex($src) . '.png';
    $c->db->fast_insert('image', {
    filename => $filename,
    src => [$src, {pg_type => DBD::Pg::PG_BYTEA}],
    ctime => time(),
    });
    my $url = $c->req->base . $filename;
    return $c->create_response(200, [], [$url]);
    };

    View Slide

  53. use DBD::Pg;
    use Digest::MD5 qw/md5_hex/;
    post '/upload' => sub {
    my ($c) = @_;
    my $imagedata = $c->req->upload('imagedata') or die;
    # アップロードできる画像は1MB未満とする
    die unless $imagedata->size < 1024 * 1024;
    # 画像データを読み込む
    my $src = do {
    open my $fh, '<:raw', $imagedata->path or die $!;
    local $/; <$fh>;
    };
    # PNGであることをシグネチャで判定する
    die unless $src =~ /^\x89PNG\x0d\x0a\x1a\x0a/;
    my $filename = md5_hex($src) . '.png';
    $c->db->fast_insert('image', {
    filename => $filename,
    src => [$src, {pg_type => DBD::Pg::PG_BYTEA}],
    ctime => time(),
    });

    View Slide

  54. use DBD::Pg;
    use Digest::MD5 qw/md5_hex/;
    post '/upload' => sub {
    my ($c) = @_;
    my $imagedata = $c->req->upload('imagedata') or die;
    # アップロードできる画像は1MB未満とする
    die unless $imagedata->size < 1024 * 1024;
    # 画像データを読み込む
    my $src = do {
    open my $fh, '<:raw', $imagedata->path or die $!;
    local $/; <$fh>;
    };
    # PNGであることをシグネチャで判定する
    die unless $src =~ /^\x89PNG\x0d\x0a\x1a\x0a/;
    my $filename = md5_hex($src) . '.png';
    $c->db->fast_insert('image', {
    filename => $filename,
    src => [$src, {pg_type => DBD::Pg::PG_BYTEA}],
    ctime => time(),
    });
    perldoc Plack::Request::Upload

    View Slide

  55. post '/upload' => sub {
    my ($c) = @_;
    my $imagedata = $c->req->upload('imagedata') or die;
    # アップロードできる画像は1MB未満とする
    die unless $imagedata->size < 1024 * 1024;
    # 画像データを読み込む
    my $src = do {
    open my $fh, '<:raw', $imagedata->path or die $!;
    local $/; <$fh>;
    };
    # PNGであることをシグネチャで判定する
    die unless $src =~ /^\x89PNG\x0d\x0a\x1a\x0a/;
    my $filename = md5_hex($src) . '.png';
    $c->db->fast_insert('image', {
    filename => $filename,
    src => [$src, {pg_type => DBD::Pg::PG_BYTEA}],
    ctime => time(),
    });
    my $url = $c->req->base . $filename;
    return $c->create_response(200, [], [$url]);
    };

    View Slide

  56. post '/upload' => sub {
    my ($c) = @_;
    my $imagedata = $c->req->upload('imagedata') or die;
    # アップロードできる画像は1MB未満とする
    die unless $imagedata->size < 1024 * 1024;
    # 画像データを読み込む
    my $src = do {
    open my $fh, '<:raw', $imagedata->path or die $!;
    local $/; <$fh>;
    };
    # PNGであることをシグネチャで判定する
    die unless $src =~ /^\x89PNG\x0d\x0a\x1a\x0a/;
    my $filename = md5_hex($src) . '.png';
    $c->db->fast_insert('image', {
    filename => $filename,
    src => [$src, {pg_type => DBD::Pg::PG_BYTEA}],
    ctime => time(),
    });
    my $url = $c->req->base . $filename;
    return $c->create_response(200, [], [$url]);
    };
    ΠσΟΦϜ$imagedata->pathͷ಺༰ΛಡΈࠐΉ

    View Slide

  57. post '/upload' => sub {
    my ($c) = @_;
    my $imagedata = $c->req->upload('imagedata') or die;
    # アップロードできる画像は1MB未満とする
    die unless $imagedata->size < 1024 * 1024;
    # 画像データを読み込む
    my $src = do {
    open my $fh, '<:raw', $imagedata->path or die $!;
    local $/; <$fh>;
    };
    # PNGであることをシグネチャで判定する
    die unless $src =~ /^\x89PNG\x0d\x0a\x1a\x0a/;
    my $filename = md5_hex($src) . '.png';
    $c->db->fast_insert('image', {
    filename => $filename,
    src => [$src, {pg_type => DBD::Pg::PG_BYTEA}],
    ctime => time(),
    });
    my $url = $c->req->base . $filename;
    return $c->create_response(200, [], [$url]);
    };
    ਖ਼نදݱͰ1/(ͷγάωνϟΛ൑ఆ

    View Slide

  58. die unless $imagedata->size < 1024 * 1024;
    # 画像データを読み込む
    my $src = do {
    open my $fh, '<:raw', $imagedata->path or die $!;
    local $/; <$fh>;
    };
    # PNGであることをシグネチャで判定する
    die unless $src =~ /^\x89PNG\x0d\x0a\x1a\x0a/;
    my $filename = md5_hex($src) . '.png';
    $c->db->fast_insert('image', {
    filename => $filename,
    src => [$src, {pg_type => DBD::Pg::PG_BYTEA}],
    ctime => time(),
    });
    my $url = $c->req->base . $filename;
    return $c->create_response(200, [], [$url]);
    };

    View Slide

  59. die unless $imagedata->size < 1024 * 1024;
    # 画像データを読み込む
    my $src = do {
    open my $fh, '<:raw', $imagedata->path or die $!;
    local $/; <$fh>;
    };
    # PNGであることをシグネチャで判定する
    die unless $src =~ /^\x89PNG\x0d\x0a\x1a\x0a/;
    my $filename = md5_hex($src) . '.png';
    $c->db->fast_insert('image', {
    filename => $filename,
    src => [$src, {pg_type => DBD::Pg::PG_BYTEA}],
    ctime => time(),
    });
    my $url = $c->req->base . $filename;
    return $c->create_response(200, [], [$url]);
    };
    perldoc Teng

    View Slide

  60. # PNGであることをシグネチャで判定する
    die unless $src =~ /^\x89PNG\x0d\x0a\x1a\x0a/;
    my $filename = md5_hex($src) . '.png';
    $c->db->fast_insert('image', {
    filename => $filename,
    src => [$src, {pg_type => DBD::Pg::PG_BYTEA}],
    ctime => time(),
    });
    my $url = $c->req->base . $filename;
    return $c->create_response(200, [], [$url]);
    };

    View Slide

  61. # PNGであることをシグネチャで判定する
    die unless $src =~ /^\x89PNG\x0d\x0a\x1a\x0a/;
    my $filename = md5_hex($src) . '.png';
    $c->db->fast_insert('image', {
    filename => $filename,
    src => [$src, {pg_type => DBD::Pg::PG_BYTEA}],
    ctime => time(),
    });
    my $url = $c->req->base . $filename;
    return $c->create_response(200, [], [$url]);
    };
    $c->create_reponse()ͰϨεϙϯεΛ࡞੒͠
    ը૾ͷ63-Λฦ͢

    View Slide

  62. wpMFOBNFQOHʹΞΫηε
    wΞοϓϩʔυͨ͠ը૾ΛݟΒΕΔΑ͏ʹ
    wը૾͕ଘࡏ͠ͳ͚Ε͹

    View Slide

  63. get '/{filename:.+\.png}' => sub {
    my ($c, $args) = @_;
    my $image = $c->db->single('image', {
    filename => $args->{filename},
    });
    if ($image) {
    return $c->create_response(
    200,
    ['Content-Type' => 'image/png'],
    [$image->src]
    );
    } else {
    return $c->res_404;
    }
    };

    View Slide

  64. get '/{filename:.+\.png}' => sub {
    my ($c, $args) = @_;
    my $image = $c->db->single('image', {
    filename => $args->{filename},
    });
    if ($image) {
    return $c->create_response(
    200,
    ['Content-Type' => 'image/png'],
    [$image->src]
    );
    } else {
    return $c->res_404;
    }
    };
    ਖ਼نදݱ͕࢖͑Δ(perldoc Router::Simple)
    $args->{filename}Ͱड͚औΔ͜ͱ͕Ͱ͖Δ

    View Slide

  65. get '/{filename:.+\.png}' => sub {
    my ($c, $args) = @_;
    my $image = $c->db->single('image', {
    filename => $args->{filename},
    });
    if ($image) {
    return $c->create_response(
    200,
    ['Content-Type' => 'image/png'],
    [$image->src]
    );
    } else {
    return $c->res_404;
    }
    };
    status,
    [headers],
    [body]

    View Slide

  66. wIJTUPSZʹΞΫηε
    wΞοϓϩʔυͨ͠ը૾Ұཡ

    View Slide

  67. get '/history' => sub {
    my ($c) = @_;
    my @images = $c->db->search('image',
    {},
    {
    order_by => {ctime => 'DESC'}, # 降順
    }
    );
    return $c->render('history.tt' => {
    images => \@images,
    });
    };

    View Slide

  68. get '/history' => sub {
    my ($c) = @_;
    my @images = $c->db->search('image',
    {},
    {
    order_by => {ctime => 'DESC'}, # 降順
    }
    );
    return $c->render('history.tt' => {
    images => \@images,
    });
    };
    DSFOEFS ςϯϓϨʔτ໊UUaςϯϓϨʔτม਺

    View Slide

  69. [% WRAPPER 'include/layout.tt' %]

    [%- FOR image IN images %]


    height="360" />


    [%- END %]

    [% END %]
    UNQMIJTUPSZUU

    View Slide

  70. [% WRAPPER 'include/layout.tt' %]

    [%- FOR image IN images %]


    height="360" />


    [%- END %]

    [% END %]
    UNQMIJTUPSZUU
    $c->render()Ͱ౉ͨ͠ςϯϓϨʔτม਺

    View Slide

  71. [% WRAPPER 'include/layout.tt' %]

    [%- FOR image IN images %]


    height="360" />


    [%- END %]

    [% END %]
    UNQMIJTUPSZUU
    image.filename → $image->filename

    View Slide

  72. [% WRAPPER 'include/layout.tt' %]

    [%- FOR image IN images %]


    height="360" />


    [%- END %]

    [% END %]
    UNQMIJTUPSZUU
    ~จࣈ࿈݁ ςϯϓϨʔτ಺

    View Slide

  73. wෆཁͳίʔυΛऔΓআ͘
    w׬੒
    IUUQTHJUIVCDPNBLJZNQFSMIFSPLVFYBNQMFHZB[P
    •$ git add .
    $ git ci -m 'initial commit'

    View Slide

  74. Ϟδϡʔϧґଘ
    wґଘϞδϡʔϧΛDQBOpMFʹॻ͘
    wDBSUPOJOTUBMM

    View Slide

  75. requires 'perl', '5.008001';
    requires 'Amon2', '4.03';
    requires 'Text::Xslate', '1.6001';
    requires 'DBD::Pg' , '2.19.3';
    requires 'JSON' , '2.50';
    requires 'Module::Functions' , '2';
    requires 'Plack::Middleware::ReverseProxy', '0.09';
    requires 'Teng' , '0.18';
    requires 'Test::WWW::Mechanize::PSGI' , '0';
    requires 'Time::Piece' , '1.20';
    on 'configure' => sub {
    requires 'Module::Build', '0.38';
    requires 'Module::CPANfile', '0.9010';
    };
    on 'test' => sub {
    requires 'Test::More', '0.98';
    };
    DQBOpMF

    View Slide

  76. खݩͰಈ͔͢
    $ sqlite3 db/development.db < sql/sqlite.sql
    $ carton install
    $ carton exec -- plackup app.psgi

    View Slide

  77. View Slide

  78. ΞϓϦΛެ։

    View Slide

  79. )FSPLVͰ1FSM͸
    ࢖͑Δʁ

    View Slide

  80. ࠷ۙͷ1FSMʹ଍Γͳ͍Ϟϊc)BDIJPKJQN೔Ί͘ΓςοΫτʔΫ
    IUUQIBDIJPKJQNHJUIVCJPFOUSZQFSM@TIPSUBHFIUNM

    View Slide

  81. ެࣜαϙʔτ͸͞Ε͍ͯ·ͤΜ͕
    ࣮͸࢖͑·͢

    View Slide

  82. εςοϓ

    View Slide

  83. UPPMCFMU
    wIUUQTUPPMCFMUIFSPLVDPN
    •$ heroku login

    View Slide

  84. εςοϓ

    View Slide

  85. CVJMEQBDL
    wCVJMEQBDLͱ͍͏࢓૊ΈͰެࣜͰ
    αϙʔτ͞Ε͍ͯͳ͍ݴޠ΋ѻ͏͜ͱ͕
    Ͱ͖Δ
    wNJZBHBXBIFSPLVCVJMEQBDLQFSM
    w҆৺ͷNJZBHBXB͞Μ

    View Slide

  86. wࠓճ͸ΑΓεϜʔζʹਐΊΔΑ͏ʹ͢Δ
    ͨΊʹ
    wDBSUPOରԠ
    wBLJZNIFSPLVCVJMEQBDLQFSMUSFF
    DBSUPO

    View Slide

  87. UJQT
    w1FSMͷόʔδϣϯࢦఆ
    wQFSMWFSTJPOʹॻ͘
    wFDIPQFSMWFSTJPO

    View Slide

  88. w)FSPLVʹσϓϩΠ͢Δલʹ
    wDBSUPOJOTUBMM
    HJUBEEDQBOpMFTOBQTIPU
    HJUDPNNJUNEFQT

    View Slide

  89. $ heroku create --stack cedar --buildpack ↩
    https://github.com/akiym/heroku-buildpack-perl.git\#carton
    Creating sleepy-dusk-4854... done, stack is cedar
    BUILDPACK_URL=https://github.com/akiym/heroku-buildpack-perl.git#carton
    http://sleepy-dusk-4854.herokuapp.com/ | [email protected]:sleepy-dusk-4854.git
    Git remote heroku added
    $ git push heroku master
    -----> Fetching custom git buildpack... done
    -----> Perl/PSGI app detected
    -----> Vendoring Perl
    Using perl 5.16.3
    -----> Bootstrapping cpanm/Carton
    -----> Installing dependencies with Carton
    -----> Installing Starman
    -----> Discovering process types
    Procfile declares types -> (none)
    Default types for Perl/PSGI -> web
    -----> Compiled slug size: 20.3MB
    -----> Launching... done, v5
    http://sleepy-dusk-4854.herokuapp.com deployed to Heroku

    View Slide

  90. $ heroku create gyoza --stack cedar --buildpack ↩
    https://github.com/akiym/heroku-buildpack-perl.git\#carton
    ΞϓϦͷ໊લΛࢦఆ͢Δ৔߹

    View Slide

  91. $ heroku create --stack cedar --buildpack ↩
    https://github.com/akiym/heroku-buildpack-perl.git\#carton
    Creating sleepy-dusk-4854... done, stack is cedar
    BUILDPACK_URL=https://github.com/akiym/heroku-buildpack-perl.git#carton
    http://sleepy-dusk-4854.herokuapp.com/ | [email protected]:sleepy-dusk-4854.git
    Git remote heroku added
    $ git push heroku master
    -----> Fetching custom git buildpack... done
    -----> Perl/PSGI app detected
    -----> Vendoring Perl
    Using perl 5.16.3
    -----> Bootstrapping cpanm/Carton
    -----> Installing dependencies with Carton
    -----> Installing Starman
    -----> Discovering process types
    Procfile declares types -> (none)
    Default types for Perl/PSGI -> web
    -----> Compiled slug size: 20.3MB
    -----> Launching... done, v5
    http://sleepy-dusk-4854.herokuapp.com deployed to Heroku

    View Slide

  92. $ heroku create --stack cedar --buildpack ↩
    https://github.com/akiym/heroku-buildpack-perl.git\#carton
    Creating sleepy-dusk-4854... done, stack is cedar
    BUILDPACK_URL=https://github.com/akiym/heroku-buildpack-perl.git#carton
    http://sleepy-dusk-4854.herokuapp.com/ | [email protected]:sleepy-dusk-4854.git
    Git remote heroku added
    $ git push heroku master
    -----> Fetching custom git buildpack... done
    -----> Perl/PSGI app detected
    -----> Vendoring Perl
    Using perl 5.16.3
    -----> Bootstrapping cpanm/Carton
    -----> Installing dependencies with Carton
    -----> Installing Starman
    -----> Discovering process types
    Procfile declares types -> (none)
    Default types for Perl/PSGI -> web
    -----> Compiled slug size: 20.3MB
    -----> Launching... done, v5
    http://sleepy-dusk-4854.herokuapp.com deployed to Heroku

    View Slide

  93. $ heroku create --stack cedar --buildpack ↩
    https://github.com/akiym/heroku-buildpack-perl.git\#carton
    Creating sleepy-dusk-4854... done, stack is cedar
    BUILDPACK_URL=https://github.com/akiym/heroku-buildpack-perl.git#carton
    http://sleepy-dusk-4854.herokuapp.com/ | [email protected]:sleepy-dusk-4854.git
    Git remote heroku added
    $ git push heroku master
    -----> Fetching custom git buildpack... done
    -----> Perl/PSGI app detected
    -----> Vendoring Perl
    Using perl 5.16.3
    -----> Bootstrapping cpanm/Carton
    -----> Installing dependencies with Carton
    -----> Installing Starman
    -----> Discovering process types
    Procfile declares types -> (none)
    Default types for Perl/PSGI -> web
    -----> Compiled slug size: 20.3MB
    -----> Launching... done, v5
    http://sleepy-dusk-4854.herokuapp.com deployed to Heroku

    View Slide

  94. )FSPLVΛ࢖͏্Ͱͷ஫ҙ఺
    w42-JUFΛѻ͏͜ͱ͕Ͱ͖ͳ͍
    wͲ͏͢Ε͹͍͍ʁ
    w)FSPLVͰ͸1PTUHSF42-Λαϙʔτ
    ͍ͯ͠·͢

    View Slide

  95. $ heroku addons:add heroku-postgresql
    Adding heroku-postgresql on sheltered-earth-3211... done, v6 (free)
    Attached as HEROKU_POSTGRESQL_CYAN_URL
    Database has been created and is available
    ! This database is empty. If upgrading, you can transfer
    ! data from another database with pgbackups:restore.
    Use `heroku addons:docs heroku-postgresql` to view documentation.
    $ heroku pg:psql HEROKU_POSTGRESQL_CYAN_URL < sql/pg.sql
    NOTICE: CREATE TABLE will create implicit sequence "image_image_id_seq" for serial column
    "image.image_id"
    NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "image_pkey" for table "image"
    NOTICE: CREATE TABLE / UNIQUE will create implicit index "image_filename_key" for table "image"
    CREATE TABLE

    View Slide

  96. $ heroku addons:add heroku-postgresql
    Adding heroku-postgresql on sheltered-earth-3211... done, v6 (free)
    Attached as HEROKU_POSTGRESQL_CYAN_URL
    Database has been created and is available
    ! This database is empty. If upgrading, you can transfer
    ! data from another database with pgbackups:restore.
    Use `heroku addons:docs heroku-postgresql` to view documentation.
    $ heroku pg:psql HEROKU_POSTGRESQL_CYAN_URL < sql/pg.sql
    NOTICE: CREATE TABLE will create implicit sequence "image_image_id_seq" for serial column
    "image.image_id"
    NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "image_pkey" for table "image"
    NOTICE: CREATE TABLE / UNIQUE will create implicit index "image_filename_key" for table "image"
    CREATE TABLE

    View Slide

  97. $ heroku addons:add heroku-postgresql
    Adding heroku-postgresql on sheltered-earth-3211... done, v6 (free)
    Attached as HEROKU_POSTGRESQL_CYAN_URL
    Database has been created and is available
    ! This database is empty. If upgrading, you can transfer
    ! data from another database with pgbackups:restore.
    Use `heroku addons:docs heroku-postgresql` to view documentation.
    $ heroku pg:psql HEROKU_POSTGRESQL_CYAN_URL < sql/pg.sql
    NOTICE: CREATE TABLE will create implicit sequence "image_image_id_seq" for serial column
    "image.image_id"
    NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "image_pkey" for table "image"
    NOTICE: CREATE TABLE / UNIQUE will create implicit index "image_filename_key" for table "image"
    CREATE TABLE

    View Slide

  98. $ heroku addons:add heroku-postgresql
    Adding heroku-postgresql on sheltered-earth-3211... done, v6 (free)
    Attached as HEROKU_POSTGRESQL_CYAN_URL
    Database has been created and is available
    ! This database is empty. If upgrading, you can transfer
    ! data from another database with pgbackups:restore.
    Use `heroku addons:docs heroku-postgresql` to view documentation.
    $ heroku pg:psql HEROKU_POSTGRESQL_CYAN_URL < sql/pg.sql
    NOTICE: CREATE TABLE will create implicit sequence "image_image_id_seq" for serial column
    "image.image_id"
    NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "image_pkey" for table "image"
    NOTICE: CREATE TABLE / UNIQUE will create implicit index "image_filename_key" for table "image"
    CREATE TABLE

    View Slide

  99. $ heroku addons:add heroku-postgresql
    Adding heroku-postgresql on sheltered-earth-3211... done, v6 (free)
    Attached as HEROKU_POSTGRESQL_CYAN_URL
    Database has been created and is available
    ! This database is empty. If upgrading, you can transfer
    ! data from another database with pgbackups:restore.
    Use `heroku addons:docs heroku-postgresql` to view documentation.
    $ heroku pg:psql HEROKU_POSTGRESQL_CYAN_URL < sql/pg.sql
    NOTICE: CREATE TABLE will create implicit sequence "image_image_id_seq" for serial column
    "image.image_id"
    NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "image_pkey" for table "image"
    NOTICE: CREATE TABLE / UNIQUE will create implicit index "image_filename_key" for table "image"
    CREATE TABLE

    View Slide

  100. ΞϓϦଆͷઃఆ
    wIUUQTQPTUHSFTIFSPLVDPN
    EBUBCBTFT͔Β࡞੒ͨ͠σʔλϕʔεΛ
    ݟΔ͜ͱ͕Ͱ͖·͢
    w%#ͷDPOpHΛઃఆ͠·͢

    View Slide

  101. +{
    'DBI' => [
    "dbi:Pg:dbname=DATABASE;host=HOST;port=PORT;sslmode=require", 'USER', 'PASSWORD',
    +{}
    ],
    };
    DPOpHQSPEVDJUPOQM

    View Slide

  102. +{
    'DBI' => [
    "dbi:Pg:dbname=DATABASE;host=HOST;port=PORT;sslmode=require", 'USER', 'PASSWORD',
    +{}
    ],
    };
    DPOpHQSPEVDJUPOQM

    View Slide

  103. $ git push heroku master

    View Slide

  104. IUUQTMFFQZEVTLIFSPLVBQQDPN

    View Slide

  105. ·ͱΊ

    View Slide

  106. wॳΊͷҰาͱͯ͠ͷ(ZB[PΞϓϦ
    w)FSPLVͰ1FSMΛಈ͔͢ͷ͸؆୯
    wखܰʹެ։
    wࠓճ͸"NPOΛ࢖͍·͕ͨ͠
    ΋ͪΖΜ.PKPMJDJPVTͰ΋ಈ͖·͢

    View Slide

  107. IUUQTHJUIVCDPNBLJZNQFSMIFSPLVFYBNQMFHZB[P

    View Slide

  108. ͋Γ͕ͱ͏͍͟͝·ͨ͠
    ΞϓϦΛಈ͔ͯ͠ΈͯΘ͔Βͳ͍ͱ͜Ζ͕͋Ε͹!BLJZN·ͰͲ͏ͧ

    View Slide