l'essentiel est invisible pour les yeux

Saturday, September 02, 2006

Prototype.jsのCHANGELOGまとめと自分なり拡張

Prototype.jsが Updateされいくつかの機能が追加されました。
メソッドチェーンによるメソッドの呼び出しが完全にサポートされたのは大きな変更かと思います。

CHANGELOG

Element, Form, Form.Elementメソッドの仕様変更
Elment, Form, Form.Elementが配列ではなく、一つの要素(or ID)を引数に取るように仕様変更され、返り値として引数で渡したオブジェクトを返すようになりました。

この修正により、

$('div').each(function(val) {Element.show(val);}); // Before
$('div').each(Element.show); // After

$('input[type="text"]').each(function(val) {Field.clear(val);}); // Before
$('input[type="text"]').each(Form.Element.clear); // After
eachメソッドにElement.*関数を高階関数と渡すだけで全要素に処理を適用できます。

ただし、この仕様変更により次のようなレガシーなコードは変更が必要になります。


Element.show('page', 'sidebar', 'content');// Before
['page', 'sidebar', 'conent'].each(Element.show); // After



Field.*はForm.Methodsに統一
Fieldオブジェクトに定義されていたメソッドは、Form.Elementに統一されました。FieldはForm.Elementへの参照として定義されているため、後方互換性は保たれています。


Form中の要素を全て有効化/無効化するメソッドの追加
Form中の要素全てを有効化/無効化するためのメソッドとして、Form.enable, Form.disableが追加されました。


$('form').disable();
$('form').enable();


$(), $()で返される要素にForm.Methods, Form.Elementがmixinされるようになった
Element.extendメソッドに、FORM要素にはForm.Methodsが、INPUT, TEXTAREA, SELECTの場合には、Form.Element.Methodsがmixinされるようになった。
この変更により、$()の結果に対しての完全なメソッドチェーンでのメソッド呼び出しがサポートされた。


$('input1').clear();
$('input2').focus();



Objectのコピーを生成する、Object.cloneの追加
オブジェクトのコピーを生成し返すメソッドが追加されました。

clone: function(object) {
return Object.extend({}, object);
}


Object中に定義されているキーと値を取得するメソッド
Object.keys, Object.valuesメソッドが追加され、それぞれ引数に渡したオブジェクト中に定義されているキーとその値を取得して配列で返します。

Object.keys(Array); // => ["from","bind","bindAsEventListener"]
Object.values(Array); // => [function (iterable) {...},function () {...},function (object) {...}]



$()関数で参照した要素の子要素に対してのクラス名・CSSセレクタでの参照が可能に。
Element.Methods.getElementByClassName, Element.Methods.getElementsBySelectorが追加され、$()で参照した要素の子要素に対してクラス名・CSSセレクタでの参照が可能になりました。

$('form').getElementsByClassName('item');
$('form').getElementsBySelec('input[type="text"]'); // => テキスト入力フォームのみ取得


Array.reduceメソッドの追加
配列中の要素が一つの場合は、要素を返し、複数要素を含む場合には配列自身を返すメソッドです。
配列内の要素が1つかどうかいちいち確認するのをDRYにします。


[1, 2].reduce() // [1, 2]
[1].reduce() // 1
[].reduce() // undefined


PUT/DELETEメソッドをPOSTメソッドでエミュレートする
DHHによる変更です。Ajax.Requestに渡すオプション中で、method: put, method: deleteと指定している場合は、methods: postに置き換え、_method: put, _method: _deleteを定義して、PUT/DELETEをエミュレートします。
ActiveResourceをJavascriptからも利用しやすくするための変更だと思われます。

個人的に機能拡張しているところ。
prototype.jsは、便利で活用しているのですが、細かいところで「こんな機能あったらなぁ・・」と思って、自分で拡張して実装しています。そのいくつかを書いておきます。

Hash.mapの変形バージョン
HashクラスもEnumerableクラスをmixinしているため、Hash.mapが使えるのですが、ハッシュに高階関数を適用すると次のように適用した結果の値を取り出した配列が返されます


Hash.collect2 = function(iterator) {
var result = $H({});

this.each(function(value, index){
result[value.key] = iterator(value, index);
});
return result;
}
Hash.map2 = Hash.collect2;
function inc_value(val){return val.value+1;};

$H({fruits: 2, apple: 3, melon: 4}).map(inc_value);
//=> [3,4,5]

console.log($H({fruits: 2, apple: 3, melon: 4}).map2(function(val){return val.value+1;}).inspect());
// =>
#<hash:{'fruits':3, apple: 4, melon: 5}>


Hash.toQueryStringの拡張
prototyp.js中に定義されている、Hash.toQueryStringはハッシュをURLパラメータ形式の文字列にして返してくれます。しかし、ハッシュ中に配列が含まれたり、ハッシュがネストしている場合には、次のようになります。

$H({fruits: ["apple", "banana", "melon"]}).toQueryString();
// => "fruits=apple%2Cbanana%2Cmelon"
$H({fruits: {name: "apple", size: 3, color: "red"}}).toQueryString();
// =>
"fruits=%5Bobject%20Object%5D"


人により差異はあるかもしれませんが、これは意図した通りにサーバ側でパースされません。
そこで、Hash.toQueryStringを変更し、次のように返すようにしています。

$H({fruits: ["apple", "banana", "melon"]}).toQueryString();
// =>"fruits[0]=apple&fruits[1]=banana&fruits[2]=melon"
$H({fruits: {name: "apple", size: 3, color: "red"}}).toQueryString();

// => "fruits[name]=apple&fruits[size]=3&fruits[color]=red"


と返されるように、Hash.toQueryStringを次のように定義しています。


Hash.toQueryString = function(){
return this.map(function(pair){
if(pair.value instanceof Array) {
var key = pair.key;
return pair.value.length == 0 ? "" : $A(pair.value).map(function(val){return key+"[]="+val}).join('&');
} else if(pair.value instanceof Object) {
var key = pair.key;
return $H(pair.value).map(function(_pair){return key+"["+_pair.key+"]="+_pair.value}).join('&');
} else {
return pair.map(encodeURIComponent).join('=');
}
}).compact2().join('&');
};


Array.compact2については下記で説明します。

Array.compact2-NULLとundefinedと空文字列(==falseと評価される値)を配列から取り除く
Array.compactの変形バージョンです。


Array.prototype.compact2 = function(){
return this.select(function(value) {
return value != undefined && !(value == false);
});
};



Array.unique - 重複した要素を取り除いたユニークな配列を返す。
Array.prototype.unique = function(){
var ret = [], sorted = this.sort(), cur;
for(var i=0,l=this.length;i<l;++i) i="" return="" ret="">


String関連の拡張
全角スペースを半角スペースにとか。

String.prototype.empty = function(){
return (this == "");
};
String.prototype.full2harf = function(){
return this.gsub("\u3000", "\u0020");
};



書式付き日付出力
そういえば、Prototype.jsには日付関連の拡張は一切無い。
/**
* Date.strftime
*
* Inspired by
* http://www.mattkruse.com/javascript/date/date.js
* Author: Matt Kruse <matt@mattkruse.com>
*/

Date.MONTH_NAMES = 'January February March April May June July August September October November December Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ');
Date.DAY_NAMES = 'Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sun Mon Tue Wed Thu Fri Sat'.split(' ');
Date.prototype.strftime = function(format) {
function LZ(x) {return(x<0||x>9?"":"0")+x}
format=format+"";
var result="";
var i_format=0;
var c="";
var token="";
var y=this.getYear()+"";
var M=this.getMonth()+1;
var d=this.getDate();
var E=this.getDay();
var H=this.getHours();
var m=this.getMinutes();
var s=this.getSeconds();
var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k;
// Convert real date parts into formatted versions
var value=new Object();
if (y.length < y="" h="=">12){value["h"]=H-12;}
else {value["h"]=H;}
value["hh"]=LZ(value["h"]);
if (H>11){value["K"]=H-12;} else {value["K"]=H;}
value["k"]=H+1;
value["KK"]=LZ(value["K"]);
value["kk"]=LZ(value["k"]);
if (H > 11) { value["a"]="PM"; }
else { value["a"]="AM"; }
value["m"]=m;
value["mm"]=LZ(m);
value["s"]=s;
value["ss"]=LZ(s);
while (i_format < c="format.charAt(i_format);" token="" result="result" result="result">


タグベースのイテレータ

ネタ元は、Enctymedia blog.

/**
* Tag base iterator
*/
(function() {
/* Tag base iterator */
var tags = "div p span ul ol li span form input select textarea h1 h2 h3 h4 h5 h6 dl dt em strong a";
var methods = {};
$A(tags.split(' ')).each(function(tag){

methods["each"+tag.charAt(0).toUpperCase()+tag.substring(1)] = function(element, iterator) {
element = $(element);
element.cleanWhitespace();
$A((element).getElementsByTagName(tag)).each(iterator);
}
});
Object.extend(Element, methods);
})();


正規表現文字のエスケープ

RegExp.quote = function(str){
return str.replace(/([\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:\\\-])/g, '\\$1');
};

Thursday, August 31, 2006

PHPのevalは遅くない (require vs eval)

今日の昼飯は、とんかつ 武信分点行ってきました。
ロースカツ膳 竹を食べて、かにクリームコロッケ単品を一つで注文。値段は、1600円ほど。
米油であげているからか、ジューシュにかりっとあがっていて、食べた後も意外とあっさり。肉厚でなかなか美味でした。

夜は焼酎と日本酒を取り揃えているみたいなので、焼酎を飲みながら特選醤油ダレとすだちでとんかつを食べてみたい。


最近、理由あってRuby, PHP, Javascript3つを同時に書かなければいけなくて頭が混乱しそう。

PHPのスクリプトをWEBサーバ経由で返すサーバからPHPファイルを取得し、キャッシュし(ファイルを作成して)実行するプログラムを書いてると、ふと気になった。

* ファイル作ってファイルシステム経由で読み込むよりも共有メモリでやるほうが断然早いよな。
* 共有メモリ上のデータをevalするほうが遅いか?ファイルシステムから読み込む方が遅いか?

ファイルシステムとか使うよりもダイナミックにロードする方がカッコイイが速度が気になる。。

で、作った。

SPEC
* eAcceleratorが入っている場合には共有メモリつかいますよ。
* 入っていない場合には、ファイルシステム使いますよ。
* 作成途中のファイルにアクセスが来ても不整合なファイルにアクセスすることが無いように、ロックかけるか作成してからリネームしましょう。
* ソースコード中の最初にある定数宣言でスクリプトレポジトリを設定してくださいよ。

ソースコード


// ScriptLoaderに関する設定
define('SC_UPDATE_SERVER_PATH', 'http://yourserver/');
define('SC_CACHE_DIR', 'caches/');
define('SC_CACHE_EXPIRED_MINUTES', 10);

/**
* ネットワーク越しにスクリプトをロードしキャッシュする
*/
class ScriptCache
{
/* 強制的にネットワークから読み込む */
var $_forceLoad = false;

function ScriptCache()
{
$this->_enableEAccelerator = ! is_callable("eaccelerator_load");
}

/**
* ファイルを読み込む
* キャッシュが有効な場合はキャッシュを読み込む
*
* @access public
* @param $file string 読み込むファイル名
*/
function fetch($file, $force_load=false)
{
$cach_file_name = $this->_getCachedFileName($file);
if($this->_enableEAccelerator) {
$cached_contents = eaccelerator_get($file);
if(!$cached_contents || $force_load) $this->_load($file);
eval($cached_contents);
} else {
if($force_load || !file_exists($cach_file_name) || (mktime() - filemtime($cach_file_name) > SC_CACHE_EXPIRED_MINUTES * 60)) $this->_load($file);
require_once $cach_file_name;
}
}

/**
* キャッシュファイルを削除する
*
* @access public
* @param $file string ファイル名
*/
function clear($file)
{
$cach_file_name = $this->_getCachedFileName($file);
if($this->_enableEAccelerator) {
eaccelerator_rm($file);
} else {
file_exists($cach_file_name) &&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp; unlink($cach_file_name);
}
}

/**
* ガベージコレクション
* 不要となった共有メモリ領域の開放
*/
function gc()
{
if($this->_enableEAccelerator) eaccelerator_gc();
}

/**
* ネットワーク経由でファイルを読み込みキャッシュする。
*
* @param $file string ファイル名
* @return boolean 失敗したらfalse
*/
function _load($file)
{
if($res = $this->_networkFetch(SC_UPDATE_SERVER_PATH. $file)) {
$tmp_filename = tempnam('','');
$cache_file_name = $this->_getCachedFileName($file);
if($this->_enableEAccelerator) {
eaccelerator_lock($file);
eaccelerator_put($file, preg_replace('/<\?(php:?)?|\?>/ims', '', $res['body']), SC_CACHE_EXPIRED_MINUTES * 60);
eaccelerator_unlock($file);
} else {
if($fp = fopen($tmp_filename, 'w')) {
if(fwrite($fp, $res['body']) === FALSE) {
return false;
}
fclose($fp);
rename($tmp_filename, $cache_file_name);
chmod($cache_file_name, 0644);
}
}
return true;
} else {
return false;
}
}

/**
* キャッシュファイル名の取得
*
* @access private
* @param $file string ファイル名
* @return string キャッシュファイル名
*/
function _getCachedFileName($file)
{
$cwd = getcwd();
return $cwd. '/'. SC_CACHE_DIR. $file;
}

function _networkFetch($url, $initheaders=array(), $timeout=10, $limit=10)
{
!is_array($url) && ($aUrl = parse_url($url));
$client =& new HTTP_Client();
$client->setDefaultHeader($initheaders);
$client->setMaxRedirects($limit);
$client->get($url, null, false);
if(PEAR::isError($res = $client->currentResponse())) {
return false;
}
return $res;
}
}
?>

で早速ベンチとってみた。

キャッシュせずに常にネットワーク上から読み込む。

ソースコード

start();
$sl->fetch('test.php', true);
$timer->stop();
ob_end_clean();

print '<pre>';
print_r($timer->getOutput());
print '</pre>';

$sl->gc();
?>

time indexex time%
Start1157032411.98688100-0.00%
Stop1157032412.023001000.036120100.00%
total-0.036120100.00%


ファイルシステムでキャッシュしてrequire
キャッシュタイプを切り替えるオプションは用意していないので、

$this->_enableEAccelerator = ! is_callable("eaccelerator_load");

のようにしてファイルキャッシュするようにする。


time indexex time%
Start1157032411.98688100-0.00%
Stop1157032412.023001000.036120100.00%
total-0.036120100.00%

で、次。



eAcceraletaor APIを使いメモリ上にキャッシュしてevalする。

time indexex time%
Start1157033202.28823800-0.00%
Stop1157033202.290016000.001778100.00%
total-0.001778100.00%

結果はファイルシステム経由で読み込んだ場合とほとんど同じ。


結論
PHPのevalは遅くない。
eAcceralerator API使ったが思ったほどパフォーマンスが出なかったのが気になる。

今日の昼飯は特選醤油トンカツ

東京-京都を交互に行き来する生活も4ヶ月目。
東京は、知り合いや友達からの情報があらかじめないと、美味しいものを食べれる店にたどりつくのが難しいと聞いていたのですが、納得。

今日の昼はとんかつ 武信分店に行ってきます。
すだちで食べる、特性醤油とんかつがうまそう。。

後、せもりなという国分寺のパスタが気になる。。
HPが無いのに数々のブログで紹介されていて、高い評価を受けている。
今週末あたり行ってみようかな。

せもりな
紹介されているブログ
http://usalog.cocolog-nifty.com/usalog/2004/09/post_28.html
http://oisiine.exblog.jp/3485243/

Tuesday, August 22, 2006

VMWareでホストOSと時刻の同期

備忘録。簡単な方法。


$ cd /usr/local/src/
# mkdir vmw
# wget http://chitchat.at.infoseek.co.jp/vmware/vmw.c http://chitchat.at.infoseek.co.jp/vmware/vmw_door.s
# gcc -O -DHAVE_CLOCK_SETTIME -o vmw vmw.c vmw_door.s -lrt
# ./vmw -t
# ln -s /usr/local/src/vmw/vmw /usr/bin/vmw
# vim /etc/crontab
0-59 * * * * root vmw -t 2>&1 /dev/null
を追加

Saturday, August 19, 2006

CSS3について覚え書き

CSS3についてメモ。

Border 関連のプロパティ

15. The 'border-color' properties
グラデーションの掛かったボーダーを実現する。

対応ブラウザ: Mozilla
div.box {
height: 15px;
padding: 10px;
width: 200px;
text-align: center;
}
div.border-color {
border: 10px solid #000;
-moz-border-bottom-colors: #000000 #003F09 #005F0D #008F13 #00CF1C #00EF20 #3FFF59 #6FFF82 #AFFFBA #DFFFE3;
-moz-border-top-colors: #000000 #003F09 #005F0D #008F13 #00CF1C #00EF20 #3FFF59 #6FFF82 #AFFFBA #DFFFE3;
-moz-border-left-colors: #000000 #003F09 #005F0D #008F13 #00CF1C #00EF20 #3FFF59 #6FFF82 #AFFFBA #DFFFE3;
-moz-border-right-colors: #000000 #003F09 #005F0D #008F13 #00CF1C #00EF20 #3FFF59 #6FFF82 #AFFFBA #DFFFE3;
}


-moz-border-<position>-colors:
プロパティでボーダーの色を複数指定できる。順に色合いを変えることでグラーデーション調の線を引くことが可能。


<div class="box border-color">
UIEvolution Japan.
</div>

サンプル

19. The 'border-radius' properties

CSSで角を丸くする

対応ブラウザ: Mozilla, Webkit 最新版

div.border-radius {
border:1px solid #000;
/* Mozilla */
-moz-border-radius-topleft: 10px;
-moz-border-radius-topright: 10px;
-moz-border-radius-bottomleft: 10px;
-moz-border-radius-bottomright: 10px;

/* Safari */
-webkit-border-left-radius: 10px;
-webkit-border-right-radius: 10px;
-webkit-border-top-radius: 10px;
-webkit-border-bottom-radius: 10px;
}


<div class="box border-radius">
UIEvolution Japan.
</div>

サンプル

3.4.1 The 'border-image' properties
通常のボーダーの変わりに画像を使用することが出来る。
コーナーと、コーナーとコーナーを結ぶ線の画像をそれぞれ指定することが出来る。

border-image: uri;
  • border-top-image
  • border-right-image
  • border-left-image
  • border-bottom-image
border-corner-image: uri;
  • border-top-left-image
  • border-top-right-image
  • border-bottom-left-image
  • border-bottom-bottom-image
ボーダー・コーナーともに個別に指定することが出来る。

最新版のWebkitでは、
-webkit-border-image:
でサポートしている様子。

3.26. Media Queries
Media Queryを使用すればより細かくデバイスに適用するスタイルを選択することが出来る。
media属性の指定とスタイルに対する指定でスタイルを適用するかどうか判定する。

対応ブラウザ: Operaと最新版のWebkit


/* Media query */
@media all and (min-width:650px){
div.box2#min-width {
background-color: #0300FF;
}
}
@media all and (max-width:1000px){
div.box2#max-width {
background-color: #0300FF;
}
}



<div class="box2 media-query" id="min-width">
650pxより大きいと青色
</div>
<div class="box2 media-query" id="max-width">
1000pxより小さいと青色
</div>


OperaとWebkitでウインドサイズを色々変えて表示してみてください。
サンプル

このMedia Queryを使用すれば、PCと携帯両方で動作するHPを簡単に作ることが出来そう。
スタイル属性にはディスプレイのサイズ・ディスプレイの高さ・ディスプレイの色の濃さを指定できる。
To describe in more detail what type of devices a style sheet applies to, this document proposes media queries. A media query consists of a media type and one or more expressions to limit the scope of a certain style sheet. Among the proposed media features that can be used in expressions are "width", "height" (size of the display), and "color" (color depth of the display).
とWorking Draft中に書かれている。

CSS3 module: Multi-column layout
対応ブラウザ: Mozillaのみ

3カラムレイアウトや2カラムレイアウトを簡単に作ることが出来ます。

div.multi-column{
-moz-column-with: 100px;
-moz-column-gap: 1em;
-moz-column-count: 3;
}


<div class="multi-column">In preparation for the release of Mozilla
Firefox 1.5 Beta 1, the tree will be locked down tonight at 11:59pm Pacific
Daylight Time (UTC -0700). After this point, no more checkins will be accepted
for Firefox 1.5 Beta 1, which is set for release on Thursday.

In preparation for the release of Mozilla
Firefox 1.5 Beta 1, the tree will be locked down tonight at 11:59pm Pacific
Daylight Time (UTC -0700). After this point, no more checkins will be accepted
for Firefox 1.5 Beta 1, which is set for release on Thursday.
</div>

一つのカラムの幅を100px、カラム間の空きを1em, カラム数を3つに指定します。

サンプル


7. Box Model addition
長い間、多くのWeb開発者やデザイナが苦しめられてきたボックスモデルの違い。
CSS2.1勧告のボックスモデルでは内容領域の高さと幅をあらわしますが、IEなどの一部のブラウザはボーダーやパディングも含めた領域を高さと幅とします。
CSS3で勧告されている、box-sizingプロパティは、どちらのボックスモデルを適用するかを指定することが出来ます。

box-sizing: content-box | border-box
content-box
要素の高さ(height)と幅(width)はCSS2.1勧告の視覚整形モデルに従う。指定された高さと幅は内容領域の高さと幅に適用される。パディングと高さは内容領域の外側に描かれる。

border-box
指定した幅と高さは、パディングとボーダーを含めた値になる。

9.1. 'resize' property
対応ブラウザ: 最新版のWebkitのみ

CSSのみで要素のリサイズを可能にします。
resize: both | horizontal | vertical;
bothを指定すると両方向にリサイズ可能。
horizontalを指定すると水平方向にリサイズ可能。
verticalを指定すると垂直方向にリサイズ可能。

サンプル

9.8. Text shadows: the 'text-shadow' property
対応ブラウザ: 最新版のWebkitのみ

Photoshopで作成できるような影付き文字の簡易版をCSSだけで作成することが出来ます。

text-shadow: 2px 2px 2px #ff0000;


CSS2から存在するが現在実装されているブラウザはWebkitだけなようです。
上記の設定では、右側に影を2px, 下側に影を2px、ぼかしを2pxで色は赤色。

サンプル


6.3.2. Substring matching attribute selectors
部分文字列でマッチ可能なセレクタが追加される。

[att^=val]
attがvalから始まる要素
[att$=val]
attがvalで終わる要素
[att*=val]
attが少なくとも一回はvalを含む要素

拡張子が.htmlのアンカ要素のみ取得するセレクタ
a[href$=".html"]


疲れたので、とりあえずこのへんで。

Thursday, June 22, 2006

[UIEvolution] YAMAKIをリリースしました





伊東での2泊3日地獄の開発合宿から帰宅。
ブレスト→開発。そして、一つのネタ?をリリース。

ページ丸ごとContent Editableにしてしまい、簡単に編集&保存が可能になるブックマークレットを作成しました。お世話になった旅館のロゴとリンク、さらにソースコードの著作権表示にも旅館へのリンクを入れることで、山喜旅館に還元しようという考えです。

YAMAKI

現在、MTでしか保存できませんが、他のブログ等でも保存できるように対応するかもしれません。
(GMで保存部分のパッチを作ってサブドメイン制限を越えるとか)
今後とも山喜旅館UIEvolution Japanをご愛好のほどよろしくお願いいたします。

Wednesday, June 14, 2006

[Rails] controller/model/viewを内包したプラグインの作り方

via http://habtm.com/articles/2006/06/11/having-your-plugins-and-eating-them-too

contoller, model, view, helperなどをMVCのサブセットを含むプラグインを作るときEnginesプラグインを使う以外にも$LOAD_PATHを上書きしてやるだけでそちらを読み込むようになる。

# init.rb

config.controller_paths << File.join(directory, 'app', 'controllers')
$LOAD_PATH << File.join(directory, 'app', 'controllers')
$LOAD_PATH << File.join(directory, 'app', 'models')
$LOAD_PATH << File.join(directory, 'app', 'helpers')


さらにcontrllerでviewのパスを次のように設定しないとtemplate missingになる。
LoginController.template_root = File.join(File.dirname(__FILE__), '..', 'views')

Wednesday, May 24, 2006

[IE 7] baseタグが効かないバグ

Internet Exploler 7.0.5346.5
では、baseタグが効かないようだ。

あらゆるCSSモードを試したわけではないが、自分の環境下ではbaseタグが効かないorz

[Rails] lighttpdとWEBRickでのRequestオブジェクトの挙動

開発環境はWEBRick、プロダクション環境はlighttpd+fcgiで運用しているという方も多いと思います。ここで一つ注意が必要なのは、requestを扱う際の注意です。

requestにはユーザからのHTTPリクエストをサーバが解析しRailsがrequestオブジェクトにマッピングします。そのため、WEBサーバの実装によって一部値が変わってきます。
requestオブジェクトのプロパティの値で処理を分岐させるなどしている場合は、うまく動作しません。

同一のソースをlighttpd + fcgiとWEBRickで動作させた場合のrequestオブジェクトを比較してみます。(どちらもRAILS_ENV => development)

http://rakuto/testにアクセスしたとして話を進めます。
lighttpdは80番、WEBRickでは3000番ポートで動作させます。


lighttpd + fcgi


{"SERVER_NAME"=>"rakuto", "PATH_INFO"=>"", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3",
"HTTP_ACCEPT_ENCODING"=>"gzip,deflate", "SERVER_ADDR"=>"222.230.136.50",
"SCRIPT_NAME"=>"/dispatch.fcgi", "REDIRECT_URI"=>"/dispatch.fcgi",
"SERVER_PROTOCOL"=>"HTTP/1.1", "HTTP_HOST"=>"rakuto",
"HTTP_ACCEPT_LANGUAGE"=>"ja,en-us;q=0.7,en;q=0.3",
"SERVER_SOFTWARE"=>"lighttpd/1.4.11", "REMOTE_ADDR"=>"222.230.136.61",
"HTTP_KEEP_ALIVE"=>"300", "HTTP_ACCEPT_CHARSET"=>"Shift_JIS,utf-8;q=0.7,*;q=0.7",
"HTTP_COOKIE"=>"__utma=146761718.1802360063.1148377808.1148377808.1148439243.2;
_session_id=1b956cee50de1e32e9c1762b1c9df19e;
__utmz=146761718.1148377808.1.1.utmccn=(direct)|utmcsr=(direct)|utmcmd=(none);
username=rakuto; __utmc=146761718",
"DOCUMENT_ROOT"=>"/var/www/rakuto/current/public", "REQUEST_URI"=>"/test/",
"SERVER_PORT"=>"80", "GATEWAY_INTERFACE"=>"CGI/1.1", "REMOTE_PORT"=>"38429",
"QUERY_STRING"=>"",
"SCRIPT_FILENAME"=>"/var/www/rakuto/current/public/dispatch.fcgi",
"HTTP_ACCEPT"=>"text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
"REQUEST_METHOD"=>"GET", "REDIRECT_STATUS"=>"200", "HTTP_CONNECTION"=>"keep-alive"}


WEBRick

{"SERVER_NAME"=>"rakuto", "PATH_INFO"=>"/test/", "REMOTE_HOST"=>"222.230.136.61",
"HTTP_ACCEPT_ENCODING"=>"gzip,deflate", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Windows; U;
Windows NT 5.1; ja; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3",
"SERVER_PROTOCOL"=>"HTTP/1.1", "HTTP_ACCEPT_LANGUAGE"=>"ja,en-us;q=0.7,en;q=0.3",
"HTTP_HOST"=>"rakuto:3000", "REMOTE_ADDR"=>"222.230.136.61",
"SERVER_SOFTWARE"=>"WEBrick/1.3.1 (Ruby/1.8.4/2006-04-28)", "HTTP_KEEP_ALIVE"=>"300",
"HTTP_COOKIE"=>"__utma=146761718.1802360063.1148377808.1148377808.1148439243.2;
_session_id=1b956cee50de1e32e9c1762b1c9df19e;
__utmz=146761718.1148377808.1.1.utmccn=(direct)|utmcsr=(direct)|utmcmd=(none);
username=rakuto; __utmc=146761718",
"HTTP_ACCEPT_CHARSET"=>"Shift_JIS,utf-8;q=0.7,*;q=0.7",
"REQUEST_URI"=>"http://rakuto:3000/test/", "SERVER_PORT"=>"3000",
"GATEWAY_INTERFACE"=>"CGI/1.1", "QUERY_STRING"=>nil, "REMOTE_USER"=>nil,
"HTTP_ACCEPT"=>"text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
"REQUEST_METHOD"=>"GET", "HTTP_CONNECTION"=>"keep-alive"}


おまけで、Mongrelも。
Mongrelの場合は、自分の環境ではWEBRickで動作させたときと挙動は同じでした。

{"SERVER_NAME"=>"rakuto", "PATH_INFO"=>"/test",
"HTTP_ACCEPT_ENCODING"=>"gzip,deflate", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3", "SCRIPT_NAME"=>"/",
"SERVER_PROTOCOL"=>"HTTP/1.1", "HTTP_ACCEPT_LANGUAGE"=>"ja,en-us;q=0.7,en;q=0.3",
"HTTP_HOST"=>"rakuto:3000", "REMOTE_ADDR"=>"192.168.255.1",
"SERVER_SOFTWARE"=>"Mongrel 0.3.12.4", "HTTP_KEEP_ALIVE"=>"300",
"HTTP_COOKIE"=>"__utma=124112458.1769474473.1147068425.1147768112.1147937337.13;
_session_id=24d7dc5809d572486716edac18f59a33;
__utmz=124112458.1147068425.1.1.utmccn=(direct)|utmcsr=(direct)|utmcmd=(none);
username=rakuto", "HTTP_ACCEPT_CHARSET"=>"Shift_JIS,utf-8;q=0.7,*;q=0.7",
"HTTP_VERSION"=>"HTTP/1.1", "REQUEST_URI"=>"/test/",
"SERVER_PORT"=>"3000", "GATEWAY_INTERFACE"=>"CGI/1.2",
"HTTP_ACCEPT"=>"text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
"HTTP_CONNECTION"=>"keep-alive", "REQUEST_METHOD"=>"GET"}


まず一つ目の違い。
request.env["QUERY_STRING"]の値が異なります。

http://rakuto/testでアクセスしてみます。

"QUERY_STRING"=>"" # lighttpd + fcgi
"QUERY_STRING"=>nil # webcik
# => Mongrel プロパティが存在しない


あれ、lighttpdではQUERY_STRINGが空のときはnilではなくて空文字列になるんだ。

次に、htttp://rakuto/test?q=hogeでアクセスしてみます。

"QUERY_STRING"=>"" # lighttpd + fcgi
"QUERY_STRING"=>"q=hoge" # webcik


・・・lighttpd + fcgiの場合は、request.evn["QUERY_STRING"]は使えないようですね。

controller内でQUERY_STRINGの値でその後の処理を期待した、コードを書いていたらダメポと言うことです。WEBRickからlighttpd環境に移したときに正常に動作しません。

class IndexController < ApplicationController
def index
query_string =request.env["QUERY_STRING"].nil?? "" : request.env['QUERY_STRING']}
・・・
end
end



二つ目の違い
request.env["PATH_INFO"]の値もlighttpd+fcgi環境下では正常に設定されません。


"PATH_INFO"=>"" # lighttpd + fcgi
"PATH_INFO"=>"/test" # WEBRick


結論
request.env内の値は、WEBサーバの実装依存。この値を元にロジックを実装するのは危険!ということを学びました。

Saturday, May 20, 2006

[JavaScript]MarkUpBuilder - DOMの生成

JavascriptでのDOM生成について、調べていたところ、マークアップビルダ、存在しないメソッドの記事を見つけて、SpiderMonkey以外のJavascript実装のブラウザでも汎用的に使えるように、実装してみました。

Prototype.jsに依存しています。


var MarkupBuilder = Class.create();
MarkupBuilder.DOM = ["ul", "ol", "li", "td", "tr", "thead", "tbody", "tfoot", "table", "th",
"tbody", "input", "span", "p", "a", "div", "img", "textarea", "label", "script", "iframe"]
MarkupBuilder.cache = null;
MarkupBuilder.UA = navigator.userAgent.toLowerCase();
MarkupBuilder.prototype = {
__noSuchMethod__ : function(name, args) {
var element = document.createElement(name);
var attrs = args[0] || {};
var bMSIE = MarkupBuilder.UA.indexOf("msie") != -1;
for(var name in attrs) {
if(bMSIE && name.indexOf('on') != -1) // IE setAttribute bug
attrs[name] = new Function(attrs[name]);
else if(!bMSIE && name == "className"){
var _class = attrs[name], name = "class";
attrs[name] = _class;
}
try {
if(attrs.hasOwnProperty(name)) element.setAttribute(name, attrs[name]);
} catch(e) {throw e;}
}
for(var i=1, len=args.length;i<len;++i) {
if(args[i] instanceof Array)
for(var j=0, len2=args[i].length;j<len2;++j) element.appendChild(args[i][j]);
else
if(typeof args[i] == "string" || args[i] instanceof String) element.innerHTML += args[i];
else element.appendChild(args[i]);
}
return element;
},
initialize : function() {
if(MarkupBuilder.cache) return MarkupBuilder.cache;
if(!navigator.userAgent.match(/Gecko/)) {
var proto = $A(MarkupBuilder.DOM).inject({}, function(methods, tagName, idx) {
methods[tagName] = function() {return this.__noSuchMethod__(tagName, arguments)};
return methods;});
if(this.__proto__) this.__proto__ = proto;
else Object.extend(MarkupBuilder.prototype, proto);
}
MarkupBuilder.cache = this;
}
}

追記(2006/05/20):
・IEではsetAttributeメソッドでイベントリスナが登録できないバグに対応
・効率化のためにシングルトンにした。
・IE以外では、classNammをclassに変換。
・DOMの一次元配列を渡せるようにした
・HTMLを挿入できるようにした


使用例

var b = new MarkupBuilder();
var dom = b.div({id: "id1", className: "class1"},"divInnerText",
b.span({id: "id2", className: "class2"}, "spanInnerText"),
b.a({id: "id3", onclick: "return false;", href: ""}, "link"));

生成されるHTML

<div class="class1" id="id1">
divInnerText
<span class="class2" id="id2"> spanInnerText </span>
<a href="" onclick="return false;" id="id3"> link
</div>

SpiderMonkey系のブラウザでは、__noSuchMethod__を使用し、それ以外のブラウザではMarkupBuilderを拡張しています。
IEではobj.__proto__が使用できないので、Object.extend()を使って拡張しています。

Firefox1.5とIE6では動作確認していますが、バグがあるかもです。


追記
随分久しぶりの投稿になってしまいました。。
現在、「UIE Japan、今度は「組み込みエンジニア」募集」で紹介されているサービスを実装中です。今までに無い面白いサービスを6月末あたりにリリースしますのでお楽しみに。

JavaScriptを本格的にやり始めてから2ヶ月がたちました。
面白いですね。と言うことで、リリース予定の新しいサービスはJavaScriptゴリゴリです。

Monday, April 24, 2006

ドリコム上場記念パーティ

言い訳をすると忙しくてブログをかけていない。
でも友達が読んでくれたりしていて、「がんばってるねんなぁ」とか、何をやっているか知っていてくれるとすごくうれしい気持ちになるので、日記を書こうと思う。


最近、急速なペースで自分の周りの環境が動き始めた。東京で働くようになり、半月はこちらにいることになった。仕事以外にもイベントや勉強会に積極的に参加することになった。

大学も4回生になった。友達とも就職活動の話をしたり、将来の話を語ることも多くなった。
みんな色々な夢を持っていることに刺激される。自分とは全く違う道を進む友達がいることは、非常に面白い。(一般的な)大企業を目指すやつもいれば、自分でファッションブランドを立ち上げるやつもいる。お笑いをがんばっているやつもいる。みんな素敵だと思うし刺激される。

今月は、4月15日のRails勉強会に参加した足で、東京に来た。
28日まで東京で過ごす予定である。

4月になって新年度も始まり、急速なスピードで自分の環境が変わっていく。
京都ベースで一つの会社でアルバイトをしていたのが、UIEvolutionという東京に日本本社を置く会社でも働くようになり、中島さん・今野さんと新しい人とも出会え日々刺激を受けている。

彼女は東京に転勤になり、1年半の同棲とも別れを告げた。一人暮らしすることも考えたが、来年は東京に来ることと半分は東京で過ごすことともあり、結局実家に戻った。


そして、2006年4月21日金曜日ドリコム上場記念パーティが開かれた。
第1部・第2部・第3部とあり、それぞれ内部関係者のみ、外部の人も、新卒内定者歓迎となっていた。
内部関係者なので、第1部から参加した。

第2部では、エルビス・プレスリーの仮装をした。
仕事がいっぱいで、パーティーの打ち合わせに1度も参加できていなかったので段取りはほとんで把握していなかった。



















ドリコムに出会ったのは、2003年の立命館大学の新歓祭だった。内藤社長と井上CTOの二人がビラ配りをしているのに、僕が食いついたわけだった。18歳のときだった。

あれから、3年がたった。内藤社長も3年という期間、ドリコムを作り上げてきた。そして、上場するという大きな成功を収めたが、これからが本当のスタート地点であるという認識はみな共通している。

パーティーは「ドリコムらしかったね」と言う評価をいただいて、成功だったと聞いた。
来期新卒内定者は、僕と同い年である。立命館大学も多かったし、VBCの知り合いもきていた。
「男は元気な感じの人」「女の子は派手な感じの子」が多いように感じた。

新卒内定者ともそれほど話さなかったので、詳しくはわからないが、エッジの利いた人間は僕の話した中には感じなかった。倍率が40倍とか、半端ではなかったらしいことを社長から聞いた。

「ドリコムという企業は人気なんだな。」
素直にそう感じた。古くからドリコムの内部で働いている人の感覚と、世間一般的なドリコムのイメージは大きくずれているのかもしれない。こういうパーティー系な感じが、経営学部の畑を持っている子や、イケメン社長がかわいい女の子にはウケルのだろう。

2年前の僕なら、この会社に魅力を感じただろうし、今回来ている人達は「デキル」と感じたと思う。
かわいい子も多いし。(技術系の会社にはありえないw)

でも、現在は一人のエンジニアとしてすごく寂しく感じた。

それは、自分の目が肥えたとかではなく、
「ただ、できるな」とか「こいつらとものづくりをしたい」と思う感覚が大きく変わっただけである。
「ういっす!」とかノリがやたら高いとかそういう人間ができるんだと、感じていた2年前の自分から、強烈にエッジのきいた人間って面白いって感じるようになった。

例えば、はてなには、かわいい女の子や、パーティー系な人間は集まらないだろう。

「エンジニア」が魅力を感じる会社つくり、そのへんをもっともっと叫んでいかなければならないし、営業が「時代はWeb2.0ですよ」と得意気に叫ぶような会社になることは、絶対阻止するべきだと僕は考える。

モノつくりをする、エンジニアが大事にされるべきである。
それでないとよいものは生まれない。

パーティーの翌日から2日間、熱海でドリコムの技術合宿に行ってきた。
今後の方針等に関して意見をくみかわす場である。
そして、本日の夕方に東京に戻ってきた。


内藤社長とも前回ご飯に誘ってもらった時につづき、今回もドリコムとして、社長として考えているビジョンを聞けて楽しかった。

「社長の言うことだろうが、会長の言うことだろうが、平気で反対し意見を覆す」
「みんなが絶対反対するようなサービスを企画する」
そういうエッジの利いた人間になっていくのが今年の実現目標である。

Saturday, April 01, 2006

お世話になっている社長

2週間こちらに滞在しました。今日京都に戻ります。
エイプリルフールを見届けて、ドリコムの東京オフィスの引越しを手伝ってから帰ることになりました。
YAPCにも参加してきました。

http://www.uievolution.co.jp/
UIEJのHPが開設しました。エイプリルフール限定バージョンですのでお早めにどぞ。

http://satoshi.blogs.com/life/2006/03/uie_japan_.html
http://d.hatena.ne.jp/junkonno/
さらに、中島さんと今野さんのブログのトップページにもデカデカとメイドとのペアプログラミングの写真がアップされましたw一躍有名人ですね。これ。
リアルに私設メイドを雇うのが今期の目標です。

「かたもみもみ」サイコーっすよ。
































お世話になっている社長方々w

UIEJからは、エイプリルフールのおみやげがあります。

Wednesday, March 15, 2006

21歳になりました

3月12日で21歳になりました。

21 * 365 = 7635

20歳の時期なんてあっという間にすぎ、ついこの間「今日から20歳になった」といってた気がする。
うるう年除くとわずか7635日ぐらいしか過ごしてないことに素で驚きました。

Le Sud Hayakawa

夜は、ここで彼女にご馳走になりました。場所は京都三条御幸町です。
なんとも贅沢な空間使いで、スペースの割りにテーブルが少なくゆったりできます。

エスカルゴがウマイ!
サザエみたいでおいしかった。

ワインをボトル2本(赤・白一本ずつ。名前忘れた。) 開け、お勧めのブランデーをオーダし・・・とまぁよく飲んだのでウェイターさんがチーズだり、ドライフルーツなりいろいろとサービスしてくれました。

かなり満腹だったのですが、三条のちんゆうの看板を見つけ、
ちんゆうにかけこみラーメンを完食。
翌日、暴飲暴食のため胃が痛くなる。。このへんが21歳なんだろう(笑

3年間使っていた財布から、
BOTTEGA VENETTAのイントレチャート(編みこみ)の財布に買い換えました。

BOTTEGAに爬虫類の絵が描いていて、鼻緒?の部分がわに革になっているサンダルがめちゃめちゃカッコよかった・・。でも、かかとの減りが早くすぐ靴をだめにしてしまうたちので、もって3ヶ月。
と考えると、5万のサンダルは買えなかった(笑

男のステータス的に見られるのが、
・時計
・ペン
・財布
の三つのアイテムだそう。

BOTTEGAもイントレチャートが特徴のため、見たらそれとすぐそれとわかるらしい。
時計はなかなか手がでませんね。。フランクミューラーのロングアイランドカッコヨス。
ペン?確かにペンはかっこいいよ。カードのサインのときにマイペンてかっこいいよ。
・・ジャケットに内ポケットついてないねんけど。

優秀な人ほど、それほど外見に気を使っていない人が多いのがこの業界。
自分の中の興味をすべてコンピュータに注ぎ込むため、他に興味を持たないからだろうな。
そういう方向を目指すのではなく、目指せ、ちょいわるデベロッパ。

18日まで赤倉温泉でボード。
18日夜は大阪でm-flow, sold outらの集まるライブに参加。これ楽しみ。
19日から末までまた、東京でUIEのお仕事。

Tuesday, March 14, 2006

セッションをMySQLのオンメモリストレージに載せるための考察

MySQL5.0.3からVARCHARのMAXが65535byteまで増えたという情報を入手。

http://dev.mysql.com/doc/refman/5.0/en/char.html
http://dev.mysql.com/doc/refman/5.0/en/news-5-0-3.html

この二つの記事中の以下の情報をマージすると・・・

MEMORY (HEAP) can have VARCHAR() fields.
The length can be specified as a value from 0 to 255 before MySQL 5.0.3, and 0 to 65,535 in 5.0.3 and later versions.


HEAPエンジンでは、VARCHARが使えるようになりましたよ。
んでもって、MySQL5.0.3からVARCHAR型は、最大65,535byteまで格納できますよと。

で、実際に次のようなクエリを発行。

CREATE TABLE sessions_heap (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, `session_id` varchar(255), `data` varchar(65500), `updated_at` datetime)engine=heap;

ERROR 1163 (42000): The used table type doesn't support BLOB/TEXT columns


さてさて、雲行きが怪しくなってきました。
同じクエリをengine=MyISAMとかにすればクエリは問題なく実行できます。
どうやらVARCHARのMAX以外の制限に依存していそうです。

で次は、VARCHARのサイズを減らして実行。

CREATE TABLE sessions_heap (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, `session_id` varchar(255), `data` varchar(20000), `updated_at` datetime)engine=heap;

Query OK, 0 rows affected (0.01 sec)


どうやら、VARCHARは確かにサポートしている様子。
65500だとTEXT/BLOB型だと見なされてエラーになる。

そこで、VARCHARの限界値を探ってみた。

CREATE TABLE sessions_heap (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, `session_id` varchar(255), `data` varchar(21800), `updated_at` datetime)engine=heap;

ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. You have to change some columns to TEXT or BLOBs


先程と結果が変わった。
なるほど。HEAPエンジンの一行あたりのサイズの制限に引っ掛かった様子。もういちど、

http://dev.mysql.com/doc/refman/5.0/en/char.html
もう一度読み返してみることに。

The length can be specified as a value from 0 to 255 before MySQL 5.0.3, and 0 to 65,535 in 5.0.3 and later versions. (The maximum effective length of a VARCHAR in MySQL 5.0.3 and later is determined by the maximum row size and the character set used. The maximum length overall is 65,532 bytes.)


あ~あ。完全に読み飛ばしていた。
VARCHARの制限の前に、一行あたりの最大サイズの制限に引っ掛かる様子。HEAPエンジンは内部では、BTREEインデクスやHASHと同じ形なので制限が厳しい様子。

ちなみに、VARCHAR(21000)なら問題なく実行できる。
21000byte -> 21k
セッションデータ入れるには、ちょい厳しいか・・。

そしてtmpfsの話も少しだけ。
これもスレーブ上のMyISAMのテーブルを設置する以外は使い道がなさそう。セッションテーブルをtmpfsで運用するためには、障害時のことを考えて何かしら対策を打っておかないとデータが逝ってしまう。

セッションテーブルのレプリケーション先は、InnoDB on ext3にしておいてディスクに残すとかかな。

なかなかうまくいかない。。

Thursday, March 09, 2006

[Rails plugin] Rails session with MySQL MEMORY storage

--------------------------------------------------------------
今日の午前。

RailsのSession DataをInnoDB&MEMORYストレージ両方に書き込むプラグイン。

参照は、sessions_heapテーブルに。
更新は、sessionsとsessions_heapに。

使い方は、enviroment.rbの編集と、rake create_sessions_tableのみ。
さらに、DBに保存してしまえばWEB負荷分散時の心配もいらず。
美しい。今から作ろう。

セッションデータは、厳密な同期が求められる?
からマスタに問い合わせた方がいいのかなぁ。
--------------------------------------------------------------

今日の午後。

死亡_ト ̄|...........○

7.4. HEAP テーブル

プラグインの開発が万事うまくいき、セッションテーブル作成用のtaskも書き、
さぁLet's rake.

[furutani@furutani]/site/sandbox% rake create_sessions_table_with_heap
(in /site/sandbox)
rake aborted!
Mysql::Error: #42000The used table type doesn't support BLOB/TEXT columns: CREATE TABLE sessions_heap (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, `session_id` varchar(255), `data` text, `updated_at` datetime) ENGINE=HEAP

(See full trace by running task with --trace)
[furutani@furutani]/site/sandbox%


はいはい、そうですか。知らなかった。。
今日の一日は一体・・・。

tmpfsにon diskのテーブル載せるよりもHEAPの方が2倍近く速いって言ってる人がいたのに。
名案募集中です。

Wednesday, March 08, 2006

ActiveRecord Cluster (複数データベースを扱うプラグイン)

Active Record Cluster

ttp://rakuto.nobody.jp/src/active_record_cluster.zip

直接リンクだと403になります。
右クリックでURLをコピーして直接URLバーに入力してください。

を作りました。

で、これは何かって言うと

DBコネクションをクラスタ単位で扱うためのプラグイン。
「DB一台じゃクエリ捌くの無理だろ」って時に使います。

だけど、ActiveRecordは複数DBを扱うのに向いていないんですよ。
というかRails作者のDavid自身が一つのDBでARを使うことを強くお勧めしています。

そのため内部では、クラス名をハッシュキーとスレッドIDをキーとしてコネクションアダプタ(ActiveRecord::ConnectionAdapter)をキャッシュしています。

ActiveRecord Clusterでは、クラスタ毎に
「更新系リクエストのためのコネクションアダプタ」
「参照系リクエストのためのコネクションアダプタ」
を同時に管理できるように、キャッシュ機構を設けました。

個人用途ではあまり意味無さげ。
社内ではSVNレポジトリ立てたんだけど、外部に公開できるサーバ持って無いので、
ダウンロードして手動で/path/to/rails-app/vendor/plugins以下に配置します。

---------
更新履歴
---------
2005/02/16
スケジューリングアルゴリズムモジュールを切り離し、
コネクションのキャッシュを一つのメソッドで一元管理するようにしてDRY(Don't repeat your self).

2005/02/17
○コネクションの冗長化を実装しました。
(参照系リクエストの場合にスレーブ障害が起きていて接続できない場合、スレーブを接続対象から切り離し他のスレーブに接続を試みる。全てのスレーブがダウンしている場合は、マスタに対して接続を試みる。)

○ClusterモジュールをMixinして拡張を行う方法に変更。
(以前はActiveRecord::Baseに特異クラスを定義していた。)

2005/03/07
○Active Record Clusterの#delete or #delete_all呼び出しの際にコネクションをマスタに付け替える実装を付け加えました。
○バグ修正

ソースコード: Active Record Cluster

READMEを引用
== Welcome to Active Record Cluster

Active Record Clusterは、Active Recordを拡張するプラグインです。
一つマスタと複数台のスレーブを一つのクラスタとして扱い、
クラスタに対してコネクションを獲得することが出来るように拡張しています。

設定したクラスタへのコネクションで更新系クエリは全てマスタに対して
獲得され、参照系のクエリはスレーブ間で分散されます。

スレーブ間での負荷分散のアルゴリズムは現在のところラウンドロビンスケジューリングのみをサポートしています。

冗長化機能について (参照系のリクエストのみ)
接続先のホストに障害が発生していて接続できない場合、他のスレーブに対して
障害が起きているホストをクラスタから切り離し、他のスレーブに対して接続を試みます。
もし、全てのスレーブがダウンしていた場合は、マスタに対してコネクションを確立します。

Author:: rakuto (http://rakuto.blogspot.com/)
Copyright 2006 rakuto (http://rakuto.blogspot.com/)
License:: MIT

== Getting start
1. 始めにclusters.ymlというファイルを編集する必要があります。このファイルにはDBのクラスタ情報を書いておきます。書けたら#{RAILS_ROOT}/config/clusters.ymlにデプロイでOKです.

2. 次にclusters.ymlのクラスタ設定ファイル中に設定したホスト情報の接続情報を#{RAILS_ROOT}/config/database.ymlに書きます。文法はRailsのdatabse.ymlと同じです。

3. 使用するApplicationController(デフォルトなら#{RAILS_ROOT}/app/application.rb)中にクラスタ情報の初期化命令をbefore_filter(ActionController::Filters::ClassMethods)のコールバックメソッドとして指定します。

class ApplicationController < ActionController::Base
before_filter :init_cluster

def init_cluster
ActiveRecord::Base.initialize_clusters
end
end

こんな感じです。

4. 後は、各モデルで接続先のクラスタを指定します。クラスタの指定にはspecify_clusterメソッドを使用します。

class Hoges < ActiveRecord::Base
specify_cluster :hoge_cluster01
end

ID等の条件で一つのテーブルを複数ホストに分割したい場合は、コールバックメソッドを定義する。コールバックメソッドでは、接続先のクラスタ名を返す。

class Hoges < ActiveRecord::Base
specify_cluster_condition :condition

def conditon
if self.id %lt; 1000000
'hoge_cluster01'
else
'hoge_cluster02'
end
end
end

ですがこの機能はまだテストしていません。
次のバージョンで動作するようにします。はい。


== config file format

1. clusters.yml
次のようなフォーマットになります。
スレーブは省略できますが、マスタは省略できません。(そんな構成はないので。)
ルートの要素は、#{RAILS_ENV}の値にしてください。
ここに書いたホストへの接続情報をdatabase.ymlに指定します。

production:
hoge_cluster01:
master: master-db001
slaves:
- slave-db001
- slave-db002
- slave-db003

2. database.yml
指定するスペックはRailsと同じです。

master-db001:
host: master-db001
user: david
database: transaction-db

5. TODO
ID等の条件による接続先クラスタの切り替え
UnitTestの作成.



Tags:  

Monday, March 06, 2006

[Vol.3] RailsとMySQLによる大規模サイト構築実験 

MySQL + Railsで大規模サイト構築実験もいよいよ大詰めです。

前回までは、
DB : Active-Active
LB : Active-Passive

構成を想定して構築してきました。

また、レプリケーションの遅れによる更新の衝突を避けるための解をいくつか紹介しました。

だがActive-Active構成にするコストが大きい!!
「そもそもActive-Active構成で組む必要があるのかどうか?」といった疑問が沸いてきました。

調査を進めたところ、DBのActive-Active構成にするコストに対して、メリットが少ないことが判明。

[Active-Active構成を組むメリット]
瞬間的なコネクション増への対応が可能。
コネクションの負荷分散のみ可能。(I/Oの負荷分散はできない)

[Active-Active構成を組むデメリット]
更新系クエリの負荷分散は不可。
レプリケーション遅延による更新の衝突の問題。
厳密な同期を要するアプリでは、どちらのDBにもレコードを問い合わせる必要がある。
レプリケーション遅延の度合いを監視する必要がある。

以上を考慮した結果、
DBもActive-Passive構成で組むという結論に至ることになりました。

[MySQLをActive-Standby構成で組む]
MySQLをActive-Passive構成で組むために、ldirectordを少しハック的に使います。

2台の実MasterサーバとVIPを
real server1: 192.168.10.219
real server2: 192.168.10.230
VIP : 192.168.10.226

と定義する。

ldirectord.cfの設定で、通常なら
virtual=192.168.10.226:mysql
real=192.168.10.219:mysql gate 10
real=192.168.10.230:mysql gate 10
fallback=127.0.0.1:mysql gate

とするところを

virtual=192.168.10.226:mysql
real=192.168.10.219:mysql gate 10
fallback=192.168.10.230:mysql gate 10

とする。

このマジックは、fallbackサーバにスタンバイサーバを指定することによりActive-Passive構成を実現します。

※fallbackサーバは、全ての実サーバに接続できないときに接続するサーバです。通常はローカルホストを指定します。


これでActiveのMySQLが落ちた場合にスタンバイ側にコネクションが確立されるようになり、高可用性を実現できました。

WARNING!!!!
一つ注意するポイントがあります。この問題でまる2日ハマりました。
LBを配置しているホストにDBもWEBも配置している場合、WEBからDBへのリクエストはLBを経由して、DBにフォワードされます。この構成では、ダイレクトルーティング使用時、リクエストのフォワーディングに失敗します。

上の状況、つまりロードバランサー(Linux directord)がゲートウェイになりうる時は、パケットの送信元がVIPになるが、LB自体のインタフェースにもVIPが設定されているため偽装さ れたパケットとみなさ、ldirectordによって廃棄されるという罠があります。

詳しくは、
http://ultramonkey.jp/papers/lvs_tutorial/html/

-- 引用 --
しかし、場合によっては(Linux Directorが、本当に実サーバのネットワークのゲートウェイである場合など)、実サーバからの返信パケットをLinux Director経由でルーティングすることが望ましいこともあります。このようなパケットの送信元アドレスはVIPになります。ただし、このVIPは Linux Directorのインターフェイスに属しているので、Linux Directorはパケットが偽造されていると見なしてこれを破棄してしまいます。

この問題を解決する方法はいくつかあります。最もよいのは、Julian Anastasov提供のカーネル・パッチを適用することです。このパッチは、インターフェイスごとにパケット破棄の動作を無効にすることができる procエントリを追加します。このパッチはhttp://www.ssi.bg/~ja/#lvsgwから入手できます。



[更新系の負荷分散]
更新系の負荷分散は、Vol1でも書いたとおり、クラスタ自体のパーティションにより実現します。クラスタのパーティションには二つあります。

・役割に基づくパーティション
・データに基づくパーティション

現在のバージョンのActive Record Clusterでは、役割に基づくパーティションのみサポートしています。


これでMySQL+オープンソースプロダクトを用いたMySQLクラスタの実現に成功しました。
Active-Recordと組み合わせれば、大規模サイトの構築も簡単にできてしまいます。
レプリケーション遅延による自動増分キーの衝突も発生しません。

・・・すばらしい(笑

[Vol.2] RailsとMySQLによる大規模サイト構築実験

vol2でも引き続き、負荷分散・高可用性を備えるDBとWEBアプリケーションフレームワーク(以下AF)のセットアップを目指します。



デュアルマスタ構成に複数台のスレーブがぶら下がっています。
もう少し、詳しく今考えているアーキテクトを見ていきたいと思います。

最小構成: マスタ2台, スレーブ1台

最低3台のDBを用意します。
なぜ3台か?スレーブを追加する際には、マスタのスナップショット取得のためにマスタへの更新を停止する必要があります。データ量が増えると、マスタを停止してスナップショットを取得するには、長時間を要するため、これは現実的な解ではない。

そこで、スレーブをマスタのスナップショットとして考え、スレーブへのレプリケーションを一時停止し、スレーブのスナップショットを新規に用意したスレーブにコピーしレプリケーションを設定する。

スレーブが1台しか存在しない場合でのスレーブ停止時は、参照系のクエリは、全てマスタに向けられるようにActive Record Clusterを実装しています。

また、ロードバランサー自体も冗長化され2台のマスタ上にActive-Passive構成で配置されています。

デュアルマスタ構成構築に当たっての注意点
デュアルマスタは、すべての問題を万事解決する銀の弾丸では無いということを理解しておかなければなりません。
具体的には、次のような問題を念頭に入れておくべきです。

・更新がリアルタイムで反映されるわけではない

つまり、レプリケーション遅延がクリティカルになるようなサービスには向かないということです。でも、現状のBtoCサービスを見る限りおそらくこれは、クリティカルな問題ではないと思ってます。

レプリケーション遅延時間の許容値
レプリケーション遅延時間の許容値を設定するのは非常に難しいです。
また、レプリケーションの遅れは定期的に監視しておくべきです。

・レプリケーション遅延時間の監視

最新のタイムスタンプのみを格納するテーブルを一つ用意し、このテーブルに対して一定の間隔でマスタがレコードを挿入するようにする。そして監視プロセスが一定間隔置きでこのレコードを読み出し→タイムスタンプを調べる。

→Nagios経由で監視するのがベター。

レプリケーション遅延が大きくなってきたときの解決策。
レプリケーション遅延が許容できなくなるぐらい大きくなったときは、もう一台クラスタを用意してテーブルを分割→負荷分散する。

[vol.1]で、次のような考察点が上がりました。
考察1 - 参照系クエリもLBで振り分けるようにするか
考察2 - マスタ間でのレプリケーション遅延により発生する、
      AUTO_INCREMENTシーケンスの衝突問題を回避するための対策
考察3 - テーブル設計の工夫

以上の考察点をもう少しブラッシュアップ。


考察1
参照系のクエリをAFで振り分けるのか?LBで振り分けるのか?

LBをセットアップするのが面倒。
AF側でリクエストの負荷分散を管理する方がよさそうである。


考察2
デュアルマスタのアーキテクトを選択することにより、AUTO_INCREMENTカラムのシーケンスの衝突が発生する問題である。レプリケーションが完了する前に各マスタがINSERTクエリを受け付けシーケンスを割り当ててしまい、レプリケーション時にキーが衝突し一意制約に反するため、レプリケーションに失敗し同期できなくなる。

現在の解決方法は二つである。
・ID値として採用しているAUTO_INCREMENT属性をやめ、MD5ハッシュを使用する。
→レプリケーション遅延によるキーの衝突はなくなりそう。

・外部のライブラリを使用し、グローバルな固有識別子(GUID)を使用する。

しかし、どちらも現実的な解ではなさげ。

もう少し、ハックなアプローチを紹介。
1. マルチパートキー
2. AUTO_INCREMENTフィールドの拡張

マルチパートキー
一つのプライマリキーを用いるのではなく、AUTO_INCREMENTなプライマリキーとMySQLで定義するserver_idのマルチパートキーをプライマリキーとして使用する。

では、INSERTのケースの場合どのようにserver-idを知ることが出来るだろうか?
MySQLでは、SERVER_ID()関数を使って自身のサーバIDを取得することが出来る。
例えば、

INSERT INTO drecoms values (SERVER_ID(), 'naito', 'naito@drecom.co.jp');

しかし、この方法には大きな問題点がある。。
→マルチパート自動増分キーはMyISAMしか使えない。
→外部キーの設定が難しくなる。

マスタはInnoDBをストレージエンジンとして使用するだろうから、これは却下。
また、AF側で決めうちでサーバIDを指定する方法がありますが、LBを通しているため、どこのマスタにデータが格納されるかわからないので、実は決めうちでserver-idを指定できない罠。

→よって却下

AUTO_INCREMENTカラムの拡張
IDカラムをINTEGER型ではなく、BIGINTを使用し、64bitに拡張。
上位32bitにサーバIDを格納し、下位32bitにIDを格納。
マルチパートキーを一つのキーで実現します。

このアプローチが一番現実的。
AR側に拡張が必要になるかもしれない。

問題点
→レコード件数が膨大になったときに、DISK容量が爆発する。
→ID値が広く分散する。


考察3
異なるクラスタ間でのテーブルのJOINには(現状)対応できないため、JOINが発生しないようにテーブルを分散できるよう設計する必要がある。


現状の進捗
現在、デュアルマスタ&スレーブ一台のクラスタの構築を完了済み。
Active Record Cluster経由でクエリが正常に分散されることも確認済み。

障害時の動作検証
Heartbeat障害&ホスト障害→Activeに発生した場合はフォールバックされることを確認。
MySQLプロセス障害→LVSハッシュテーブル・Ldirectordともに正常動作しているように見受けられるが、なぜかリクエストがルーティングされない。

→デバッグ中。

TODO
・デュアルマスタ構成はシンプルなマスタスレーブ構成に比べて少し複雑。
障害別対応をきちっと決めておく必要がある。

・MySQLの設定をデフォルトで使用しているケースが見受けれる。
例えば、スレーブが一台死んだだけで、リソースはあまっているのにMaxClientの制限に引っ掛かって接続できないようでは意味が無い。パラメータの設定基準を明確にしドキュメント化する。

[Vol.1] RailsとMySQLによる大規模サイト構築実験

大規模サイト構築のための土台を作っていきます。
ASP事業に力を注入するとなると、24H7D動作し続ける安定したサービスのためのインフラがまうます必須になるはずです。

アーキテクト
WEBサーバ 何でもいい。
WEBアプリケーションフレームワーク Ruby on Rails
DB MYSQL

で実験していきます。

とりあえず、必要そうなもの。
1. WEB 負荷分散 ・冗長化
2. DB 負荷分散
3. DB 冗長化
4. Railsの拡張(DBへのコネクション周り)

まず1番。
ロードバランサー使えばできるし現在のドリコムでも、DUOBLOG APIやCMS ASPはクリアしてます。DNSラウンドロビンは、障害検知が不可能なのでNGです。

次は、2番。
クエリは、参照系クエリと更新系クエリに分類されます。
今ドリコムでDBへのクエリを負荷分散させているプロジェクトは(僕の知っているところは)ありません。

次は、3番。
DB自体の冗長化です。
MySQLを使用しているのでDUOBLOG 検索APIがDBの冗長化をサポートしています。
単純なマスタ・スレーブ構成を構築し、Mojavi側でコネクションの切り替え(マスタが死んでいる際はスレーブに接続)を実装しました。

でも、これって完全には冗長化されていませんね。現状参照系クエリの冗長化しか行っていません。通常マスタが死んだ場合はスレーブに引き継ぐことになりますが、フェールバックは手動になります。

次は、4番。
Active Record Clusterをこのために作りました。


やることイパーイなのですが、このシリーズではMySQL自体の冗長化と負荷分散とRails側の拡張を実験していきます。

アーキテクト
次のようなデュアルマスタ構成に複数代のスレーブがぶら下がっている、クラスタがたくさんあると考えてください。



更新系のクエリはLB(負荷分散装置)が振り分け、参照系のクエリについてはRails側で振り分けます。
(参照系クエリもLBで振り分けるようにするか- 考察1)

一つ注意が必要なのは、更新系のクエリは図に示しているLB(ロードバランサ)で分散されるわけではありません。ここでLBをはさんでいるのはあくまでも 冗長性のためです。マスタ間でレプリケーションが行われるため、リクエストの受け付けの負荷分散が出来るだけで本当の意味での負荷分散が出来るわけではあ りません。

(マスタ間でのレプリケーション遅延により発生する、AUTO_INCREMENTシーケンスの衝突による問題を回避するための対策 - 考察2)

では、更新系の負荷分散はどうするか?
このようなクラスタを複数用意します。テーブルによって所属するクラスタ自体を分けてしまいます

(テーブル設計の工夫 - 考察3)

考察と書いたところは、考える必要があるポイントです。
まずは、マスタへのクエリの分散を行うためのLBを構築したいと思います。
Heartbeat + Ldirectordを使用して構築します。

技術的には全く難しいことはしてないんですけど、セットアップが若干めんどくさい。。
レイヤ4スイッチングを使用し、MySQLへクエリを発行し正常に結果を取得できなかったら、DBサーバをハッシュテーブルからはずすだけです。(正確にはWeightを0に設定しリクエストが振り分けられなくする。)

LBはActive-passive構成で組み、DBはActive-Activeで組みます。

セットアップ手順

1. カーネルの再構築
ARP問題を回避するためにパッチを当てたカーネルが必要です。
linux-2.6.11を使用します。最新のlinu-2.6.16でやると、カネパになりました。

http://kernel.org/
ここからカーネルをwget.

# tar jxvf kernel-x.x.x.tar.bz2
# cd kernel-x.x.x

http://www.ssi.bg/~ja/#hidden
ここからカーネルのバージョンにあったパッチをwget.

# patch -p1 < style="font-weight: bold;">2. ネットワーク設定
IPフォワーディングとか隠れデバイスを有効にするための設定を行います。
ARP問題を解決するために、ループバックインタフェースを隠します。
言葉の意味がわからなければ気にしないでください。

/etc/sysctl.conf
を編集します。
次のように設定します。
した二つは、設定に無いので新たに追加します。

net.ipv4.ip_forward = 1

# 隠れデバイスの設定を有効にする
net.ipv4.conf.all.hidden = 1
# ループバックインタフェースを隠す
net.ipv4.conf.lo.hidden = 1

設定が完了すれば、
# /sbin/sysctl -p
で設定が反映されていることを確認します。


3. Heartbeatのインストール
Heartbeatはソースからインストールします。v2.xを使用します。
依存関係が複雑なので必要なプロダクトを先にインスコします。
インスコの順番は次のようにすればまず、間違いはないはず。

1. libgpg-error
2. libgcrypt
3. gnutls
4. libnet
5. heartbeat

Heartbeat以外は
$ ./configure
$ make
$ sudo make install

Heartbeatのみ
$ ./ConfigureMe configure
$ make
$ sudo make install

です。
もし、エラーが出る場合は足りないプロダクトをインスコしてください。


4. ldirectordのインストール

$ cvs -d:pserver:guest@cvs.linux-ha.org:/home/cvs/linux-ha login
$ cvs -z3 -d:pserver:guest@cvs.linux-ha.org:/home/cvs/linux-ha co linux-ha/ldirectord/

login時に要求されるパスワードは、"guest"です。
で完了です。


5. デプロイ
プログラム・設定ファイル類をコピーします。

# cp ldirectord /etc/ha.d/resource.d/
# chmod +x /etc/ha.d/resource.d/ldirectord
# cp ldirectord.cf /etc/ha.d/
# cp /usr/share/doc/heartbeat-2.0.3/authkeys /etc/ha.d/
chmod 600 /etc/ha.d/authkeys

6. 設定
Heartbeatは、この記事あたりを見れば設定できるはず。

Ldirecotordの設定が多少異なります。

以下は、/etc/ha.d/ldirectord.cfの設定サンプル。
192.168.10.226がVIPで、realが実サーバ(マスタ).
requestには実行するSQLクエリ、serviceにはmysql、login, passwd, databaseをそれぞれ指定します。サービスにDBを指定した場合は、recieveディレクティブは使用できません。

virtual=192.168.10.226:3306
real=192.168.10.219:3306 gate 1
real=192.168.10.230:3306 gate 1
fallback=127.0.0.1:3306 gate
service=mysql
request="SHOW VARIABLES;"
# receive="Test Page"
# virtualhost=some.domain.com.au
scheduler=rr
#persistent=600
#netmask=255.255.255.255
protocol=tcp
login="heartbeat"
database="test"
passwd="hogera"

大抵一発では通りません。
デバックしましょう。
ldirectodのデバックは簡単です。-dオプションをつけて起動すると、デバックモードとして起動し、デーモンになりません。

$ /etc/ha.d/resoruce.d/ldirectord /etc/ha.d/ldirectord.cf

出力されたメッセージを見ましょう。
PerlのMySQLモジュールが無いと出ていないですか?
出ている場合は、インスコします。

http://search.cpan.org/dist/DBD-mysql/lib/DBD/mysql.pm
をwget.

$ perl Makefile.PL
$ make
$ sudo make install

もう一度実行します。
モジュールDBDも無いといわれた場合は、CPANからインスコします。

ここからレプリケーションを組んでスレーブ遅延による問題を回避し、
考察点としてあげたあたりをvol2で追求します。

テーブル設計工夫にあたり、Rails側の拡張ももう少し必要になるかもしれませんね。

Wednesday, February 22, 2006

アルバイト1.0->2.0

社員1.0と2.0

面白いので自分に当てはめてみました。
社員ではないのでアルバイト1.0かアルバイト2.0か?いざチェック。













1.02.0
全ての情報源がCNETと日経新聞あたりだったりするBloglinesです。(新聞読んでないなぁ。。)
ブログの存在すら知らない。ブログは書き始めて2年です。
会社はヒエラルキーで、経営者が戦略を作ってくれると思っている。自分で作った方が楽しいと思います。
ミッション以外の仕事はやらない。決められたミッションの無い職場かな(笑
MTが葬式のように暗い。興味が無いと眠くなります・・
自分の評価や給料が低いと愚痴る。自分に対して愚痴ります。おい、俺駄目だぞみたいな
いつも何かを恐れている。何か恐れますか??
転職先は、MS, NetApp, Dellあたりを考えている。起業。
ハードウェアが好きだ。ソフトウェア系だけど、ハードウェアも結構好きです。
自分の成功体験に固執する昔話をよくする。まだまだ、成功体験を語れる身ではないですので日々精進。
目はいつも中を向いている。外を見ているのかなぁ


みなさんも試してみてください~。
就職活動中の友達にトラバしてみる。

Saturday, February 18, 2006

Drecom内藤社長にご飯に連れて行ってもらいました

(株)DRECOMが2月9日に東証マザーズに上場しました。

ドリコム内藤社長は上場企業の社長になりました。
そして、先日株式を600株を347万円の単価で売却し20億円の利益を得た事をニュースで知りました。

そんな中社長から「一度ご飯でも食べに行こう。」と声を掛けてもらって、昨日祇園の鹿六という焼肉店に連れて行ってもらいました。


内藤さんに初めて会ったのは、2003年4月大学一回生の新歓祭。
そこで出会ったのが、ドリコムの勧誘ビラでした。今では考えられない話ですが、社長とCTOの二人が丸一日、勧誘に費やしたそうです。

ビラには、ほぼ何も書かれていませんでした。「普通じゃものたりないあなた。いいバイトがあります。」そんな怪しげなメッセージだけだったと思います。このビラは今でも記念にとってあります。でも、何かを感じ取ったんですね。気づいたら書かれていたHPアドレスから会社のHPを調べて、採用のページから応募要項を調べてメールしていました。

でも、「当時本当に何もわからなかった」
C言語での文字列の扱い方もわからない。(高校で教えてくれなかった。。)
「#include もおまじないだ」と教えられていました。

本当に期待値だけでインターンとして採用をしてもらいました。

1ヶ月でC言語を覚え、2ヶ月目からはC++を勉強しました。一ヶ月終わったときに、「給料ください!」と社長に直々にメールを送ったときは「まだ最低時給800円分の働きはしていませんので、給料は払えません。」とメールを返してもらったのも今となっては懐かしい思い出です。二ヶ月目に再度同じメールを送り、給料をもらい始めました。
この世界で認めてもらいたい。そんなエネルギーにつき動かされていたのだと思います。

そして、3年半がたった今、ご飯に誘っていただきました。
「どうしても勧誘したい人」に対して社長がいいお店にご飯を食べに誘う「内藤マジック」として社内では有名です。そして、今はちょうど就職活動シーズンです。期待値だけでなく、能力を認めてもらっているのは非常にうれしいことだと思っています。

ドリコムのビジネス展開・社員の力を発揮させる組織作り・ライブドアショック
株のこと・Web2.0・ASPビジネス・・・と色々話してもらいました。

内藤さんと同じMTに参加し一緒に仕事が出来るのは、最初の一年ほどだけでした(東京進出が始まったので)。その一年で社長の頭の回転の速さ・負けず嫌いさ・エンターテイメントを作り出す心得・・・色々なことを学ばせてもらいました。

そして、「東京に来て自分の近く(経営より)で働いてほしい」と声をかけてもらいました。
ですが、最後は「rakutoの人生だしやりたいことをやりなよ」といってもらえたところに改めて、内藤さんの心の広さに感動しました。

昔、内藤社長から「自分の昔の姿と重なるなぁ」と言ってもらったことがあるのですが、だからこそ自由にやればいいよと言ってもらえたのかもしれません。


「優秀な経営者は6年ごとに現れている。」
「第一世代はソフトバンク孫社長、第二世代は楽天三木谷社長、ライブドア堀江(元)社長、サイバーエージェント藤田社長・T&G野尻社長」

「そして、第三世代がドリコム内藤社長」
ということも重なり、ドリコムが注目されていると聞きました。

どんな優秀な人であっても、運・時代が絶妙に重なり合わないと大きな成功はありえません。
また優秀な人というのは運を呼び込む力を持っている人だとも思っています。ドリコムがITで会社を興したことも、WEBが変わりPCだけのものでは無くなっていく2006年に上場したのも全てが最高のタイミングではないかと思います。(ライブドアショックをのぞけば。)


お世話になった恩返しをするためにも、頑張らなければならないと思っています。
そして、上場おめでとうございます。

あ、第四世代は2012年といえば・・26歳のとき(笑

Friday, February 17, 2006

[ActiveRecord Hacks] Dynamic Finder

ActiveRecord Hacks - Dynamic Finder

ActiveRecord Hacksでは、ActiveRecordの面白いメソッドを紹介しながら、どうやって実装しているのかをソースコードを見ながら検証してみます。

ActiveRecordにはDyanamiFinderという機能が実装されています。
(静的言語を主に使ってる人にとっては、これはもうマジックすね。)

Dyanamic Finderは、読んで文字のごとく動的なレコード検索機能を提供します。例えば、こんなスキーマがあったとします。

CREATE NOT IF EXIST TABLE drecoms(
id INT AUTO_INCREMENT,
name VARCHAR(50),
email VARCHAR(100),
);

例えば、「rakuto」という名前の社員を検索したいときに、self.findメソッドを使いますよね。

rakuto = Drecom.find(:all, :conditions => ["name = ?", "rakuto"])

Dynamic Finderの機能を使えば、次のように書くことが出来ます。

rakuto = Drecom.find_all_by_name("rakuto")

hoge@hoge.comというemailを持っている社員は次のように検索できます。
hoge = Drecom.find_by_email("hoge@hoge.com")

rakutoという名前で、hoge@hoge.comというメールアドレスを持つ社員ならば、
rakuto = Drecom.find_by_name_and_email('rakuto', 'hoge@hoge.com')


ヤバイっすねこの機能。
どういう仕組みで動作するのでしょう?もちろんfind_by_(カラム名)というメソッドがActiveRecord::Base内に実装されているわけではないことは一目瞭然です。
動的にカラムを条件として参照するためのメソッドを作り出しています。

このマジックの種明かしをするために、ActiveRecord::Baseのソースを追ってみます。

Drecom.find_by_(カラム名)というメソッドを呼び出したときに、宣言されていないので静的言語ならコンパイルエラーとかでアボーンですが、Rubyではmethod_missingメソッドが呼ばれます。

activerecord-1.13.2で言えば、970行目の"def method_missin(method_id, *arguments)"になります。

def method_missing(method_id, *arguments)
if match = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(method_id.to_s)
finder = determine_finder(match)

attribute_names = extract_attribute_names_from_match(match)

super unless all_attributes_exists?(attribute_names)
conditions = construct_conditions_from_arguments(attribute_names, arguments)

コールされたメソッドが存在しない場合、メソッド名が"find_(all_by or by)_(アルファベット)"にマッチするかを調べます。determine_finderは、一致するカラム全て検索(find_all_by)なのか、一つだけ取り出すのか(find_by)を特定します。

次にextract_attribute_names_from_matchメソッドでメソッド名を"_and_"でsplitしてカラム名を配列として取り出し、メソッド名として指定されたカラムが全て存在するかどうかを調べています。

そして、"extract_attribute_names_from_match(match)"メソッドで"name = 'rakuto' AND email = 'hoge@hoge.com'"といったSQLの条件部分を作成します。

メソッドの振る舞いの実体は出来上がりました。残るはメソッドにリミットやオフセットなどのオプションの処理とベースのfindメソッドの呼び出しです。

if arguments[attribute_names.length].is_a?(Hash)
find(finder, { :conditions => conditions }.update(arguments[attribute_names.length]))
else
send("find_#{ finder}", conditions, *arguments[attribute_names.length..-1]) # deprecated API
end

もし、Drecom.find_by_name('rakuto', :limit => 10)といった感じでオプションが与えられている場合とそうでない場合で呼び出す基本のfindメソッドを切り替えています。

条件部分を作り出しているメソッドをもう少し詳しく見たいと思います。

def construct_conditions_from_arguments(attribute_names, arguments)
conditions = []
attribute_names.each_with_index { |name, idx| conditions << "#{table_\ name}.#{ connection.quote_column_name(name)} #{attribute_condition(arguments[idx\ ])} " } [ conditions.join(" AND "), *arguments[0...attribute_names.length] ] end

引数としてカラム名の配列と条件として指定した値('rakuto')を取ります。

それらをconditons配列にPUSHしてSQLインジェクション防止のためにプリペアドステートメントを適用します。返り値は、['name = ? AND emai = ?', ['rakuto', 'hoge@hoge.com']]といった感じです。


まとめ

強力で面白い機能ですが、インデクスの張り具合を理解していない厨房が使うと死にます。インデクスの張られていないカラムを検索条件としてばんばん検索をかけるはめに。

Tag:

Tuesday, February 14, 2006

立命館高校の長山先生が死亡事故に巻き込まれました。

事故:12台絡む玉突き、3人死亡 京滋バイパス

倫理の授業などでお世話になっていて、キャラクターも強烈で記憶残っている長山先生が交通事故に巻き込まれてお亡くなりになりました。

ショックです・・・ショックです。。
おつやが明日の18時から、
告別式が16日の10時からと聞きました。

理系倫理を取っていた人ならば、立命館高校の長山先生の倫理が他とは違うってみんな思っていたと思います。

普段生活をしているときは、「今を生きているという風」にすごしていますが、その今というのは過去の経験値の積み重ねの上にあるということを強く感じました。

長山先生の教えも自分にとって大きな経験値になっているのだなと、強く感じました。

長山先生ご本人が一番無念だったと思います。
ご冥福をお祈りいたします。

Sunday, February 05, 2006

ヨゴコーゲン・ヤップへGO!


ようやく、「趣味って何?」って聞かれたら胸張って「スノボーかな。」って言えるぐらいになったかな。

今年は、近場でも雪がいいので毎週末行っていて、今回は4回目。

滋賀で、最高と評判の高いヨゴコーゲン・ヤップに始めて行ってきた。HPもナウでヤングな感じに作られていて、イベントも色々アル様子。最大のイベントは2月18日に開催予定のヤップ・ナイト

余呉?滋賀の北の方だろう~と安易に考えていたのだけど、これが完全に舐めている。だってヤップの頂上から日本海が見える!滋賀というよりは、福井に近かった。

でも、でも!!
これが、結構お安くでいける。

バスツアーで参加。
土・日はスノーライナーというバスが、彦根・長浜などから出ていてリフト券こみで4200円!!
車持ってたら車で行ってもいいだろうけど、今回の帰りだけでも、結構大きな事故が2件起きていました。雪道慣れていない都会ものが運転するには厳しすぎる条件だし、バスは楽だしおすすめです。

何が「最高にいけてる」のか。

  1. 雪質が最高

  2. コースがいい。パーク・キッカーが充実

  3. 日本海が見える


雪質がとにかくイイ!
まさに、信州クラスの粉雪。積雪量も3メートルを超える。


コース設計がイイ!
全てのコースが同じ場所に滑り降りてくる。つまりコースの幅がとても広くて、リフトの乗り換えも簡単。
コースマップで見ると「うーんどうかな。。」って感じですが滑ってみると格別!
関 西はリフトの乗り場がばらばらで、コースの幅も狭い中でかなりいいコース設計です。さらにキッカー・パークも充実していて中級者で「トリックやキッカーで ジャンプ決めたいなぁ」(ちょうど、私レベル)と思ってる人にも多数の高さのキッカーがそろっておりチャレンジしやすくなってます。

アリエスBリフトを上がって右側に進み滑り降りおりていくとキッカーにチャレンジできる。
そして、アリエスDリフトを上って左側、ブレバンコース(いわゆる林間コース)にもなぜかキッカーが設置されている(笑
また一部コースの端が盛り上がってるので、パイプのように遊べます。


海が見える!
「今庄365もスキー場が見えたのかな?ジャム勝もみえるの?」ちょっと記憶が定かでないのだが、明確に覚えている限りでは、海がこんなに近くに見えるスキー場は初めて。

ウッ。。
着替え終わって、帰りに雪の斜面で滑ってひじから地面に落ちました。
久しぶりのきゃら肩です。(内輪ネタ)


言われている通り、確かにいけてるスキー場でした。
帰りのバスの中では、映画「ステルス」 を見て、「そうだ!これだった!ITで働きたかった理由は。」という原点を思い出した。それは、次の記事で。 Posted by Picasa

Friday, February 03, 2006

Thursday, February 02, 2006

UIE Japanの人達

ブログのリファラーをチェックしてたときに、今野さんのはてなRSSのUIEカテゴリからのアクセスが結構前から増えていたので、UIE Japanにはこの人たちが入るんだなと思っていた。

http://revilog.com/

http://tkido.blog43.fc2.com/

http://recordo.blogspot.com/

http://nirwana42.blogspot.com/

えっと、UIE中島さんの息子さんとぐらいと同世代ぐらいになります。
まだまだひよっこですが、皆様よろしくお願いします。

そろそろ就活について一言いっておくか

2月2日、UIE Japanが誕生した。
そして、ギークの集まるUIE Japanへの採用が決まった。

また、ゴールドマンサックスの面接合格通知が届いた。
一応次のプロセスも受けるが通っても多分いかないだろう。
~なんて言ったら文系の友達に怒られるだろうな。

Wednesday, February 01, 2006

Javascriptのライブラリリスト

A Huge List of Javascript Libraries

Javascript libraries roundup

jQueryは、まだリストにあがってないなぁ。

jQuery - PrototypeにインスパイアされたJavascriptライブラリ

AjaxインタフェースCMS

SkeletonzCMS

Skeltonzは、AjaxインタフェースのCMSです。シンプルなインタフェースで、操作性&拡張性は高そうです。また、ページの作成は、GUIのHTMLエディタというよりは、テキストエディタでWikiに近いシンタックスで行います。

  • Responsive and easy to user interface
    Much of the CMS‘s communication with the server is AJAX based. For you and your users this means fast, responsive and easy to use interface!

  • Insert and format with a very simple syntax
    Creating pages, uploading files or images is done by a wiki based syntax.

  • One-click backup of the whole site
    With just one click you can backup the whole site – and with one click you can restore that backup!

  • Easy to use template system
    The template system is VERY small, clean and extendable

  • Group and user management included
    Set premissions on pages – with a few clicks
  • 勝手に日本語訳
    ・扱いやすいユーザインタフェース
    ・Wikiベースのシンプルな構文でページを作成する
    ・ワンクリックでページ全体のバックアップが可能
    ・テンプレートを簡単に使用できる。
    ・ユーザ・グループの管理が可能
    (少しでも)慣れている人にとっては、(遅い)GUIよりも(軽くて速い)Wikiベースのユーザインタフェースが使いやすい。

    ライセンスとアーキテクト

    License :: GPL
    Programing Language :: Python 2.3
    Database :: SQLlite
    Fremework :: Cherrypy
    Template Engine :: Cheetah template
    Javascript Library :: Mochikit
    Database O/R Mapping :: SQLObject

    お、TurboGearsと似たような構成。ほぼ同じ。
    (Railsもいいですけど、TurboGearsもヨサゲなんですよ。マルチバイト文字あたりがなんとかなれば。。)

    ということでAjax部分の実装は、TurbGearsと同じ、Mochikitを使用しています。

    CMS編集ページのデモが用意されています。

    で、早速使ってみるがEdit Contentボタンをクリックしても永遠にView HTMLの横の読み込み中が回り続けてページの編集が出来ないのでやめ。

    Python周りのプロダクトの使い方の参考としてソースを読んでいくことにする。

    オープンソースのAjaxフレームワーク

    Plex

    Backbaseと近い系統のAjaxフレームワークである。

    これは、ぞくぞくさせられる。
    独自定義系?と表現されてもいたこの系のフレームワークを後で調べる。

    jQuery - PrototypeにインスパイアされたJavascriptライブラリ

    jQuery

    jQuery is designed to change the way that you write Javascript.

    jQueryはあなたのJavascriptプログラミングの方法に変化をもたらすように設計されている。

    というわけで、jQueryを使ったJavascriptプログラミングにチャレンジしてみよう。

    What is jQuery?
    jQuery is a Javascript library that takes this motto to heart: Writing Javascript code should be fun. jQuery acheives this goal by taking common, repetitive, tasks, stripping out all the unnecessary markup, and leaving them short, smart and understandable.

    jQueryは、「Javascriptプログラミングを楽しくするべきだ。」をマジメに考えるJavascriptライブラリだとさ。

    非常にコンパクトなライブラリでサイズは10kbほどで、コードリーディングもさほど辛くない。

    また、jQueryを使った、デモはこちらから見ることが出来ます。
    http://jquery.com/docs/Demos/


    jQueryのアーキテクト
    から構成される。

    jQueryBaseは、jQueryのコア部分である。DOM要素を取得する$()やイテレータ,イベントハンドラの登録関数などからる。
    BasicEffectsは、Effect(視覚効果)を与えるメソッドを実装している。
    Advanced Eventsは、イベントを扱うときに役に立つメソッドの実装である。


    jQueryBase

    jQueryでは、全てのプラグイン、基本の関数、セレクタでCVSXPathをサポートしている。
    PrototypeからインスパイアされているjQueryを語るには、$()メソッドを初めに説明するべきだろう。
    Prototpyeの$()とjQueryのそれは、動作が異なる。これについては、後の「PrototypeとjQuery」で説明したい。

    $() 関数

    全ての元になる大事な関数。DOM要素を参照する。
    jQuery の$()関数は、Prototypeの$()とは異なる。Prototypeでは、IDでDOMを参照するがjQueryではXpath, CSS, 独自の式でDOM要素を参照する。また、返り値はjQueryObjectで、これもPrototypeとは異なる。

    $()関数は、3つのシンタックスを取る。
    • $(EXPRESSION)
    • $(DOMElement)
    • $(..., Context)
    EXPRESSIONは、CSSとXpathからなるセレクタの式である。
    簡単な使用方法として、pタグの要素を全て取得する場合は次のように書く。
    $("p")
    $()は、jQUeryオブジェクトを返すので連鎖してメソッド呼び出しが可能である。
    $("p").addClass("test").show()
    デフォルトでは、$()関数は現在のHTMLドキュメントからDOM要素を検索する。
    DOM要素を指定して$()関数を通す事で、DOM要素をjQueryObjectとして取得でき、jQueryObject

    が持つ全てのメソッドが使用可能となる。
    $(document.getElementById("test")).onclick(...)
    $(document).onready(...)
    $(window).onload(...)
    $(xml.responseXML) // For working with XMLHTTPRequest

    $(...,Context)
    第二引数の、コンテキストはオプションで指定可能である。このパラメータを指定した場合、コ

    ンテキストの中からDOM要素の参照が行われる。つまり、下記の記法の速記法である。
    $(context).find(...)
    shorthand(速記法):
    $("p", document)
    $("title", xml.responseXML)

    Iteration

    size()
    要素数を返す。
    $("p").size()
    get(), get(N)
    DOM要素を配列で返す。Nが指定された場合は、N番目の要素が返される。
    $("p").get()
    each(function())
    全ての要素に対して関数を適用する。
    コード例:
    $("p").each(function(i){
    this.innerHTML = this + " is the Element, " +
    i + " is the Position";
    });

    BaseEvents

    クリック等のイベントに対するイベントハンドラの登録関数。

    bind(String,Function)
    Stringで指定したイベントに、Functionで指定した関数をバインドする。
    $("p").bind("click",function(){
    alert("Hello!");
    });
    p要素中のテキストをクリックすると、イベントハンドラとしてバインドされたalert("Hellow!")が起動する。

    unbind(String, Function)
    バインドした関数を解除する。


    DOM Modification

    DOM操作を提供するクラス。
    DOM要素へのinnerHTMLの編集・追加、要素の削除など。

    html(String)
    Stringで指定された内容をDOM要素に設定する。

    サンプル:
    $("p").html("This is html!");

    remove()
    選択されている要素を削除する。
    $("p").remove()

    append(String)

    append(DOMElement)
    append(Array)
    マッチした要素の最後に文字列や、DOM要素を追加する。
    $("p").append("Append text")

    prepend(...)

    before(...)
    after(...)
    マッチした要素の最初や最後に文字列やDOM要素を追加する。


    wrap(...)

    マッチした要素を引数で指定したHTMLでラップする。
    次の例は、pタグに一致する要素をpタグで囲む。
    $("p").wrap("wrap_text");


    Style Methods

    スタイルに関するメソッド。CSSを適用する。

    CSS(Key, Value)
    要素に対してスタイルを適用する。element.Key = Value;と同じ効果。
    サンプルコードは、全てのpタグの要素に対して"font: 14px Arial"を適用する。
    $("p").css("font","14px Arial");

    CSS(Hash)
    要素に対してスタイルを適用する。上の関数と違い、スタイル名:値のハッシュを指定する。
    サンプル:
    $("p").css({
    font:"14px Arial",
    border:"1px solid #000"
    });

    addClass(String)
    マッチしたクラスにCSSクラスを適用する。
    サンプル:
    $("p").addClass("even")

    removeClass(String)

    マッチした全てのクラスからCSSを削除する。
    $("p").addClass("even")

    BaseEffects
    視覚効果のためのメソッド。

    hide()
    マッチした要素を非表示にする。

    show()
    マッチした要素を表示する。

    toggle()
    マッチした全ての要素をトグルする。


    ここまでで、jQueryBaseの説明は終わりだが、視角効果の実装をもう少し補足する。

    fx

    jQuery中に実装予定のEffectsのためのライブラリである。

    PrototypeとJQuery
    この項が重要?
    結局、jQueryはPrototypeとどう違うのか?Prototypeの$()が拡張され、CSS+XPath + 独自の言語でDOMを参照するというのが、よさそうな印象を受ける。

    そして、JQueryは、Prototypeに非常にインスパイアーされています。
    $()メソッドなどは、同名だが動作が異なるため、Prototype.jsとJQueryを一緒に使用する場合は注意が必要である。

    1. PrototpyeとJQueryを共用する場合は、jquery.jsを後に読み込む必要がある。

    PrototypeとJQueryの挙動の違い

    $()関数の挙動の違い

    $("pre")
    Prototype: ドキュメント中からIDが"pre"の要素を探し、存在する場合はオブジェクトを返し、見つからない場合はnullを返す。
    JQuery: preタグの要素を探す。
    見つからない場合: "pre"というIDを持つ要素を探し、見つかればその要素を返す。見つからなければ、空のマッチした要素がセットされたJQueryオブジェクトが返される。
    見つかった場合: マッチした全てのpre要素からなるJQueryオブジェクトが返される。

    $(DOMELEMENT)
    DOM要素を直接指定するシンタックスである。

    Prototype: DOM要素を返す。
    JQuery: DOM要素をラップしたJQueryオブジェクトを返す。そのため、返り値はPrototype, JQueryで利用可能である。

    PrototypeとJQueryを共用する際のポイント
    PrototypeのbehaviorとJQueryは、異なるので次の二つの対策のうち一つをすることがお勧めである。
    1. あいまいなセレクタの禁止
    2. ID名にDOM要素のタイプ(HTMLタグ名など)を使用しない
    1番目について。
    IDでユニークなDOM要素を参照するときには、
    $("#pre")
    とコーディングする。
    $("pre")
    では、IDでユニークな要素を参照するのかpreタグの要素を参照するのかあいまいである。

    2番目について。
    Prototype記法を使用したい場合には、このID名の命名ルール「ID名としてDOM要素のタイプで現れる可能性のタグを使用しない。」を適用する解決策が考えられる。
    このルールは一つでもDOM要素のタイプとして存在するID名が使われた途端に破綻します。


    JObjectは、DOM要素のラップ

    先 程、「DOM要素をラップしたJQueryオブジェクトを返す。」と書いた。JQueryでは、PrototypeユーザとJQueryユーザの両方をサ ポートするように設計されている。従来のDOMオブジェクトのプロパティへのアクセスも残したまま、新規にJQueryのメソッドが追加されている。

    $("wrap").style.display = 'none';

    JQueryでもラップしたDOM要素を返すために上記の記法は有効である。
    だが、JQueryではこの書き方は推奨されない。
    $("#wrap").hide();
    安全のために、JQueryにより認可された機能と用語のみを使用すべきである。
    jQueryは、Prototypeの不足している部分を補ってくれている。
    また、Prototypeと共用して使用することも考慮している。CSS, XPathセレクタの独自定義の式は便利そうである。

    Tags: