とりあえず Test::WWW::Mechanize::Catalyst は使わずに Catalyst::Test だけでがんばることにした。
うごかんわけだが、みんなどうしてんの?
5.10 の問題ではなくて、最新版の libwww-perl (5.10に付属)の問題という話だけど、5.8.x でも LWP あげたらおなじことになるのかなー。
Catalyst に関してだけ言えば Model いらなくね?という話もわかるのだが、アプリの複数の箇所で共通で使うオブジェクトはModelを使うと楽だし論理的な実装になるとおもう。
僕のMVCの理解が変かもしれないけど、今はそんな感じで考えている。>共通で使うオブジェクトはModel
そんで、Catalyst::Model 微妙っていうのはやっぱり Catalyst 専用の Model つくってもほかから利用できないしなってのがあるんだけど、それは Catalyst::Model::Adaptor で解決。jrockway++
なので、単体で動作する何らかのモジュールをまずつくり、それを Model::Adaptor で連結する、というのが今の好きな方法。
下記camr作った時点での僕のポリシーは
なんか抜けてる気がするけどこんな感じ。
かっこいい。
オレオレ規約をもとに雛形を生成するヘルパースクリプトでがんばるよりこういう自分用のコントローラをつくるほうがスマートだなぁ。
Catalystユーザー的にはオールドタイプな自分としては見習おうと思った。
いままでずっと JSON::Syck を使い続けていたのだけど、いまの JSON::Syck には
/y|Y
|n|N
|yes|Yes|YES
|no|No|NO
|true|True|TRUE
|false|False|FALSE
|on|On|ON
|off|Off|OFF
|null|Null|NULL
|~
/x
の正規表現にマッチする値はシングルクオートでくくられてしまうという仕様があり、これはJSONの仕様に反するのでパーサーによっては正しく処理できないものがある。
これがFlashから使うJSON APIでもろにはまって、adobe の corelib に入っている JSON のパーサーはこれをパースできない。(rubyのyamlパーサーでもエラーになるようだ)
cho45さんがこれを直すパッチを書いてくれたのだけど、いい機会なので JSON::XS を試してみた。JSON::Syck のドキュメントからも言及されてるしね。
Catalyst::View::JSON から使うには以下のような感じで MyApp::View::JSON をかけばOK。
package MyApp::View::JSON;
use strict;
use base 'Catalyst::View::JSON';
use JSON::XS ();
sub new {
my $self = shift->NEXT::new(@_);
my $dumper = JSON::XS->new->latin1;
$self->json_dumper(sub { $dumper->encode($_[0]) });
$self;
}
1;
JSON::Any つかってないのは Any だと環境変数で使用ライブラリを上書きできてしまっていやなので XS を直使用している。
一日くらい社内でテストしたけどだいじょうぶっぽかったのでさっき本番投入してみた。どうだろうな。
基本自社サービス or 個人サービスしかつくってないので、本当にcatalystを使っているとコピペ地獄になる。
また同じことかいてるなーってのが多すぎるため、むかついてなるべくコピペを減らす工夫をしている。
codereposにあげたcatstarterってのもそうだし、あとはヘルパーを結構書いてる。
Catalystのヘルパーってまぁ要するにコードジェネレータなんだけど、ユーザー登録とかログイン・ログアウトとかほんとにいつもかいてるのでその辺はヘルパー一発で雛形生成するようにしてたりとか、jsのajax apiのサーバーサイドとかもjson形式を決めうちにしちゃってこれも雛形生成している。
いまの Catalyst::Helper って新しいコード生成には使えるけど既存のクラスにメソッド追加とかそういうのできないのが不満。なんかつくりたい。
この辺の話はちゃんとまとめたいな。catalyst conやりたい!
Catalyst::Plugin::URI::MtimeQueryとCatalyst::Plugin::Assets - dann@catalyst - Catalystグループ
Catalyst::Plugin::AssetsにCatalyst::Plugin::URI::MtimeQueryのアイデアをインスパイアしたものを加えると、Assetsも結構使えるかも知れないなぁ。export_with_mtimequeryみたいなのがあればいいのかも。
これ今まさに仕事で使おうと思ってかいている。
そのうち公開します!
いまどきのperl使いな皆様におかれましては内部ではutf8フラグを立てたutf8 stringとして文字列を扱っていると思いますが、そういった場合は uri_for は正しく動作します。
そうではなく、utf8 bytes を渡した場合、今の uri_for では壊れたURIが帰って来るというバグがあります。単純なミスなのだけど。
かなり前からなんだけど誰も気がつかないのか必要ないのか一向に直らないので業を煮やしてメールでパッチ送ったところすぐに反映してくれた。ナイス。
パッチはこんなん。
canonical してないのは仕様らしい。(昔のはしてたよね?)
mst によれば
For RSS/atom feeds etc.
とのこと。どゆこと?
舌足らずすぎた。
Model::DBIC:
connect_info:
- dbi:mysql:table
- root
- on_connect_do:
- SET NAMES utf8
cursor_class: DBIx::Class::Cursor::Cached
cache_file: __path_to(tmp/query_cache)__
さっきはこんなconfigで使った場合のコードです。
すばらしすぎる。もっと早く使えばよかったとおもった。
Catalyst::Model::DBIC::Schema で使う場合はこんな感じでOK。
sub new {
my $self = shift->NEXT::new(@_);
my $cache = Cache::FastMmap->new( share_file => $self->{cache_file} );
$self->schema->default_resultset_attributes({
cache_object => $cache,
});
$self;
}
そんで、あとはsearchのattrとして { cache_for => 300 } とかかいてやればそのクエリは300秒キャッシュされる。
実際にはsearch時ではなくてallとかnextのときにそのクエリをキャッシュしてる。
ちなみにdefault_resultset_attributesにcache_forを含めるとすべてのクエリがキャッシュされる。
あげた。
もともと使ってるのはpmsetupを改造したので雛形つくるだけだったんだけど、毎回DBとか使うわけでもないので、ついでにそこを選べるように改造した。
ほぼ僕専用ルールにのっとった雛形を作成しますので他の方はまったく使えないと思いますが、参考にはなるかもしれません。
他の方のも見てみたいのでぜひともcodereposで共有しましょう!
あ、Catalyst自分規約カンファレンス、とかおもしろいかもしれない。
Catalystは Authentication::Credential::* プラグインを複数使うことにより、複数の認証方式に対応できる。
IP認証などのような、Credプラグインが提供されていないものも即席でCredインタフェースにそったモジュールを作ることで簡単に認証方法を追加することができる。
MyApp.pm はこんな感じ
package MyApp;
use strict;
use warnings;
use Catalyst::Runtime '5.70';
use Catalyst qw/
ConfigLoader
Authentication
Authentication::Credential::HTTP
+MyApp::Credential::IP
Authentication::Store::DBIC
/;
our $VERSION = '0.01';
__PACKAGE__->setup;
Cred::HTTP が Basic 認証用のモジュール。+MyApp::Credential::IP がIP認証用のオリジナルモジュール。
この MyApp::Credential::IP のコードはこんな感じ。
package MyApp::Credential::IP;
use strict;
use warnings;
use base qw/Catalyst::Plugin::Authentication::Credential::Password/;
sub authenticate_from_address {
my $c = shift;
my $ip = $c->model('DBIC::UserIp')->find({ ip => $c->req->address }, { prefetch => 'user' })
or return;
my $user = $c->get_user( $ip->user->username )
or return;
$c->set_authenticated($user);
1;
}
1;
こんな短いコードで認証増やせるの。なかなか便利。
で、実際に利用するのは以下のように、Root Controller の auto アクションに
sub auto :Private {
my ($self, $c) = @_;
# basic auth
$c->authenticate_http and return 1;
# ip auth
$c->authenticate_from_address and return 1;
# return basic auth response when no auth success
$c->authorization_required_response( realm => 'Require Authentication' );
return;
}
などとする。
全体に認証かけたいときはルートコントローラの auto に、コントローラ個別に指定したいときは、上記コードを埋め込んだ認証用の親コントローラクラスつくって認証必要なコントローラはそれを継承する。
パスごとにやりたいときは Authentication::ACL ってのがあるけど僕は使っていない。