l'essentiel est invisible pour les yeux

Sunday, September 17, 2006

パビリオン山椒魚でドーン


















はーい、息吸ってー、とめて!ドーン!
本物とか、偽者とかどっちでもいいの。

前作「亀虫」をみていないので、富永ワールドを始めて見ることになった。

山椒魚の名前は、キンジロー。生誕150年でパリの万国博覧会にも出されたすごい歴史をみてきた山椒魚だそうだ。キンジローが、すげーかわいい。

そのキンジローを盗んでレントゲンをとり、本物かどうか確かめるようにオダギリジョー演じる天才レントゲン医師の飛鳥が依頼をうける。

・・・んな、まともな説明は必要ないような・・正直わからない!!

なぜ山椒魚なのか?なぜレントゲン医師の次は山賊なのか?第二農響って何なんだよ!!
まったくわからない(笑
そんな理屈は必要ないんだろうな。でも、もう1回見ないと多分満足できない。
ジャジーな音楽と映像がカッコよく、キャストが豪華で、色んな人間を演じている映像ががたまらなくカッコよくて面白かった。左上の写真は、購入したサントラ。

メゾンド・ヒミコの時のオダギリジョーも最高にセクシーだったけど、レントゲン医師のオダギリジョーもぬめぇっーとした色気がでていて、やっぱカッコいいししかも笑わせてくれる。

女優役は、最近注目の若手女優(らしい。最近テレビを見なくなったので全くしらなかった。)香椎由宇。プロフィール見たら、1987年生まれ!いや、ビビッった。。

最近大切なものを失って、ずっと低空飛行している気分だったが、そんなわかっていながらもださださだった俺もぶっとんだ。






















話は変わるけど、東京人は祭り好きなの?シネセゾン渋谷を出ると、ドーン!ドーン!太鼓の達人の10倍くらいの音が聞こえてきた。

って出ようと思ったら、ムキムキのお兄ちゃん達が扉の前に座っていて開かない。おみこしが通り、太鼓が鳴り響き、道玄坂は祭りムードになっていた。

この前原宿でも、表参道の通りを全部車とめて祭りをやっていた。あんなでかい通りよくとめるよなとふと思った。東京ではこういう祭りをよく開催しているのかな?

Tuesday, September 05, 2006

国分寺のパスタ屋 せもりな

京都に帰ってきた。
3週間東京にいると結構疲れるので、2週間滞在→京都→2週間滞在。

東京では、グルメな友達を持つにかぎる!情報がないと、なかなか美味しいお店までたどりつけません。
そこで、先日のフットサルでの筋肉痛をこらえ、日本一うまいと書いている人もいるぐらいの国分寺のパスタ屋 せもりなまで足を運んでみました。

井の頭線で吉祥寺まで出て、JR中央線乗り換えで国分寺へ。
駅から歩くこと10分。

昔からそこにあるような小さな食堂らしき家を見つけた。
看板をみると、せもりなでした。


















これは、よさそうな感じ。
期待を抱いて店内へ。

メニューを見せてもらうと、パスタの種類もピザの種類も豊富でかなり悩む。

ワインのおすすめを聞くと、白ワインのGaviというワインを勧められたので、
とりあえず、それをハーフボトルでお願いしますで。

アラカルトが300円で超おすすめ!
とブログに書いている人がいたことが頭によぎったが、ここは最近ずっと食べたかった牡蠣を注文。


















大きな岩かきがそのまま出てきて、身がぷるぷる言ってる・・・。
レモンをしぼり牡蠣から出ているスープと一緒に食べた。実がぷりぷりしていて、美味しかったです。

次は、パスタ。
最低2種類食べたかったので、一人前の量を聞くと100g.
二つ頼むと、「少し一皿の量を減らしましょうか?」とシェフの人が気をつかってくれました。

一皿目は、フレッシュトマトのカペリーニ。



トマトとカペリーニのパスタは冷性パスタとしてにんにくとバジルで食べるとうまくて、よく食べるけど、これは温かいパスタ。

トマト・にんにく・パセリがカペリーニと絡んで美味しかったけど、トマトから水分が出て少し大味になっていた感じがした。


二つ目のパスタは、クリームパスタと決めていたので、
ほたてとうにのクリームソース 生パスタを注文。




これは、うまい!!
ほたてからこんなに濃厚な味が出るとは驚き。生パスタがいい感じに絡んですごくおいしかったです。
クリームソースも一滴たりとも残さず完食。

ハーフボトルを飲み干したので、2本目を注文。
何にしようか迷ったので、となりの常連さんぽい夫婦にオススメしていた、
Gavi di Gaviというワインをまたボトルで注文。

こういうとき、「いつも大丈夫ですか?」と聞かれるが、「いいんです。大丈夫なんです。」と答える。

こちらのワインは、おいしい!一気に飲み干してしまいそうなのを抑えて、ワインのあてになるようなメニューを物色。後で調べると、「Gavi中のGavi」という意味らしく、そらGaviよりもおいしいはずだな。

生ハムは残念ながら、置いてなかった。
で、前を見ると、ピザのメニューが目に留まる。

迷わず、アンチョビピザを注文。



チーズ・アンチョビ・バジルのみのシンプルな薄生地のピザ。
その分、アンチョビのうまみが引き立っていて最高でした。
右手に見えるのは、タバスコではなく、唐辛子をつけたオリーブオイル。

で、ピザを食べながら、Gavi di Gaviも一気に飲み干して大満足。

お店の人に「インターネットでブログで日本一のパスタ屋って書かれているのを見て来たんですよ。」と言ったら、著作権の了解も無しにと笑っていました。店に来ていた人も毎週のように来ているという感じの人が一杯で、よそ者がたくさん来てお店の感じがかわると嫌かなとも思ったのですが、おいしかったのでブログに書いてしまいました。すみません。。

是非また、行きたい。
国分寺の少し手前の武蔵小金井あたりは、再開発中で今なら土地も安く結構おすすめだと、hoge山さんに教えてもらったので今度チャリンコのって見に行きます。

Sunday, September 03, 2006

Rubyのexpect.rbの使い方

SSHでつないで何か操作を行ったり、パスワードの入力を自動化したり、対話形式のプログラムをプログラム中から利用する場合は、expectモジュールを使用するのが一般的です。

Rubyは標準ライブラリ中にexpect.rbが存在していますが、IO#exceptとしてIOのメソッドとして定義されていて、このままでは扱いにくいので、仮想端末を扱うPTYライブラリとあわせて使用します。


require 'pty'
require 'expect'

module Expect
def spawn(cmd)
  puts "CMD: #{cmd}" if $expect_verbose
PTY.spawn(cmd) do |r,w,pid|
@input_stream = r
@output_stream = w
@child_pid = pid
PTY.protect_signal do
yield
end
end
end

def expect(pat, timeout=10)
ret = @input_stream.expect(pat, timeout) do |match|
raise "expect %s timeout " % (pat.kind_of?(Regexp)? pat.source : pat) unless match
put_cmd(yield(match))
end
end

private
def put_cmd(cmd)
@output_stream.puts(cmd)
end
end
PTY#protect_signalでブロックを実行しないと、SSHで接続後、exitしたときなど子プロセス終了時に例外が発生します。

例えば、rootにログインしてホームディレクトリに移動して、lsコマンドを実行する。

def root_ls
include Expect
spawn("su root") do
expect(/\$$/) {|match| "su root"}
expect(/Password:/) {|match| "myrootpass"}
expect(/\$$/) {|match| "cd"}
expect(/\$$/) {|match| "ls"}
end
end

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と組み合わせれば、大規模サイトの構築も簡単にできてしまいます。
レプリケーション遅延による自動増分キーの衝突も発生しません。

・・・すばらしい(笑