Module::Setupで身に付けるよい習慣

あらすじ


前回[twitter:@hachiojipm]で行われた[twitter:@norry_gogo]さんのLTで次のような発言がありました。

自分が書いたPerlコードを添削してくれる人が周りにいなくて困ってる


添削したったでー

https://github.com/okamuuu/Twitter-Reader

おすすめのファイル構成について

一枚岩のスクリプトだとテストがしづらいので普段から以下のようなディレクトリ構成にする事をおすすめします。

Your-Dist/script/*.pl
Your-Dist/t/*.t
Your-Dist/lib/**/*.pm
Your-Dist/Makefile.PL

scriptはbinでも何でも良いと思います。こんな感じのディレクトリがいいと思います。
それを簡単につくるためのcpanモジュールがいくつかあるのですが、今回はその中からModule::Setupをおすすめします。

Module::Setupをインストール


cpanからModule::Setupをinstallします。

% cpanm Module::Setup

Module::Setupをインストールするとmoudule-setupというコマンドが使えるようになります。

% which module-setup                     
/Users/okamura/perl5/perlbrew/perls/current/bin/module-setup

Makefile.PLに次のような行が記述されているとそこにperlで書かれたスクリプトが置かれるらしいです。

install_script('bin/module-setup');

ケルトンつくる

私は$HOME/project以下で作業する習慣があるので次のようにします*1
名前空間は適当です*2

% cd ~/project
% module-setup Twitter::Reader
% cd Twitter-Reader

[twitter:@norry_gogo]さんが作成したコードをもってきます。

% cd ~/project/Twitter-Reader
% mkdir script
% curl https://raw.github.com/gist/997598/2c05441a36fa9a8b7708afd5edf75e35e97b0d94/twitter_ril.pl -o script/read_it_later.pl

とりあえず実行してみる

とりあえず生で乾杯ぐらいの気持ちで以下の作業を行います。あらら、モジュールが入ってませんでした。

% cd ~/project/Twitter-Reader
% perl -cw script/read_it_later.pl
Can't locate Net/Twitter.pm in @INC ...

Net::Twitterが必要なのですが、私の環境には入っていませんでした。こういう事をさけるためにはMakefile.PLを以下のように追記します

% cat Makefile.PL                                      
use inc::Module::Install;
name 'Twitter-Reader';
all_from 'lib/Twitter/Reader.pm';

requires 'Net::Twitter';
requires 'URI::Find';
requires 'Web::Scraper';

tests 't/*.t';
author_tests 'xt';

test_requires 'Test::More';
auto_set_repository;
auto_include;
WriteAll;

cpanmでinstallします。

% cpanm installdeps .
okamura% cpanm --installdeps .                                
--> Working on .Configuring /Users/okamura/project/Twitter-Reader ... OK
==> Found dependencies: Net::Twitter--> Working on Net::Twitter
Fetching http://search.cpan.org/CPAN/authors/id/M/MM/MMIMS/Net-Twitter-3.17001.tar.gz ... OK
Configuring Net-Twitter-3.17001 ... OK
==> Found dependencies: DateTime, Data::Visitor::Callback, ...(中略)...

ちなみにMakefile.PLの雛形はmodule-setupを使うと作成されますので便利です。

そして依存モジュールが全てinstallされている事を確認します。

:!perl -cw script/read_it_later.pl                                        
script/read_it_later.pl syntax OK

メソッドはlib/Twitter/Reader.pmにお引っ越し

とりあえずメソッドコピペします。

package Twitter::Reader;
use 5.010;
use strict;
use warnings;
our $VERSION = '0.01';

use Net::Twitter;
use URI::Find;
use Web::Scraper;
use LWP::UserAgent;
use YAML;
use Scalar::Util 'blessed';
use Encode;

sub get_list_statuses {
    my ($list, $page) = @_; 
    my $statuses;
    my $success = 1;
    eval {
        $statuses = $nt->list_statuses({
            user => $list->{user},
            list_id => $list->{list_id},
            per_page => 200,
            page => $page,
            since_id => $list->{since_id}
        }); 
    };  
    if (my $err = $@) {
        die $@ unless blessed $err && $err->isa('Net::Twitter::Error');
        $success = undef;
    }   
    return ($statuses, $success);
}

sub find_uris_from {
    my $text = shift;
    state @uris; @uris = ();
    state $finder = URI::Find->new(sub{ 
        my ($uri, $orig_uri) = @_;
        push @uris, $orig_uri;
        return $orig_uri;
    });
    $finder->find(\$text);
    return @uris;
}

sub expand_uri {
    my $uri = shift;
    my $res = $ua->head($uri);
    return unless $res->is_success;
    return $res->request->uri;
}

sub get_html_title {
    my $uri = shift;
    state $scraper = scraper {
        process 'title', 'title' => 'TEXT';
    };
    my $html;
    eval {
        $html = $scraper->scrape(URI->new($uri));
    };
    return if $@;
    return "-- No title --" unless $html->{title};
    return $html->{title};
}

1;

syntax checkをしてみます。たくさんエラーがでてきました。元のコードではget_list_statuses(), expand_uri(), get_html_title()がそれぞれスコープ外のグローバル変数を読み込んでいたのでメソッドを移動した際にふがふがぎゃふんとなった事が原因のようです。

:!perl -cw lib/Twitter/Reader.pm
Array found where operator expected at lib/Twitter/Reader.pm line 17, at end of line
        (Missing operator before ?)
Variable "$scraper" is not imported at lib/Twitter/Reader.pm line 36.
        (Did you mean &scraper instead?)
Variable "$scraper" is not imported at lib/Twitter/Reader.pm line 41.
        (Did you mean &scraper instead?)
syntax error at lib/Twitter/Reader.pm line 17, near "state @uris"
Global symbol "@uris" requires explicit package name at lib/Twitter/Reader.pm line 17.
Global symbol "@uris" requires explicit package name at lib/Twitter/Reader.pm line 17.
Global symbol "$finder" requires explicit package name at lib/Twitter/Reader.pm line 18.
Global symbol "@uris" requires explicit package name at lib/Twitter/Reader.pm line 20.
Global symbol "$finder" requires explicit package name at lib/Twitter/Reader.pm line 23.
Global symbol "@uris" requires explicit package name at lib/Twitter/Reader.pm line 24.
Global symbol "$ua" requires explicit package name at lib/Twitter/Reader.pm line 29.
Global symbol "$scraper" requires explicit package name at lib/Twitter/Reader.pm line 36.
Global symbol "$scraper" requires explicit package name at lib/Twitter/Reader.pm line 41.
lib/Twitter/Reader.pm had compilation errors.

ひとまず引数として受け取るように改造してみます。第一引数に$classとしているのは特に意味はありません。
Twitter::Reader::find_uris_from($text,@uris)と書くよりもTwitter::Reader->find_uris_from($text,@uris)と書くのが個人的に好きだからです。

package Twitter::Reader;
use 5.010;
use strict;
use warnings;
our $VERSION = '0.01';

use Net::Twitter;
use URI::Find;
use Web::Scraper;
use LWP::UserAgent;
use YAML;
use Scalar::Util 'blessed';
use Encode;

sub get_list_statuses {
    my ( $class, $nt, $list, $page ) = @_;
    my $statuses;
    my $success = 1;
    eval {
        $statuses = $nt->list_statuses(
            {
                user     => $list->{user},
                list_id  => $list->{list_id},
                per_page => 200,
                page     => $page,
                since_id => $list->{since_id}
            }
        );
    };
    if ( my $err = $@ ) {
        die $@ unless blessed $err && $err->isa('Net::Twitter::Error');
        $success = undef;
    }
    return ( $statuses, $success );
}

sub find_uris_from {
    my ( $class, $text ) = @_;
    state @uris;
    @uris = ();
    state $finder = URI::Find->new(
        sub {
            my ( $uri, $orig_uri ) = @_;
            push @uris, $orig_uri;
            return $orig_uri;
        }
    );
    $finder->find( \$text );
    return @uris;
}

sub expand_uri {
    my ( $class, $ua, $uri ) = @_;
    my $res = $ua->head($uri);
    return unless $res->is_success;
    return $res->request->uri;
}

sub get_html_title {
    my ( $class, $uri ) = @_;
    state $scraper = scraper {
        process 'title', 'title' => 'TEXT';
    };
    my $html;
    eval { $html = $scraper->scrape( URI->new($uri) ); };
    return if $@;
    return "-- No title --" unless $html->{title};
    return $html->{title};
}

1;

もう一回実行してみます。

:!perl -cw lib/Twitter/Reader.pm                                          
lib/Twitter/Reader.pm syntax OK

これでメソッドを外部に配置する事ができました。

script/read_it_late.plから外部モジュールのメソッドを呼ぶ

以下のようにscript/read_it_late.plを書き直します。

#!/usr/bin/env perl
use 5.010;
use strict;
use warnings;
use lib 'lib';
use Twitter::Reader;
use Net::Twitter;
use URI::Find;
use Web::Scraper;
use LWP::UserAgent;
use YAML;
use Scalar::Util 'blessed';
use Encode;

use Data::Dumper;

my $config_uri ='conf/conf.yml';
my $config = YAML::LoadFile($config_uri);

my $nt = Net::Twitter->new(
    traits              => [qw/API::REST API::Lists/],
);
my $read_it_later = URI->new('https://readitlaterlist.com/v2/add');
my $ua = LWP::UserAgent->new;

for my $list ( @{$config->{lists}} ) {
    my $page = 1;
    my $start_since_id = $list->{since_id};
    my $new_since_id   = $start_since_id;
  LOOP_PAGE:
    while (1) {
        my ($statuses, $success) = Twitter::Reader->get_list_statuses($nt,$list, $page);

        $new_since_id = $start_since_id unless $success;
        last LOOP_PAGE unless @$statuses;
        for my $status (reverse @$statuses) {
            warn $status->{id};
            my @uris = Twitter::Reader->find_uris_from($status->{text});
            for my $uri (@uris) {
                my $expand_uri = Twitter::Reader->expand_uri($ua,$uri);
                next unless $expand_uri;
                warn my $html_title = Twitter::Reader->get_html_title($expand_uri);
                next unless $html_title;
                $read_it_later->query_form(
                    apikey   => $config->{read_it_later}{apikey},
                    username => $config->{read_it_later}{username},
                    password => $config->{read_it_later}{password},
                    url      => $expand_uri,
                    title    => sprintf "[TW]%s@%s / %s\n",
                                        $list->{list_name},
                                        $status->{user}{screen_name},
                                        $html_title,
                );
                my $res;
                eval {
                    $res = $ua->head("$read_it_later");
                };
                if ( $@ ) { warn $@; next; }
                if ($res->is_success) {
                    printf "[TW]%s@%s / %s (%s)\n",
                           $list->{list_name},
                           $status->{user}{screen_name},
                           encode('utf-8', $html_title),
                           $expand_uri;
                }
            }
            $new_since_id = $status->{id} if $new_since_id < $status->{id}; 
        }
        $page++;
    }
    $list->{since_id} = $new_since_id;
}

YAML::DumpFile($config_uri, $config);

exit();

確認

% perl -cw script/read_it_later.pl      
script/read_it_later.pl syntax OK

まとめ

添削したコードだとスコープ外にあるグローバル変数をなんとなーく読み込んでしまっている(?)*3様に思えました。
これはあんまり良くないかな?state変数も使ってるのでもしかしたら意味があるのかと最初は思いましたが。

ほかにもいろいろと添削できる箇所はありますし、テストも書いてみようかと思いましたが、それは本人のために宿題にします。
ということで添削した結果[twitter:@norry_gogo]さんには次の言葉を送ります!!


「module-setupを使ってちょ」

*1:人によっては$HOME/workとか

*2:Twitterをあんまり使ってないので機能をよく理解していないという

*3:違ってたらごめんなさい

shipitのはじめかた

あらすじ

macbookairを新調してから初めてshipitコマンドを実行したんですが、いろいろと忘れている事があったので備忘録を書いておきます。

Shipitを用意

インストールします。

% cpanm ShipIt

設定ファイルを用意します。

% vi ~/project/your-Dist/.shipit
steps = FindVersion, ChangeVersion, CheckChangeLog, DistTest, Commit, Tag, MakeDist, UploadCPAN
git.tagpattern = %v
git.push_to = origin

cpan-upload-httpを用意

インストール

% cpanm cpan-upload-http

設定ファイル

% vi ~/.pause
user your-account
password *********
mailto = your-email-address

MANIFESTがないと怒られたら

MANIFESTは次のコマンドで作成できます。

% make manifest

shipit

ここまできたらあとは実行するだけです。

% shipit

shipitが途中で失敗したら

gitにtagを作成するところまで進んでcpanへのアップロードが失敗した場合にもう一度shipitをやり直したいのでtagを消したいという場合は次のような操作をします。

最初にローカルのタグを削除します。

% git tag -d 0.05

削除したことをmasterに伝えます。

% git push origin :refs/tags/0.05

テスト駆動開発のすすめ

hachiojipmに行ってきたのですが#4でも#5でもTestを書くのが難しいという声が聞こえたので「テストは書いてみると簡単」「テストがあると開発が楽」という事を伝えてみようと努力する試みです。

ということでサンプルコードを書いてみました。

https://github.com/okamuuu/Sample-Plack-Test

紹介するサンプルコードについて

ここで紹介しているスクリプトはある男がBlogを作ろうと思ったがどうせたいしたことしないので俺俺WaFをつくってやろうとして実際にやったテスト駆動開発です。

おもむろにt/web.tとかつくってみる

最初にテストを書いてみましょう。

#!/usr/bin/env perl
use strict;
use warnings;
use Test::Most;
use Plack::Test;
use HTTP::Request::Common qw/GET POST/;

my $app = sub {
    return [
        200, [ "Content-Type" => "text/plain", "Content-Length" => 31 ],
        ["hello, I'm okamuuu. Thank you!!"]    ];  
};

test_psgi $app, sub {
    my $cb  = shift;
    my $res = $cb->( GET "/" );

    is $res->code, 200;
    is $res->header('Content-Type'), 'text/plain';
    is $res->content, "hello, I'm okamuuu. Thank you!!";
};

done_testing();

テスト実行。もちろん成功

% prove -vl t/web.t
t/web.t .. 
ok 1
ok 2
ok 3
1..3
ok
All tests successful.

t/web.tを書き換える

では実際にcode-refを返すシンプルな俺々WaFをつくってみましょう。
以下のようにちょっと書き換えます。

#!/usr/bin/env perl
use strict;
use warnings;
use Test::Most;
use Plack::Test;
use HTTP::Request::Common qw/GET POST/;

use_ok('MyApp::Web::Handler');
my $app = MyApp::Web::Handler->app();

test_psgi $app, sub {
    my $cb  = shift;
    my $res = $cb->( GET "/" );

    is $res->code, 200;
    is $res->header('Content-Type'), 'text/plain';
    is $res->content, "hello, I'm okamuuu. Thank you!!";
};

done_testing();

テスト実行。もちろん失敗

:!prove -vl t/web.t
t/web.t ..
not ok 1 - use MyApp::Web::Handler;
<省略>

lib/MyApp/Web/Handler.pmを追加

まずはuse_okのテストを通してみましょう。以下のようにuseできるだけのパッケージをつくります。

package MyApp::Web::Handler;
use strict;
use warnings;

1;

テスト実行。use_okはできているのですが、done_testingに到達するまえにプログラムがdieしています。まあそんなメソッドないですからねえ。

:!prove -vl t/web.t
t/web.t .. 
ok 1 - use MyApp::Web::Handler;
Can't locate object method "app" via package "MyApp::Web::Handler" at t/web.t line 21.
# Tests were run but no plan was declared and done_testing() was not seen.

<省略>

lib/MyApp/Web/Handler.pmにメソッドを追加

code-refを返すメソッドを追加してあげます。

package MyApp::Web::Handler;
use strict;
use warnings;

sub app {
    return sub {
        [   
            200,
            [ "Content-Type" => "text/plain", "Content-Length" => 31 ],
            ["hello, I'm okamuuu. Thank you!!"]
        ];
      };
}

1;

テスト実行。成功します。こんなテストでも通ると気分がよくなります。

:!prove -vl t/web.t
t/web.t ..
ok 1 - use MyApp::Web::Handler;
ok 2
ok 3
ok 4
1..4
ok
All tests successful.

テストがあれば安心して改修できます。

MyApp::Web::Handler->appが返すcode-refはPlackからハッシュリファレンスを受け取っています。
このハッシュリファレンスからユーザーが何を要求しているのか読み解くことになるます。

なのですがMyApp::Web::Handlerはかまわずに配列リファレンスを返しています。ちょっと改造します。

package MyApp::Web::Handler;
use strict;
use warnings;
use Plack::Request;

sub app {
    return sub {
        my $env = shift;

        my $request  = Plack::Request->new($env);
        my $response = $request->new_response(200);

        $response->content_type('text/plain');
        $response->body("hello, I'm okamuuu. Thank you!!");
      
        return $response->finalize();
      };  
}

1;

テスト実行。成功しました。テストがあればコードを書き直した後のチェックが早くて便利ですね。

:!prove -vl t/web.t                                                 
t/web.t .. 
ok 1 - use MyApp::Web::Handler;
ok 2
ok 3
ok 4
1..4
ok
All tests successful.

「遊ぼう」と言えば 「遊ぼう」と答え 「好き」と言えば 「好き」と答える。

いまのままだと「遊ぼう」と言っても「好き」と言っても「ばか」って言っても全部「おっす。おれokamuuu。ありがとよ!!」としか答えません。

とりあえず「またね」と言われたら「またね!!」って返事するようにしましょう。テストを書き換えます。

#!/usr/bin/env perl
use strict;
use warnings;
use Test::Most;
use Plack::Test;
use HTTP::Request::Common qw/GET POST/;

use_ok('MyApp::Web::Handler');
my $app = MyApp::Web::Handler->app();

test_psgi $app, sub {
    my $cb  = shift;
    my $res = $cb->( GET "/" );

    is $res->code, 200;
    is $res->header('Content-Type'), 'text/plain';
    is $res->content, "hello, I'm okamuuu. Thank you!!";
};

test_psgi $app, sub {
    my $cb  = shift;
    my $res = $cb->( GET "/bye" );

    is $res->code, 200;    is $res->header('Content-Type'), 'text/plain';
    is $res->content, "bye bye!!";
};

done_testing();

テストを実行するとやっぱり失敗してしまいました。

:!prove -vl t/web.t
t/web.t ..
ok 1 - use MyApp::Web::Handler;
ok 2
ok 3
ok 4
ok 5
ok 6
not ok 7

#   Failed test at t/web.t line 27.
#          got: 'hello, I'm okamuuu. Thank you!!'
#     expected: 'bye bye!!'
1..7
# Looks like you failed 1 test of 7.

ふりわける

ユーザーのリクエストに応じてどのメソッドを呼べばいいのか判定する処理を書けば良い気がします。なんかそんなのをつくります*1;

MyApp::Web::Handlerを作成します。

package MyApp::Web::Handler;
use strict;
use warnings;
use MyApp::Web::Router::Sinatraish;
use Plack::Request;

sub app {
    return sub {
        my $env = shift;

        my $router = MyApp::Web::Router::Sinatraish->router;
        my $route  = $router->match($env) or return [ 404, [], ['not found'] ];

        my $request = Plack::Request->new($env);
        my $response = $route->{code}->($request);

        return $response->finalize();
      };
}

1;

それからMyApp::Web::Router::Sinatraishを作成します。

package MyApp::Web::Router::Sinatraish;
use strict;
use warnings;
use Router::Simple::Sinatraish;

get '/' => sub { 
    my $request = shift;
    my $response = $request->new_response(200);

    $response->content_type('text/plain');
    $response->body("hello, I'm okamuuu. Thank you!!");

    return $response;
};

get '/bye' => sub {
    my $request = shift;
    my $response = $request->new_response(200);

    $response->content_type('text/plain');
    $response->body("bye bye!!");

    return $response;
};

1;

テストすると成功します。

:!prove -vl t/web.t
t/web.t .. 
ok 1 - use MyApp::Web::Handler;
ok 2
ok 3
ok 4
ok 5
ok 6
ok 7
1..7
ok
All tests successful.

まとめ

そんなこんなで少しずつWaFっぽくなってきたと思います。このようにテストを少しずつ変化させながら開発したりすると実装に集中することができるのですごく楽です。
ちなみに私はviからproveコマンドを呼んでいます*2

そんなこんなでテストを書くのが難しい、という声を聞きますが、私の場合は逆にこうやって開発したほうが手間がかからないので楽だと思います。

だからみんなもテストを書いてみませんか?

*1:文章が乱れてきました。疲れてきたからです。

*2:「,t」でショートカットを用意しています。Perl Hackに書いてあるやつです

Hachioji.pm #5

前回に引き続き八王子に行ってきました。

http://atnd.org/events/15857

ごはん

http://www.hotpepper.jp/strJ000765160/

とりあえず焼き肉屋さんで楽しく食事できたのでよかった。
んー晩ごはんにビール飲みながら焼き肉たべれるなんて幸せの極みだなーと思った。

ちょっとLTができる状況でなかったのでタイ料理屋さんで2次会+LTやりました。

http://www.thailanna.khattiya.jp/index.html

このお店の店員は感じよかったなー。
タイ行ってみたくなってしまった。

LT

[twitter:@umeyuki]さん

メモをとるのに便利なアプリを紹介。だったかな。
アプリに自分のメールアドレスを登録しておいてすぐに送信しちゃうとか、なんかそんなの*1

うーん。全然お話できませんでした。

[twitter:@hondalica]さん

メタリカ好きなプログラマさん。過去のhachioji.pmで行った店の位置を取得してごにょごにょ*2
どうやらお店の設定がかごしま食肉であるはずが想定外の2次会突入のためデモのときちょっと戸惑ってました。

[twitter:@partynight12th]さん

Webkit系のブラウザでCSSをごにょごにょ*3
発表するためのパワポを発表しながら作成するという、前代未聞のライブコーディングを披露されてました。

[twitter:@nekoya]さん

cobblerっていうOSインストールのご案内。サーバーを20台まとめてセットアップする時にすごい楽だそうです。
ちなみに以前は京都にお住まいだったそうです。

[twitter:@hide_o_55]さん

groonga+node.jsのデモ。groonga使ってるようなので今度話しかけよう。

[twitter:@ytnobody]さん

ごめんなさい。がまんできなかったのでトイレに行ってました。
帰ってきたら終わってました。

就職おめでとうございます。その会社、俺も一回応募したことあるんだぜ…

[twitter:@hirobanex]さん

「Smart::Argsとかおすすめです。よくわかんないけど」とよくわかんないけど軽妙なトークでした。Smart::Argsがおすすめなのはなんだろうと思って聞いてみたらその人の前職(ry

[twitter:@equinox79]さん

Chrome Extension「Twitter Search It」はこの人がつくりました。

[twitter:@mgiken]さん

GANCの歴史は彼からはじまった。発表内容はArcでエディタを操作。それviコマンド呼んでるだけ(ry

いつのまにかGANちゃんと呼ばれていました。

[twitter:@shinpei_cmyk]さん

アニメ好きな青年でLT以外の会話がやや残念ですが、LTはすごくまじめな事をしゃべってました。
諸事情で新潟に引き返すそうです。Niigata.pmを作りたい、と。新潟にもPerl使いはいるので結束したいとの事。

個人的には今回のLTで一番良いトークだと思いました。
前後の話がやや残念でしたけど^^

[twitter:@takeR14a]さん

未来のコミッター。焼き肉屋で同じ席でした。ALIX boardという小型サーバー用途のなんかそんなのを紹介。ドライバーと筐体をnekoyaさんに渡してました。ねじ、なくしてなければいいけど^^

[twitter:@toku_bass]さん

#3でお隣にすわってました。SeasaaのPerl Mongerさん。電子書籍の出版・販売サービス「forkN」を紹介。Seasaaって給料おいくらかしら?

[twitter:@norry_gogo]さん

カメラが欲しいそうです。海中から魚を撮った写真を見せてくれました。uzullaさんとカメラの話をしてました。uzulaさんはクラブイベントで写真撮ってるそうな。

前回のお題「今度作りたいもの」で作ってきたものを発表。Twitterのなんかそんなの。Twitterの機能をよくわかってないので説明できません><

自分が書いたPerlコードを添削してくれる人が周りにいなくて困ってるらしいです。テストこれから書いていきたいけど書くのが難しいと悩んでました。

テスト入門的な記事書こうかなー。

[twitter:@okamuuu]

俺のターン。「HTML+CSS+jQuery」フロント側の技術が最近いいなとおもったので紹介しました。資料は以下。前回の反省を生かして1分で終わるぐらいの分量にしました。

http://okamuuu.github.com/AboutjQuery/slides.html

[twitter:@uzulla]さん

このイベントの主催者です。PHPオンリーなDotCloud(?)を紹介。いろいろ手間取りながらも5分ぐらいでDB接続までできてました。

https://www.phpfog.com/

[twitter:@maka2_donzoko]さん

お約束ですが遅れて登場。盛大な拍手で迎えられてました。周りの客はみんな怪訝そうな顔をしてました。まあそれぐらい盛大な拍手です。
最近いいなー。さいきんいいなー。さい…

まとめ

2回目なので前回よりも人の名前と顔を覚える事ができました。

勉強会のいいところは優秀なプログラマの話を聞く事によって新しい知識も習得できるしモチベーションもあがります。

Hachiojipmはそれに加えわずかでも自分自身が発表することができるのでATNDで参加を決めた時点でモチベーションがあがりますし、ほかのプログラマといろんな話ができるのでそこでもモチベーションがあがります。

ま一番いいところは武蔵野市からそんなに遠くないところですw

*1:記憶があいまいです

*2:記憶があいまいです

*3:記憶があいまいです

PlackとGroongaでblog作ってるときにつまづいたこと

groongaでboolなカラムをfalseに変更する場合falseとかnullとか0を渡せばいいのですが、なぜかtrueになるのでなんだろう、という話です。

結論

Plack::Request->parametersから受け取ったハッシュが以下のようになっている

$VAR1 = {          
    '_key' => 'ho',
    'name' => 'ge',         
    'display_fg' => '0'
};

0が数値ではなくて文字列の'0'になってました。こいつをJSON::decode_jsonにかけるとこーなるからそーなる、という話でした。

{"display_fg":"0","name":"ge","_key":"ho"}

ひとまずWeb::Controllerの中に次のような記述をしておいた

    if ( not $params{display_fg} ) { 
        $params{display_fg} = 0;
    }   

Plack::Requestがparametesのvalueを数値で返してくれたらうれしいのですが、1が数値か文字かを判断するのは開発者側の決める問題なんでしょうね。

Router::Simple悩み中

追記あり

以下のようにRouter::SimpleをつかうとUserのRequestから実行すべきController、actionを判定できます。
かつ、ユーザーがどの年月を指定しているかといった情報も知る事ができます。

use strict;
use warnings;
use Router::Simple;
use Data::Dumper;

my $router = Router::Simple->new();
$router->connect( '/blog/{year}/{month}',
    { controller => 'Blog', action => 'monthly' } );

my $env = { 
    'PATH_INFO'      => '/blog/2011/05',
    'REQUEST_METHOD' => 'GET',
};

my $match = $router->match($env);
print Dumper $match;

=result
$VAR1 = {
          'controller' => 'Blog',
          'month' => '05',
          'action' => 'monthly',
          'year' => '2011'
        };
=cut

1;

なのですが、自分で書いたWeb::Controllerは次のようにcapturesをハッシュではなく、配列で渡したい。

My::Web::Controller::Blog->monthly($c, @captures);
package My::Web::Controller::Blog;
sub monthly {
    my ( $class, $c, $year, $month ) = @_; 
    
	...
}

つまりRouter::Simple::Route->match($env)にはこうした情報を返してほしい

$VAR1 = {
          'controller' => 'Blog',
          'month' => '05', # 不要
          'action' => 'monthly',
          'year' => '2011' # 不要
          'captures' => ['2010', '05'],
        };

Router::Simple::Route->matchが返すハッシュリファレンスにcapturesを加えると最も簡単。
以下'0.09'のRouter::Simple::Routeの108行目から下の行に次のような処理を加えたい。

if ( @captured ) {
	$match->{captures} = [@captured];
}

ただし、そうすると元々あったテストが次々とこける。

t/01_simple.t ..
not ok 1

#   Failed test at t/01_simple.t line 15.
#     Structures begin differing at:
#          $got->{captures} = ARRAY(0x1008317a8)
#     $expected->{captures} = Does not exist

うーん。元々のテストに影響を与えずにcaputresを配列で取得したい場合はどうすれば良いだろう。
ついでにcapturesを配列で受け取る場合はこれらをhash化する必要はないので記述を省略したい。

追記(2011-05-22 20:00:00)解決しました。

splatというものを使うと配列で記憶します。以下のように書き換えます。

$router->connect( '/blog/*/*',
    { controller => 'Blog', action => 'monthly' } );
$VAR1 = {
          'controller' => 'Blog',
          'splat' => [
                       '2011',
                       '05'
                     ],
          'action' => 'monthly'
        };

08_splat.tに書いてあったorz