この前の仕事からTipsをダンプ...
Shibuya.pm の typester さんの話で感化されたのもあって採用した Lighty、イイ。シンプルに言われたことだけをこなしてくれる感じ。
で、Catalyst を Lighty で動かすときは Catalyst 5.7004 以上が必須なんすね。でないと、SCRIPT_NAME などの環境変数のバグから、http://example.com/foo/ と http://example.com/foo のように最後にスラッシュが付く付かないで実行されるアクションが変わってしまう。なので今後はアプリケーションクラスにすぐ
use Catalyst::Runtime '5.7004';
と書くことにした。
C::P::UploadProgress を使おうと思ったのですが、Lighty x mod_fastcgi では動かないもよう。要件的にクリティカルじゃはなかったので、深追いせずLighty自身が1.5で mod_uploadprogressとして対応してくれる のを待とうと思うが。
D::C::Schema::Loader、前はロードがかなり遅くて、D::C::Schemaを使ったけど、久しぶりにさわってみたら、Loader速いじゃん! なので、Hoge/Schema.pm に
package Hoge::Schema;
use strict;
use base qw/DBIx::Class::Schema::Loader/;
__PACKAGE__->loader_options(
relationships => 0,
);
1;
とだけ書いて、あとリレーションとか付け加えたいものだけ書く、というやり方で十分実用的。楽だー。
リレーションは、例えば
hoge_master.entry_id 1 <-- 1 mt_entry.entry_id
というように、hoge_master に mt_entry を 1:1 で結びつけたいよという場合は、Hoge/Schema/HogeMaster.pm というファイルを作成して、
package Hoge::Schema::HogeMaster;
use strict;
__PACKAGE__->has_one(
entry => 'Hoge::Schema::MtEntry',
{ 'foreign.entry_id' => 'self.entry_id' }
);
1;
と書く。これで hoge_master 側から entry という名前で mt_entry をのぞける。
このとき has_one だと発行されるSQL文は JOIN (つまり INNER JOIN)、might_have にすると LEFT JOIN になる(…とPODに書いてあるのを、見つけるまで時間かかったので書いておく)
FAQかもしれないけど、日本語の言及は未だ少ない気がするので。
もともと SQL::Abstract がかなりイカしているのに加え、DBICでは独自にいろいろ拡張されているおかげで、ほとんど望みどおりのSQLをPerl構文で表現できて素敵。先の hoge_master が
create table hoge_master (
id varchar(255) not null,
entry_id integer null,
aaa varchar(255),
bbb varchar(255),
ccc varchar(255)
);
こんな感じだったとして、
SELECT
aaa, entry.entry_title,
MONTH(entry.entry_modified_on) AS mo
FROM
hoge_master JOIN mt_entry AS entry
ON entry.entry_id = entry_id
WHERE
ccc LIKE '%xxx%' AND
entry.entry_blog_id = 1 AND
entry.entry_status + 1 = 2
ORDER BY
entry.entry_created_on DESC
LIMIT 5
というSQLを吐きたければ(例なので変なことしてます)
# 通常は
use Hoge::Schema;
my $schema = Hoge::Schema->connect(...);
my $db = $schema->resultset('HogeMaster');
# Catalystの場合は上の替わりに
# my $db = $c->model('DBIC::HogeMaster');
# こんな感じ
my $rs = $db->search(
ccc => { like => '%xxx%' },
'entry.entry_blog_id' => 1,
'entry.entry_status -1' => 2,
{
select => [qw( aaa entry.entry_title )],
'+select'=> [ ['month(entry.entry_modified_on)'] ],
'+as' => ['mo'],
join => [qw( entry )],
order_by => ['entry.entry_created_on desc'],
rows => 5,
}
);
# あとはいろいろと...
warn 'found:'. $rs->count;
use Data::Dumper;
while (my $row = $rs->next) {
print Dumper {
aaa => $row->aaa,
title => $row->entry->entry_title,
mo => $row->get_column('mo'),
};
};
こう、SQL関数が混じってきてもリファレンスとかを使ってうまいこと書ける。やりすぎるとわかりにくくなるけど、そういう時はスキーマクラスに関数生やしたり、inflate/deflate を使った方が良い時かと。
先の例でバレるのですが、今回のアプリは、MovableTypeをモデルとして使っています。MTを使えば、『各レコードが更新されるたびに、スタティックHTML1枚、JSON1個を作成し、更新履歴を更新』 みたいなのはすぐできるのと、意外と工数がかかる『きれいな管理画面』が付いてくるので、ケースによってはかなり開発時間を短縮できる。
(投稿者数によってはライセンスが高くつくので、スケジュールとコストを見比べて・・という感じですが..)
JSONを作ろうとする時とかに気になる、MTのテンプレートで「最後だけカンマなし」は、
[
<MTEntries lastn="6">
{
id: "<$MTEntryBasename$>",
title: "<$MTEntryTitle encode_html="1"$>"
}
<MTEntriesFooter><MTElse>, </MTElse></MTEntriesFooter>
</MTEntries>
]
こんなよーに、MTEntriesFooterとMTElseを使って書ける
追記。上記のように、動的じゃなくて静的な JSON ファイルを使う場合、文字化けとキャッシュに注意。
特に Safari は、「Ajaxでデータをとる場合、そのレスポンスに charset で utf8 があるか、BOMがついているかしないと文字化けちゃうよ問題」があるけど、MTの普通の出力では BOM が付かない(まあ、あたりまえ)なので、
http://e8y.net/repos/mt-bom/trunk/BOM.pl
こういうプラグインを入れて、JSONとして吐き出すテンプレートの最初に
<$MTBOM$>
とするか、もしくは大胆に httpd.conf にて
AddType text/javascript;charset=utf-8 .js
と書くとかしても良い。どうせ、Jsは強力にキャッシュされてしまうので、 httpd.conf などで
<Location /静的JSONファイルがあるディレクトリ>
Header add Expires "Thu, 01 Jan 1970 00:00:00 GMT"
</Location>
みたいなのが必要。Expires の書き方の注意点については前調べたとおり。
http://e8y.net/blog/2006/09/13/p132.html
v2.67からの機能。今までのアイコンは緯度・経度の2次元で置いていたのだけど、それに加えて地図の縮尺を指定し、3次元で追加できるようになった。倍率10以上12以下のココに、というように指定できて、その倍率の時だけ描画される。
たくさんアイコン出すと重いので、アイコンを倍率で分散して軽くしたり、遠くから見た時と近くから見た時でアイコンを変えたりとかいうアイディアにも使える。
わかってて使うのなら良いのだけどこれ、map.clearOverlays() でアイコンを消しても、地図の倍率を変えるとまたアイコンが復活する、という状態になるので注意。普通の map.addOverlay(marker); で追加した場合は、map.clearOverlays() した後は倍率を変えても復活しなかったのだけど。
これは、GMarkerManager が zoomend イベントにアイコン再描画を登録するのが原因。このイベントだけ消す方法、どうやってやるんだろう。わからなかったので、
GEvent.clearInstanceListeners(map);
で全部消して、手動で登録したイベントを再登録する、みたいな方法をとってみたが・・・。
軽く最近の機能を紹介。unstableなものもあります。
map.addControl(new GMapTypeControl(true));というように第一引数にtrueを入れると、「地図・航空写真・地図+写真」が、「地図・写真・両方」というように短縮表記になる。
Imager、いいですよね。APIかっこいいし(重要)、それが画像ならたいてい読んでくれるし(拡張子ごまかしたりしてもOK)、やりたいことはたいていできてしまうので、Perlで画像扱う時のお気に入りです。
Imagerや、Catalyst、DBIx::Class、DateTimeなど、CPAN の The Next Generations なモジュールがある程度固まってきたこの時期ウェブアプリを作れるのは楽だし楽しくてよい。
さてEXIFの話ですが、デジカメ写真のEXIFを読むモジュールは Image::EXIF、Image::Info、Image::ExifTool とたくさんあるのですが、天下のImager も 0.46あたりからEXIFサポートしてます。
use Imager;
use Data::Dumper;
my $img = new Imager();
$img->read(file => "/path/to/image") or die $img->errstr;
warn Dumper { map @$_, $img->tags };
マイナーなEXIF項目だと、Image::EXIF で取れて Imager で取れない、みたいなことがあったですが、撮影日や緯度経度くらいなら、Imagerも対応しているのでOKです。
プログレッシブJPEGの意味らしいです。最初、pjpegって何だヨ?ってなった。知らなかった。
本案件の収穫はこんなもんかな。ていうかすげー支離滅裂だな。