のらねこの気まま暮らし

技術系だけど、Qiita向きではないポエムとかを書くめったに更新されないヤツ

Text::Xslateにis_array_ref, is_hash_refはビルトインで入ってる

月一でとあるチャンネルでぼやいてるきがするのでそろそろメモることにした。

$ perldoc Text::Xslate::Manual::Builtin
: if is_array_ref(hoge) {
<p>arrayref</p>
:}

そんな僕を見た某かるぴす先生から適切なアドバイスを受けた。

$MODULE::Manual 以下はひと通り眺めてからつかうといいよ!

おっしゃるとおりで・・・だが、 忘 れ て し ま え ば 、な ん の 意 味 も な い

ので、日々記録を大切にね。

grunt-init で jquery-plugin を作って travisCIに上げるまでのメモ

準備

以下をインストール

$ npm install -g grunt-init
$ git clone https://github.com/gruntjs/grunt-init-jquery.git ~/.grunt-init/jquery

プロジェクトの作成

grunt-init jqueryをプロジェクトディレクトリで走らせるとplugin用のアセットを生成してくれる。 なお、既にルートディレクトリにpackage.jsonが存在するとエラーになる模。様l

$ mkdir eg-grunt-init
$ cd eg-grunt-init
$ grunt-init jquery

生成されたコード

.
├── CONTRIBUTING.md
├── Gruntfile.js
├── LICENSE-MIT
├── README.md
├── eg-grunt-init.jquery.json
├── libs
│   ├── jquery
│   │   └── jquery.js
│   ├── jquery-loader.js
│   └── qunit
│       ├── qunit.css
│       └── qunit.js
├── package.json
├── src
│   └── eg-grunt-init.js
└── test
    ├── eg-grunt-init.html
    └── eg-grunt-init_test.js

とりあえず動かす

なにもオリジナルなコードは書かずに、そのまま動かす。

$ npm install
$ npm test

すでにもうテストが動くという親切っぷり。 テストはqunitを使っている。そのまま使うか、mochaでも入れるか、は各自の自由なのかな。 また、jshintも設定されてるので、これに従えばjqueryの規約に沿ったコードになる様子。

サンプルコードは2indentで書かれているが、jshint的には特に規定はなく、そのまま4indentで書いてしまったが、好きに設定して良いのかもしれない。 まあ、minifyするか

pluginを実装する

かく。 テスト通らせる

travisCIに上げる

ここでちょっとあれこれしたのでメモ。

  • grunt-cliをinstallする
    • デフォルトではgrunt-cliが無いので、npm install --save-dev grunt-cliした
  • vesionかえる
    • package.jsonに定義されているversionが0.0.0-ignoredだったので、npmとれなかった気配
    • versionやnpmの流儀についてはちゃんと調べずここまで来ているので、なんか勘違いしてるきもするけど、とりあえず動いたので。

おしまい

grunt-initはべんりだなーって思った。 でもqunitは使いにくい。

あ、あと。jquery-formize on Githubというpluginを書きました。form要素にデータ入れたりデータ取ったりエラーのスタイル当てるのにいい感じにしてくれる子がほしかった。 この子を書くときにgrunt-initに入門したので、色々勉強になった。まる。

plenv install-cpanmをすると~/perl5にcpanmをインストールして困った話

App::cpanminusはインストールディレクトリの決定にオプションとしてのlocal_lib環境変数PERL_LOCAL_LIB_ROOTPERL_MM_OPTを参照して決定するようになっている。

僕のMac Book Airさんはたぶん昔にlocal::libをインストールしたりした経緯からか、PERL_LOCAL_LIB_ROOT及びPERL_MM_OPT/User/$user/perl5になっており、cpanmのインストールがPLENV_ROOTよりも優先されてしまっていたため、何をどうしても、/User/$user/perl5にcpanmがインストールされ、plenvからcpanmがたたけ無いというこまった事態におちいったのでした。

どこでPERL_LOCAL_LIB_ROOTPERL_MM_OPTがセットされたのかは謎・・・とりあえず、こいつら消したらちゃんとplenvのversionにinstallできたので備忘録として。

追記

Cartonをinstallしたつもりが、また~/perl5にインストールされた。

$ plenv exec cpanm Carton
$ plenv exec carton install
plenv: carton: command not found
$ plenv exec perldoc -l Carton
/User/mizuki/perl5/lib/Carton.pm

で、まだなにかあるのかと、環境変数をあさったらPERL_MB_OPTというのがございましてですね。。。 これもどうやらlocal::lib系の何からしい。 こいつも潰して、もうないはず。。。

温泉発火村 #2 に参加しました #発火村

概要っぽいの

温泉発火村とは

温泉発火村 #2 : ATNDより抜粋。

温泉発火村とは 温泉でハッカソンしようぜという企画です。まーまー普通のハッカソンです。 ノートPCが持参出来ればだれでも参加出来ます。

温泉でゆったりしつつ美味しいものを食べつつわいわいもくもくハックしようぜ!ってイベントです。

会場について

今回の会場は山木旅館(静岡県熱海) という旅館で、なかなか良い感じでした。あとご飯が美味しかった。ご飯が美味しかった。(大事なことなの(ry

ふりかえり

ご飯美味しかったなぁ・・・お昼ごはんに食べた「あじなどんぶり」。味のぬかづけ?だったろうか、臭みもなくてでも鯵の味がしてて、程よい香りがついてて、一杯では物足りない感じだった。

・・・って考えてるとご飯が美味しかったコトだけで記事埋めちゃうのでこの辺はコンテンツ力ある方々におまかせして。 僕は淡々と作ったものの話をしますよ。

成果物

https://github.com/rymizuki/Heimdall

Heimdal!! キラキラネームを採用したjQuery,Zeptoで使えるFormValidatorモジュールを作りました。 もともとbackbone-validatorというBackbonejs用のValidator書いてたんですけど、弊社開発二部のチャンネル内にて「それBackboneに依存しなくてよくね」ってツッコミもらったので、underscorejsの依存を外して$系とネイティブコードを使ったValidatorに書き換えました。

SYNOPSISとしてはこんな感じ。

// validatorのインスタンスを生成
var heimdall = $.heimdall({
    "name"    : ["required", ["length", [1,  32]]],
    "age"     : ["required", "int"],
    "gender"  : ["required", ["select", ["male", "female"],
    "message" : [            ["length", [0, 256]]]
});

$('.form')
  .on('submit', function () {
    var result = heimdall.validate({
      "name": $('#input-name').val(), // みたいな感じでフォームのデータを{name: value, }でvalidateに渡す
      ...
    });
  
    if (result.has_error()) { // エラーがあったら ...
      $(this).trigger('invalid', [result]); // エラーだよってイベントのトリガ発火するとか。
    }
  })
  .on('invalid', function (e, result) {
    // エラーを表示するなり、アラートを出すなり、リダイレクトするなり。
  });

みたいな感じでバリデーションに特化したモジュールになっております。 ちょっとJSの流儀であるところの命名規則とか、jQueryの公式が出してるpluginのフォーマットに沿ってない、とかもろもろ後になってから気づいたんだけど。

特にdisが飛んでこなければ僕の好みのままで放置しようかなと。

今後の予定

  • ・・・とは言うものの、jQueryのフォーマットには沿わせようかなと。配布の仕組みとかよくしらないし。
  • @gfx さんより「キーアップ等のイベントに連動して特定のinputだけvalidationしたいケース」という宿題を提示してもらったので直近のTODOにする。

今回の困ったこと

  • 唐突に僕のpocketWifiが初期化されパスワードがわからず @kfly8 の無線LANを借りる事になった。事故にも程がある
  • 電源コンセントが辛かったのでタコ足持って行くと良い。
  • testemをgrunt-testemからではなく直接叩きたかったがディレクトリ構造の解決がうまくいかず数時間testemの調査で終わってしまった。
  • 温泉が気持ちよすぎて、ご飯が美味しすぎて、満足感に浸りすぎて作業にならない

今回の学び

  • 熱海素晴らしい
  • 熱海のご飯は実に美味しい
    • 山木旅館さんは山木茶屋という食事処を併設してて(そこでお昼食べた)そこのご飯も実に、実に美味しかった。   * またいきたい
  • grunt-init jquery jsプロジェクトのひな形はコピペで済ましていたんだけど、やっぱりジェネレーター使うべきだなって思った。
  • $.expr[':'].heimdall jQueryの拡張セレクタは、実は好きに定義することが可能。
  • Zeptojsには拡張セレクタが無いので、DOM操作周り気をつけないとjQueryでは動くけど、Zeptoだと動かないプロダクトが出来上がる。
    • 拡張セレクタが使えないのは結構大きい気もするけど、Form操作がそんなに無いなら大体querySelect系でなんとかなる気もする。
  • 海楽しい

まとめ

  • Heimdallというバリデーション特化ライブラリを書きました。
  • 熱海最高
  • 山木旅館飯が美味い
  • 温泉発火村はやっぱり最高だぜ!

#yapcasia 2013 に参加しました

9/20, 9/21 とYAPC::ASIA 2013に参加してきました。
参加するのは3回目で、初のLTにも挑戦させていただきました。

備忘録はあとでまとめるとして、ざっくり所感とか次の一年に思うトコロとかを書き留めようかなと。
あとブログ書くまでがYAPCなので。あとブログ書くまでがYAPCなので。


今年はPerlの話題が多かったですね。去年は「あれ、これPerlのカンファレンスだよね?!」ってくらい周辺技術の話が多かった気がするんですけれど(笑)


セッションで増えたなって思ったのは、Perlのテスト、とGit周りの開発フロー/環境の話。


Perlのテストは、「50ms or die」とは言わずとも、どこも時間短縮したいようで。
中規模チャットサービスの運用事例で紹介されたApp::Prove::Plugin::MySQLPoolを使って一つのサーバでMySQLを並列化するっていうのは気になった。
http://yapcasia.org/2013/talk/show/767463b0-d8fd-11e2-971a-72936aeab6a4で紹介されてたテストの手法、並列化や実行手順はもろもろ参考にしたいところでした。

また、PhantomJSやSeleniumを使ってUIをテストするという話もちらほら。
この辺りは概要だけ撫でて詳しく全然追っていなかったので、ちゃんと追いたい。


Gitに関しては、開発フロー、ブランチ戦略とか、各所で微妙に異なる方法を採用してる。
git-flowとかの影響もありつつ、それだけじゃ賄えないよね。だからうちはこうしようとおもう、みたいなものがあるっぽい。


今回のベストトークショーは二年連続しての @ さんが受賞。おめでとうございます!

Mojoliciousのトークは(一年半くらいまともに触ってない)僕としてはいろいろ忘れてたコトを思い出せてよかった。
敢えてMojoのPluginや機能をModelとかで避けるのは「Web名前空間以外ではMojoに依存したくない」という思いからそういう規約を作っているとのこと。Mojo本体もバージョンアップ速いし、互換性の無いPluginを多数投入しちゃうと、移植とか大変だよね。管理大変だよね。だから、WebはMojoにしつつビジネスロジックの部分はそこから切り離せることができるようにしたい、とのことだった。

トークの後に雑談させてもらって色々と勉強になりました!


Mojoliciousといえば、WAF、WAFといえば、Amon2!!!
Amon2をテーマにしたセッションは少ない(っていうか無かった?)んですけど、いくつかのセッションの中で「Amon2使って」って言葉が聞こえてました。

弊社も使ってます( ー`дー´)キリッ
MojoもいいWAFだけどAmon2もいいWAFなのです。


前回から楽しみにしているPerl6系の話題。
それをPerl5に取り込むモジュールの紹介、今回もいろいろ発掘があったので楽しみたいトコロ。
特にhttp://search.cpan.org/~barefoot/Method-Signatures-20130505/lib/Method/Signatures.pm:Method::Signatures←個人的にはこれがアツい。
他の言語と同じような表記を持ちつつ、しかしPerlの柔軟な引数の受け渡しを残せる。

method/func とで振る舞いが異なるので、明示的にそれらの宣言を示すことができる。
funcとして期待されてるものをうっかりmethod呼び出ししちゃったりその逆だったりが、ソースをぱっと見するだけでわかるんだよ便利!

いや、`$self = shift`してるところで気づけって説はあるんだけども。
明示的にしてくれるのは嬉しいよね。


そんなこんなで語り尽くせないくらい面白い話題がありました。
あ、これメモ見ず思いつきで書いてるんで、メモみたら1周間分くらいに記事になりそうw



@こと僕は初日に初LTしてきました。
・・・まあ、なんだ。うん、次は準備をちゃんとしよう。

反省はもろもろあるけれど、改めて言いたいことは

Web APIのデータアクセスのI/Fを共通化するために書かれたモジュール

TengのI/F(とコード)を参考にしている

なのでDSLでごりごりAPIの仕様的なのを定義できる

データを利用するためのもろもろな厄介事を隠蔽できる

ビジネスロジックには本質的に「データの扱い方」を書くことができる

ってことが言いたかった。言えれば満足できた。言えなかった、後悔している。
突発的自体が発生しても穏やかに、余裕を持って対応できる大人になりたい。そう思った。

https://github.com/rymizuki/p5-Spica

bug潰したらCPANに上げる。予定。



YAPC::ASIA楽しかったです!!!
I Love Perl Communityヽ(^o^)丿ヽ(^o^)丿

お疲れ様でした!

Spicaのドキュメントの下書き

開発二部と布教用の為に下書きしてみた。

Spicaについては以下。

https://github.com/rymizuki/p5-Spica

めざしたもの

WEB APIにまつわる困ったこと

  • 構造や命名規則やフォーマットが異なる
  • リクエスト方法が異なる
  • 受け取ったデータを加工しないと再利用できない

構造や命名規則やフォーマットが異なる

  • CameCaseだったりsnake\_caseだったり、入り交じってたり
  • dateという名前が提供もとによって時間を含んだり含まなかったり
  • ミリ秒単位まであったりepochだったり
  • そもそもパラメータ名が意味不明だったり
  • JSONだったりXMLだったり


=> 自分のルールに合わせたい

  • 時間を含むなら`_at`、含まないなら`_on`
  • DateTimeモジュールで利用可能な形式で使いたい
  • パラメータ名は仕様書読まなくてもなんとなくわかるようにしたい


リクエスト方法が異なる

  • データ取得するだけなのにPOSTリクエスト
  • データをXMLシリアライズ
  • URLにパラメータ埋め込む
  • ヘッダーにはコレ入れてね


=> どれが何を要求してるのかわからなくなる

  • APIや提供元毎に同じ書式で記述したい

受け取ったデータを加工しないと再利用できない

  • XMLを解析
  • 正規表現で不要な文字列を削除
  • ミリ秒に0.001掛けて秒に直す
  • base64をデコードする


=> Controllerとかビジネスロジックにそういうのは書きたくない

  • APIのクライアント側で利用可能なデータに整形して欲しい
  • DB系モジュールのinflateの機能が欲しい
  • RowClassで拡張して複雑な処理も柔軟に対応できるようにしたい

いままでの自作APIクライアント達

  • FurlやLWPでフェッチしてくる
  • XML::SimpleやJSONモジュールでパース
  • HashRefやArrayRefをそれぞれそのままモデルやControllerへ受け渡し
  • 必要に応じて受け取り側で解析
  • どこで何やってるのかわからなくなる
  • 何がどんなデータ持ってるのかわからなくなる
  • 複数のAPIの仕様が混線して仕様書から漁ってくる
  • APIってなんだ(ゲシュタルト崩壊)

Spica

異なるリクエスト方法をまとめて定義


Spica::SpecでAPIのリクエスト方法や受け取り方を指定できる

package Servers::Spec;
use Spica::Spec::Declare;
 
client {
    name 'service';
    endpoint 'list' => '/servers', [];
    endpoint 'single' => '/service/{service_id}' => ['service_id'];
    endpoint 'update' => +{
        method   => 'PUT',
        path     => '/service/{service_id}',
        requires => ['service_id'],
    };
    columns qw(id ip hostname name environment port description roles config updateed_at created_at);
};


Spicaからclientやendpoinを指定してAPIを叩く

my $spica = Spica->new(host => 'api.servers.dev.ry-m.com');
$spica->spec('Servers::Spec');
$spica->parser('Spica::Parser::JSON');
 
my $services = $spica->fetch(service => 'list', +{}); # iterator
 
my $serivce = $spica->fetch(service => 'single', +{service_id => '0xEE763766EC2C11E2B20E9F176D21D1C2'})->next;
 
$service = $spica->fetch(service => 'update', +{
    service_id  => $serivce->id,
    description => 'Jenkinsのサービスだよ',
});

受け取ったデータのinflate


Tengと同様のI/Fでinflateを定義できる

package Servers::Spec;
use Spica::Spec::Declare;
 
use DateTimeX::Factory;
 
client {
    name 'service';
    endpoint 'single' => '/servers', [];
    columns qw(id ip hostname name environment port description roles config updateed_at created_at);
    inflate qr{_at$} => sub {
        my $value  = shift;
        return if !$value;
        return if $value eq '0000-00-00 00:00:00';
        return DateTimeX::Factory->new(time_zone => 'Asia/Tokyo')->strptime($value => '%F');
    };
};


columnの末尾に`_at`がつくものをDateTimeのオブジェクトに変換している。

my $service = $spica->fetch(service => 'list', +{})->next;
 
say $service->created_at->ymd('/');

受け取ったデータがSpicaの想定しないない構造をしていた


初期値のSpicaはデータの受取をSpica::Receiver::Iteratorが行う。
Spica::Receiver::Iteratorは受け取るデータとして`ArrayRef`を期待するが、
以下のような構造が返ってくることが在る

+{
    data => \@data,
    pager => \%pager_args,
}


そのような場合、以下の二点で対策が可能

  • Spica::Receiver::Iteratorのnewメソッドを拡張する
  • Spica::Clientにfilterを登録する

Spica::Receiver::Iteratorのnewメソッドを拡張する


下記の例では、pagerのデータを捨てているが、もちろんIterator側にpagerのメソッドを用意してもよい。

package Servers::Spec;
use Spica::Spec::Declare;
 
client {
    name 'service';
    endpoint 'list' => '/servers', [];
    columns qw(id ip hostname name environment port description roles config updateed_at created_at);
    receiver 'Servers::Receiver::Iterator';
};
 
package Servers::Receiver::Iterator;
use parent qw(Spica::Receiver::Iterator);
 
use Class::Method::Modifieres;
 
around new => sub {
    my $origin = shift;
    my $class  = shift;
    my %args   = @_;
 
    $args{data} = $args{data}{data};
 
    return $class->$origin(%args);
};
 
1;

Spica::Clientにfilterを登録する


filterは第一引数にフックポイント名、第二引数に実行するCodeRefを期待する。
フックポイントで呼び出されたCodeRefはreceiverクラスに渡すデータを返却しなければならない。
この場合はpagerを切り捨てるしか無い。

package Servers::Spec;
use Spica::Spec::Declare;
 
client {
    name 'service';
    endpoint 'list' => '/servers', [];
    columns qw(id ip hostname name environment port description roles config updateed_at created_at);
    filter before_receive => sub {
        my ($spica, $data) = @_;
        return $data->{data};
    };
};


※ フックポイント名は今後変更する可能性があります。

APIステータスコードがエラーだったら死にたい


Spica::Clientにtrigger、もしくはfilterを登録する。
triggerの登録はfilterと同様に、第一引数にフックポイント名、第二引数にCodeRefを期待する。
triggerは返り値を考慮しない点がfilterと異なる。

package Server::Spec;
use Spica::Spec::Declare;
 
client {
    name 'service';
    endpoint 'list' => '/servers', [];
    columns qw(id ip hostname name environment port description roles config updateed_at created_at);
    trigger before_receive => sub {
        my ($spica, $data) = @_;
 
        if ($data->{status} ne 'success') {
            Carp::croak('API retruned error. reason is '. $data->{reason});
        }
    };
};


※ フックポイント名は今後変更する可能性があります。

perlでWEB APIを複雑に扱いたい人向けのSpicaというモジュールを書いている話

インターネット、便利ですよね。TwitterFacebookニコニコ動画とか、みんないろんなWEBサービス使ってますよね。

一般的に使ってる人や、中にはYoutubeの動画をNoPasteみたくペタペタできる自作のWEBサービスを作ったり、IRCからニコ動の動画検索できるようにしたり。
エンジニアなら日頃の些細な面倒くさいを簡単にしちゃいたくなりますよね。

そう、WEBのエンジニアなら様々なWEB APIを使いこなしてこそです。
自分でAPI書いたり、人が作ったAPIを使ったり。もはや日常茶飯事。息をするようにAPIを叩いているはずです。


さて、PerlにはLWP::UserAgentやFurlといったAPIを叩くためのライブラリがありますね。
とあるAPIをGETで叩いてJSONを受け取りたいなんて時も、次のようにすれば一瞬です。

use Furl;
use JSON;
use Log::Minimal;

my $client = Furl->new(agent => 'Sample');
my $response = $client->get('http://example.com/json');

my $data = JSON->new->utf8->decode($response->body);

infof ddf $data;

ちょっぱや便利!


・・・しかしですね。大規模なプロジェクトやいくつものAPIを一つのプロジェクトで使いわけようとすると、さすがに生でLWPやFurlを叩くことにも限界を感じるようになるんです。
APIから取得したデータを再利用するために加工なんてしようものなら、JSONだったりXMLだったりするデータを解析してDateTimeに変換したり正規表現で書き換えたり。。。
だんだん管理する方も「これなにやってんの?」ってなってくるんですよ。たとえ自分が書いたコードでも。
ちなみに僕はこの一年の間で、8種類くらいのWEB APIをそれぞれ実装したわけです。FurlだったりLWPだったりを使って。そのたびに、独自のラッパーを用意したり、プロジェクト毎に違う書き方だったり、僕が手をいれる前の人の歴史的経緯に従属してたりして、もはやカオスなんです。
管理しきれるかこんなの\(^o^)/


ところで、みなさんORMapper使ってますか? 使っていないのであれば、Tengをおすすめします。TengはDBIx::Skinnyの良いところを残しつつ、よりシンプルにしかし柔軟に利用できるORMapperです。TengのRowクラスは非常に拡張しやすく、しかし所在が明確なので(よほど奇特なことをしない限り)直感的に実装できるスグレモノなんです。


僕はこのTengさんを愛用しながら、「あーAPIもTeng::Rowみたいに拡張しやすければ使いやすいしこんな複雑なクラスを毎度毎度毎度毎度書かなくて済むのにー」と思ったわけです。


・・・それだ(゚∀゚)


というわけで、書きました↓


p5-Spica


いや正しくは、『書いてます』。まだ開発段階で煮詰め切れてないところが多々ありますので気になる方はこっそり使ってみてもいいけど動作は保証しないよ。テスト書いてないから←


Tengをモロパクリさせてもらって、TengみたいにAPIを扱えるライブラリ、を目指そうとしたんですが、とあるかるぱちゅぱちゅぱ先生のアドバイスもあって、ちょっと方向転換中です。

基幹にMouseを使ってますが、とりあえず手っ取り早く動かしたかったから大好きなMouseを使っているだけで、それ以上に意図は無かったので、MooにするかMouse辞めるかするかもしれません。


使い方としては、READMEにある通り、適度にSchemaを書いてそいつをSpicaに食べさせて上げるとお手軽にfetchしてくれて、かつRowオブジェクトを生成してくれます。IteratorとかRowクラスはまじパクリです。

package MySchema;
use Spica::Schema::Declare;

client {
  name 'profile';
  endpoint 'single', '/profile', [qw(user_id)];
  endpoint 'search', '/users', [];
  columns qw(user_id name text);
  inflate text => \&inflate_text;
};

package main;
use Spica;

my $spica = Spica->new(
  host         => 'example.com',
  schema_class => 'MySchema',
  parser       => Spica::Parser::JSON->new,
);

my $profile = $spica->fetch('profile', 'single', +{user_id => $user_id});
isa_ok $profile => 'Row::Profile';

can $profile => qw(user_id name text);

という感じで書けます。
`endpoint`は第一引数にendpoint名、第二引数に対象APIのパス、第三引数に必須パラメータのキーを指定できます。第一引数は省略可能で、

    endpoint '/profile', [qw(user_id)]

という書き方もできます。この場合は最後に実行されたendpointしか保持されないので注意が必要です。

最近はやりのRESTFulなURLにも対応したいという場合には以下の様にしていするとよいです。

    endpoint '/profile/{user_id}', [qw(user_id)];

こう書くと、

$spica->fetch('profile', +{user => 'hogehoge'});

と実行したタイミングで、↓のようにパスが設定されます

http://example.com/profile/hogehoge

`{}`でくくったワードをfetchメソッドのパラメータのキーで置換しているのです。便利でしょ!(自画自賛


というわけで、複雑にWEB APIを扱うためのSpicaというモジュールを書いているというお話でした。意見どしどしお待ちしてます!←