2009年6月29日月曜日

[Symfony] 静的ファイルをバージョン管理するためのメモ

Ruby on Railsを利用していて何気に便利だなと思うのが、JavascriptやCSS等の静的ファイルをバージョン管理してくれるところです。

ここでいうバージョン管理 (Versioning) というのは、ファイルの更新日時をクエリパラメータとして付加してくれることを指します。

<%= javascript_include_tag 'application' %>
# => <script src="/javascripts/application.js?1164912447" type="text/javascript"></script>

こうすることで、ファイルを更新してもブラウザのキャッシュを気にする必要がなくなります。

SymfonyにもRailsと同名の javascript_include_tag というViewヘルパーがあるので、その辺うまいことやってくれるのかと思ったら、そうではありませんでした。
仕方ないので自分でなんとかすることに。


Symfonyの場合、Javascriptファイルの出力の仕方はいろいろあると思うのですが、最終的には、javascript_include_tagが利用されているようです。
(CentOSの場合は /usr/share/pear/symfony/helper/AssetHelper.php)

ですので、javascript_include_tagをごにょごにょすれば一括で修正できそうです。

もちろんSymfonyのコアファイルをいじるのは色々問題があると思うので、AssetHelper.phpをコピーして、同じ名前のままプロジェクトディレクトリ内の lib/helper に置きます。
(helperというディレクトリない場合は新たに作成)


変更前
function javascript_include_tag()
{
$html = '';
foreach (func_get_args() as $source)
{
$source = javascript_path($source);
$html .= content_tag('script', '', array('type' => 'text/javascript', 'src' => $source))."\n";
}

return $html;
}

変更後
function javascript_include_tag()
{
$html = '';
foreach (func_get_args() as $source)
{
$version = filemtime(SF_ROOT_DIR . DIRECTORY_SEPARATOR . 'web' . $source);
$source = javascript_path($source);
$html .= content_tag('script', '', array(
'type' => 'text/javascript', 'src' => $source . '?' . $version))."\n";
}

return $html;
}


スタイルシートの場合は、AssetHelper.php内の stylesheet_tag 修正します。

変更前
function stylesheet_tag()
{
$sources = func_get_args();
$sourceOptions = (func_num_args() > 1 && is_array($sources[func_num_args() - 1])) ? array_pop($sources) : array();

$html = '';
foreach ($sources as $source)
{
$source = stylesheet_path($source);
$options = array_merge(array('rel' => 'stylesheet', 'type' => 'text/css', 'media' => 'screen', 'href' => $source), $sourceOptions);
$html .= tag('link', $options)."\n";
}

return $html;
}

変更後
function stylesheet_tag()
{
$sources = func_get_args();
$sourceOptions = (func_num_args() > 1 && is_array($sources[func_num_args() - 1])) ? array_pop($sources) : array();

$html = '';
foreach ($sources as $source)
{
$version = filemtime(SF_ROOT_DIR . DIRECTORY_SEPARATOR . 'web' . $source);
$source = stylesheet_path($source);
$options = array_merge(array('rel' => 'stylesheet', 'type' => 'text/css', 'media' => 'screen', 'href' => $source . '?' . $version), $sourceOptions);
$html .= tag('link', $options)."\n";
}

return $html;
}


SF_ROOT_DIR はプロジェクトのルートディレクトリを指します。

2009年6月27日土曜日

[Form Notifier] inputフィールド上に透かし文字を入れるjQueryプラグイン

inputフィールドにあらかじめ文字を表示して、ユーザーの注意を喚起したい場合があると思います。
人名検索用のフィールドに「e.g. 山本、田中...」と表示する等。

jQueryのプラグインでも色々あると思うのですが、比較的簡単そうなので自作してみることにしました。

で、できたのがこちら。

jquery-formnotifier - Google Code
http://code.google.com/p/jquery-formnotifier/

jQuery Form Notifier - デモ
http://www.mudaimemo.com/p/formnotifier/

  • inputフィールドのtitle属性に表示したいメッセージを書いてください。
  • <div class="formnotifier" id="fn_fieldID" >Your Message</div> がフィールド上にオーバーレイされます。
  • オプションでCSSの値が指定可能です。
  • 超軽量です (1.8k)

2009年6月25日木曜日

[Symfony] 複数条件でLEFT INNER JOINをするためのメモ

Criteriaを利用して例えばこんなSQLを組み立てたい場合

SELECT
*
FROM
table1 a
LEFT JOIN
table2 b ON a.column1 = b.column1 AND a.column2 = b.column2

普通にaddJoinを使っても条件をひとつつしか指定できません。
$c = new Criteria();
$c->addJoin(Table1Peer::COLUMN1, Table2Peer::COLUMN1, Criteria::LEFT_JOIN);

// table1 a LEFT JOIN table2 b ON a.column1 = b.column1

少し強引ですが、こういう書き方ができるようです。
$c = new Criteria();
$c->addJoin(Table1Peer::COLUMN1, Table2Peer::COLUMN1 .
' AND ' . Table1Peer::COLUMN2 . ' = ' . Table2Peer::COLUMN2, Criteria::LEFT_JOIN);

// LEFT JOIN table2 b ON a.column1 = b.column1 AND a.column2 = b.column2


Criteriaは柔軟性に欠けるので好きになれません。。。

2009年6月20日土曜日

[jQuery] $.extend を利用してJavascriptで継承を実装してみる

Javascirptでクラスという言葉を使っていいものかどうか知りませんが、便宜上そう呼びます。
とりあえず自分なり調べた限りのまとめです。

// 親クラス
var Parent = function(){}
Parent.prototype = {
speak: function () {
alert("I'm " + this.name);
}
};

// 子クラス1
var Child1 = function () {
this.name = 'child1';
}
Child1.prototype = $.extend({
kick: function () {
alert("Kick!!");
}
}, Parent.prototype);

// 子クラス 2
var Child2 = function () {
this.name = 'child2';
}
Child2.prototype = $.extend({
punch: function () {
alert("Punch!!");
}
}, Parent.prototype);


var c1 = new Child1();
c1.speak(); // alert("I'm child1")
c1.kick(); // alert("Kick!!")

var c2 = new Child2();
c2.speak(); // alert("I'm child2")
c2.punch(); // alert("Punch!!")


prototypeをマージするイメージでしょうか。
$.extend の引数の順番が親クラス -> 子クラス、ではなくて 子クラス -> 親クラスとなります。
多重継承も可能なようですが試していません。

jQuery.extend( [deep], target, object1, [objectN] )
http://docs.jquery.com/Utilities/jQuery.extend

Pythonでタブ区切りのファイルを扱う

ユーザーによってアップロードされたタブ区切りのファイル(TSV)をPythonで扱う必要があったのでちょっと調べてみました。
テスト環境はGoogle App Engine + Django

from StringIO import StringIO
import csv

def file_upload(request):
s = StringIO(request.FILES['file']['content'])
contents = csv.reader(s, delimiter='\t')
lines = []
for line in contents:
lines.append(line)

CSVは Comma Separated Valuesの略字ですので、原則はカンマ区切りで構成されたものです。
ですが、Pythonのcsvモジュールはオプションで区切り文字を指定できるので、タブ区切りでもこのモジュールを活用することが可能です。

csv.readerの第1引数は
イテレータプロトコルをサポートし、next メソッドが呼ばれた際に常に文字列を返すような任意のオブジェクトにすることができます
とのことなので、アップロードされたファイルを設定したStringIOインスタンスを指定します。

delimiter引数が区切り文字です。
タブ区切りを扱いたいので、ここでは \t を指定しています。

こちらのエントリを参考にさせていただきました。
http://d.hatena.ne.jp/gonsuzuki/20090401/1238562547

csvモジュールのリファレンス
http://www.python.jp/doc/release/lib/module-csv.html

2009年6月9日火曜日

[newsy] 1ページでテクノロジーニュースを見られるWebサービス作りました

独学のためにひっそりと公開していたのだけれど、MOONGIFTさんでとりあげてもらったので([MOONGIFT] RSSフィードを使ったニュースサイトを構築する「newsy」)、この際だから当ブログでも紹介しておきましょうか。

newsy
http://newsy.appspot.com


TechmemeTechfugaのような、いわゆるニュースアグリゲーター(っていうのかな?)です。

英語圏の有名ブログ/ニュースサイトのRSSフィードを定期的に読み込んで、Google, Apple, Web, Gadgetsなどのカテゴリに自動で分類しています。

ただ、オススメの記事をズバっと推薦するような機能は全然ありません。
ふわ〜っと半リアルタイムで記事が流れて行く感じです。


このサービスを作ったきっかけ

1. 「集合知プログラミング」


とにかくべらぼうに刺激になる本です。
Web一般に興味があって、プログラミングをかじった経験のある人なら絶対に心おどるはず。

newsyのカテゴリ分類は、この本の6章(「ドキュメントフィルタリング」)で紹介されている単純ベイズ分類器を利用しています。
何度か人力でトレーニングするうちに、エントリーを自動で適切なカテゴリに分類してくれるようになるのはけっこう快感です。
(精度はそれなりですが)

サンプルコードをそのまま利用したらものすごく負荷が高くなりそうだったので、自分なりにカスタマイズしました。


2. Google App Engineがcronに対応した

ドメインからお察しの通り、newsyはGoogle App Engine(GAE)上で動いています。
(Django + app-engine-path)

前々から趣味的なWebアプリを作るにはもってこいだったGAEのcron対応という大盤振る舞いに、こちとらのぼせあがってしまったわけです。

「集合知プログラミング」もそうですが、自動でとか勝手にとか、そういうキーワードが大好物な無精者ですから、こりゃやるしかないぞ、と。

具体的にはRSSフィードの読み込みに利用しています。
(15分おきに1フィードの割合)
フィードの読み込み時に上記の単純ベイズ分類器でカテゴリ分けを行っています。



これから追加したい機能

・オススメ記事のレコメンデーション機能
・検索機能
・記事のクラスタリング
・システムをオープンソースとして提供

実は、ソースコード自体はGithubの公開リポジトリに置いてあるのですが、他人が利用することを全く考えていないので、そのままではアプリとして使えない仕様になっていると思います。。。
その辺は今後の課題ということで、よろしくお願いします。