l'essentiel est invisible pour les yeux

Friday, May 25, 2007

[Ruby] MemoizationのRubyによる高機能な実装Memoizeを作った。

Memoize

(src) memoize-0.1.0.tgz

gemだと敷居が高い事も十分に承知なのでページ下部にソースコードも晒しておきます。ご自由にご利用ください。

MemoizationはDonald Michieにより1968年に作られた言葉でその歴史は古い。最初の実装はCommon Lispのようだ。(参照: Wikipedia - Memoization) 既に十分に古い概念なのでRubyにもMemoizationの実装は存在するが随分と低機能で無駄なIOが発生するので高機能なMemoizationの実装をつくった。

Memoizationは簡単に言えば次のような機能である。

  • 関数呼び出しの結果を引数をキーにして保存する。
  • 元の関数をキャッシュが存在すればキャッシュを返しキャッシュが存在しない場合には値を導出してからキャッシュするように再定義する。


つまり、グローバル変数の値に実行結果が依存したり同じ引数を与えても実行の度に結果が変化するような副作用を伴う関数にはmemoizationを適用することはできない。また、通常のメソッドに余分なロジックを付加してメソッドを再定義するため極端にロジックの少ないメソッドでは逆に速度が遅くなる。

Memoizedするのに向いている関数(メソッド)は次のような性質を持つ。
  • 深い再帰を持つなど同じ引数で何度も同じ関数の呼び出し行われる。

  • 副作用を持たず、関数の実行コストが高いような関数。


Memoizationを実装するには、動的な関数定義を言語レベルでサポートしている必要がある。VHLLなRubyなら何の問題もない。今回実装したMemoizeは次のような機能をサポートしている。

  • memoizedデータを保存するストレージをプログラマが独自に実装できる。
  • トップレベル(main)だけでなく任意のスコープのメソッドをMemoizedできる。
  • 永続化させるためのmemoizeデータのファイルへの書き出しはプログラム終了時だけである。


TODO
  • スレッドセーフでない。


インストール

% wget http://rubyforge.org/frs/download.php/21051/memoize-0.1.0.gem
% sudo install Memoize-0.1.0.gem


Memoizedを使用したサンプルコード
Memoize.registerやMemoize.unmemoizeの第一引数に渡すselfはおまじないだと思ってください。トップレベルのmainへの参照を別のスコープから取得する方法がわからなかったのでこうなっています。知っている方がいたら教えていただけると助かります。

memoizeした関数を別の名前で参照したい際には、Memoize.registerの第三引数に:as => newmethodを指定する。


require 'memoize'
require 'benchmark'

def fib(n)
return 1 if n < 2
fib(n-1) + fib(n-2)
end

class Math2
def self.fib(n)
return 1 if n < 2
fib(n-1) + fib(n-2)
end
end

Benchmark.bm do |b|
b.report("fib(30): ") { fib(30) }
Memoize.register(self, 'fib')
b.report("fib(30) with memoize") { fib(30) }
b.report("fib(30) with memoize") { fib(30) }
Memoize.unmemoize(self, 'fib')
end

Benchmark.bm do |b|
Memoize.register(self, 'fib', :as => 'fastfib')
b.report("fastfib(30) with memoize") { fastfib(30) }
b.report("fastfib(30) with memoize") { fastfib(30) }
Memoize.unmemoize(self, 'fastfib')
end

Benchmark.bm do |b|
b.report("fib(30): ") { Math2.fib(30) }
Memoize.register(self, 'Math2.fib')
b.report("fib(30) with memoize") { Math2.fib(30) }
b.report("fib(30) with memoize") { Math2.fib(30) }
end


実行結果

% ruby test_memoize.rb
user system total real
fib(30): 7.060000 3.320000 10.380000 ( 10.376117)
fib(30) with memoize 0.000000 0.000000 0.000000 ( 0.000043)
fib(30) with memoize 0.000000 0.000000 0.000000 ( 0.000065)
user system total real
fastfib(30) with memoize 0.000000 0.000000 0.000000 ( 0.000024)
fastfib(30) with memoize 0.000000 0.000000 0.000000 ( 0.000023)
user system total real
fib(30): 6.380000 3.140000 9.520000 ( 9.520442)
fib(30) with memoize 0.000000 0.000000 0.000000 ( 0.000029)
fib(30) with memoize 0.000000 0.000000 0.000000 ( 0.000024)
%


meoizedデータを保存するストレージの仕様
初めに書いたようにユーザ定義のストレージも使用できる。Memoize.registerに何も指定しない場合は、メモリ領域でデータを管理し、データを永続化させるために、プログラムの終了時にローカルのファイルシステムを使用してファイルとして書き込む。このファイルは/tmp以下に関数名をBase64エンコードしたファイル名.cacheで書き出される。

独自のストレージの実装
独自ストレージの実装はMemoize::Storableクラスを継承して以下の用に必要なメソッドを定義すればよい。詳しくは、lib/memoize.rb中のコメントを参照してください。MemCacheをストレージとして利用するクラスは次のように実装できる。

定義したストレージを使用するには、Memoize.registerの第三引数に:storeオプションを指定する。


class MemCacheStore < Memoize::Storable
attr_accessor :cache, :keys
def initialize(name)
@keys = []
@cache = MemCache.new 'localhost:11211', :namespace => 'rakuto.blogspot.com'
end

def get(key)
@cache.get(key)
end

def set(key, value)
@keys << key unless @keys.include?(key)
@cache.set(key, value)
end

def delete(key)
@cache.delete(key)
end

def delete_all
@keys.each { |key| @cache.delete(key) }
end
end


Memoize.register(self, 'fib', :store => MemCacheStore)
fib(30)


ソースコード: memoize.rb
memoize-0.1.0.tgz

# Memoize is implementation Memoization for Ruby, this techinique to make functions faster.
#
# == Caveats:
# * Do not memoize a function whose behaviou depends on program state.
# * Do not memoize a function with side effects.
# * Do not memoize a function that returns a data structure that is modified by it's caller.
#
# Copyright 2007 rakuto under MIT licence.
# Rakuto Furutani <rakuto at gmail.com>
#
# == See:
# Memoization - http://en.wikipedia.org/wiki/Memoization
#
module Memoize
VERSION = '0.1.0'
@@stores = {}

# Memoize::Storable class is abstract class. You must implement class which memoization data is saved.
# This module offer two storage class.
#
# * Memoize::MemoryStore -
# * Memoize::PStore - Persisten cache support.
#
class Storable
# {{{
attr_accessor :store

def initialize(name)
raise "Memoize::Strable don't instantiage for abstract class."
end

def set(key, value)
end

def get(key)
end

def update(key)
end

def delete(key)
end

def delete_all
end
# }}}
end

# MemoryStore is class which store memoization data to memory.
class MemoryStore < Memoize::Storable
# {{{
attr_accessor :store

def initialize
@store = {}
end

def get(key)
@store[key]
end

def set(key, value)
@store[key] = value
end

def delete(key)
@store.delete(key)
end

def delete_all
@store = {}
end
# }}}
end

# PStore is class which store memoization data with local file system.
# This store class be able to use persistent cache, because this class write out
# cache data on file when process is exit.
class PStore < Memoize::MemoryStore
# {{{
attr_accessor :store
PREFIX_SAVE_FILE_DIR = "/tmp"
SUFFIX_SAVE_FILE_EXT = ".cache"

def initialize(name)
@name = name
filename = encoded_filename(@name)
begin
if File.exists?(filename)
File.open(filename, "rb") do |io|
@store = Marshal.load(io)
end
else
@store = {}
end
rescue
@store = {}
end
# Write out cache
at_exit {
File.open("#{filename}", "wb+") do |io|
io.flock(File::LOCK_EX)
Marshal.dump(@store, io)
io.flock(File::LOCK_UN)
end
}
end

# Delete all memoized data and persisten cache fille
def delete_all
filename = encoded_filename(@name)
File.exists?(filename) && File.unlink(filename)
@store = {}
end

private
# Get the encoded file name
def encoded_filename(name)
File.join(PREFIX_SAVE_FILE_DIR, [name].pack('m').chomp+SUFFIX_SAVE_FILE_EXT)
end
# }}}
end

module_function
# Make memoized function.
#
# Parameters:
# * klass - You must surely specify 'self'. This parameter used to overwrite Toplevell function and
# register as toplevel function. I need to think better the means ;)
# * name - an memorized function name.
# * options - an specify options. <tt>:as</tt> is memorized function name, default is the same second arguments value.
# <tt>:store</tt> is specify class which store memorized data.
#
# == Memoized function examples
# def fib(n)
# return 1 if n < 2
# fib(n-1) + fib(n-2)
# end
#
# end
# Memoize.register(self, 'fib')
# fib(30) # => 1346269 fast
# fib(30) # => 1346269 very fast
#
# # Unmemorize
# Memoize.unmemoize(self, 'fib')
# fib(30) # => very slow
#
# == Memoized one Object methods
# class Math2
# def self.fib(N)
# return 1 if n < 2
# fib(n-1) + fib(n-2)
# end
# end
#
# Memoize.memorize(self, 'Math2.fib')
# Math.fib(30) # => fast
# Math.fib(30) # => very fast
# Memoize.unmemoize(self, 'Math2.fib')
#
# == Custom storage
# You can use own memoized data storage, in doing so you must implementaion following method
#
# Method/Arity:
# * initialize/1 - this is constructor
# * get/1 - an get the cache data with key
# * set/2 - an set the cache data with key and value
# * delete/1 - an delete the cache data with key
# * delete_all/0 - an delete all cache
#
# Following class is MemCache storage sample.
#
# # Memoized data storage using MemCache
# require 'memcache'
#
# class MemCacheStore < Memoize::Storable
# attr_accessor :cache, :keys
# def initialize(name)
# @keys = []
# @cache = MemCache.new 'localhost:11211', :namespace => 'rakuto.blogspot.com'
# end
#
# def get(key)
# @cache.get(key)
# end
#
# def set(key, value)
# @keys << key unless @keys.include?(key)
# @cache.set(key, value)
# end
#
# def delete(key)
# @cache.delete(key)
# end
#
# def delete_all
# @keys.each { |key| @cache.delete(key) }
# end
# end
#
# Memoize.register(self, 'fib', :store => MemCacheStore)
# fib(30) # => fast
# fib(30) # => fast
#
def register(klass, name, options={})
ns = name.split(/::|\./)
method, klass = ns.pop, (ns.empty? ? klass : Object.const_get(ns.join("::")))
store = options[:store].nil? ? PStore.new(name) : options[:store].new(name)
as_method = options[:as] || method
Memoize._set_store(name, store)
(class<<klass;self;end).instance_eval do # for Ruby1.9
method_name = "#{as_method}_without_memoize"
memoized_method_name = "#{as_method}_with_memoize"
define_method(memoized_method_name) do |*args|
store = Memoize._get_store(name)
ret = store.get(args)
if ret.nil?
ret = send(method_name, *args)
store.set(args, ret)
end
ret
end
alias_method method_name, method
alias_method as_method, memoized_method_name
end
end

# Specify unmemoize method.
# If you delete persistent cache when set delete_all true.
#
# == Example
# Memoize.memoize(self, 'slow_func')
# slow_func(arg)
# Memoize.unmemoize(self, 'slow_func')
#
def unmemoize(klass, name, delete_all=false)
(class<<klass;self;end).class_eval do
undef_method("#{name}_with_memoize")
alias_method name, "#{name}_without_memoize"
end
delete_all && @store.delete_all
end

# This method is public, but you don't need call directly.
def _set_store(name, store)
@@stores[name] = store
end

# This method is public, but you don't need call directly.
def _get_store(name)
@@stores[name]
end

# Not implemented
module Expire
end
end

if $0 == __FILE__
def fib(n)
return 1 if n < 2
fib(n-1) + fib(n-2)
end

class Math2
def self.fib(n)
return 1 if n < 2
fib(n-1) + fib(n-2)
end
end

class MemCacheStore < Memoize::Storable
attr_accessor :cache, :keys
def initialize(name)
@keys = []
@cache = MemCache.new 'localhost:11211', :namespace => 'rakuto.blogspot.com'
end

def get(key)
@cache.get(key)
end

def set(key, value)
@keys << key unless @keys.include?(key)
@cache.set(key, value)
end

def delete(key)
@cache.delete(key)
end

def delete_all
@keys.each { |key| @cache.delete(key) }
end
end


Memoize.register(self, 'Math2.fib', :store => MemCacheStore)
Benchmark.bm do |b|
b.report("fib(30) with memoize: ") { Math2.fib(30) }
#Memoize.unmemoize(self, 'fib')
b.report("fib(30)") { Math2.fib(30) }
end
end


考察
  • Memoizaionのテクニックは関数型言語と非常に相性がよさそう。
  • Railsのルーティング部分は副作用を含まないメソッド単位に分解して、Memoizedすることで高速化できないだろうか?

Wednesday, May 23, 2007

[Ruby] EXIFのGPS情報も取得できるExtExif 1.0をリリースした

extexif

RubyにはEXIF(Exchangeable image file format)を扱うための実装がいくつかあるが、全て開発がとまっている。Pure Rubyで実装されたruby-exifは、Ruby1.8だとパッチを当てないとコンパイルが通らない。二つ目はCで書かれたlibexifをRubyから呼び出せるように拡張ライブラリで実装されたruby-libexifであるが、2007年5月23日現在サーバが落ちている。幸いローカルにキャッシュがあったためコンパイルを試みたが、Ruby1.8.5ではコンパイルが通らない。Debainパッケージではコンパイルが通るように修正が施されているが、このライブラリはEXIFのGPSデータの取得をサポートしていない。

exif.c


#if 0
/* GPS tags are not supported in this library */
#endif


RubyのEXIF関連ライブラリはどれも開発がとまっている状況みたいなので、EXIFのGPS情報の取得もサポートするExtExifを作った。(RubyForge申請中なのでもう少し時間がかかりそう。)

インストール

% sudo gem install extexif


ドキュメント
ruby-libexifに実装されているExifクラスはそのまま利用可能であるが、もう一段抽象的な操作を提供するクラスとしてExtExifを実装している。現在のところは機能は少ないがこちらにEXIF関連で便利なメソッドとかを実装していく。

画像のパスを与えてクラスを生成して配列のようにEXIF Tag IDを使用してアクセスするだけです。

require 'rubygems'
require 'extexif'

# print EXIF data
IMG_FILE = 'image_with_exif.jpg'
image = ExtExif.new(IMG_FILE)
puts IMG_FILE
print "%-15s: %s\n" % ["Make", image["Make"]]
print "%-15s: %s\n" % ["GPSLatitude", image["GPSLatitude"]]
print "%-15s: %s\n" % ["GPSLongitude", image["GPSLongitude"]]



% ruby test_extexif.rb
image_with_exif.jpg
Make : KDDI-CA
GPSLatitude : 35.00, 39.00, 45.38
GPSLongitude : 139.00, 41.00, 16.89
%


EXIF Tag IDについては以下のサイトを参考に。
ExifTAG

exif.cに対するパッチのみいる人は以下。

< #include <ruby.h>
< #include <libexif/exif-tag.h>
---
> #include "ruby.h"
> #include <libexif/exif-ifd.h>
15a14
> #include <libexif/exif-tag.h>
137d135
< char value[1024];
142c140
< v = rb_str_new2(exif_entry_get_value(entry, value, sizeof(value)));
---
> v = rb_str_new2(exif_entry_get_value(entry));
219d216
< char value[1024];
246c243
< return rb_str_new2(exif_entry_get_value(e, value, sizeof(value)));
---
> return rb_str_new2(exif_entry_get_value(e));
253c250
< found = exif_entry_get_value(e, value, sizeof(value));
---
> found = exif_entry_get_value(e);
356c353
< rb_funcall(dest, rb_intern("<<"), 1, rb_str_new((char *)exif->ed->data, exif->ed->size));
---
> rb_funcall(dest, rb_intern("<<"), 1, rb_str_new(exif->ed->data, exif->ed->size));
379d375
< #define GPS_SUPPORT
457,488c453,454
< #ifdef GPS_SUPPORT
< {EXIF_TAG_GPS_VERSION_ID, "GPSVersionID", N_("GPS tag version")},
< {EXIF_TAG_GPS_LATITUDE_REF, "GPSLatutideRef", N_("North or South latitude")},
< {EXIF_TAG_GPS_LATITUDE, "GPSLatitude", N_("Latitude")},
< {EXIF_TAG_GPS_LONGITUDE_REF, "GPSLongitudeRef", N_("East or West longitude")},
< {EXIF_TAG_GPS_LONGITUDE, "GPSLongitude", N_("Longitude")},
< {EXIF_TAG_GPS_ALTITUDE_REF, "GPSAltitudeRef", N_("Altitude reference")},
< {EXIF_TAG_GPS_ALTITUDE , "GPSAltitude", N_("Altitude")},
< {EXIF_TAG_GPS_TIME_STAMP, "GPSTimeStamp", N_("GPS time (atomic clock)")},
< {EXIF_TAG_GPS_SATELLITES, "GPSSatellites", N_("GPS satellites used for measurement")},
< {EXIF_TAG_GPS_STATUS, "GPSStatus", N_(" GPS receiver status")},
< {EXIF_TAG_GPS_MEASURE_MODE, "GPSMessureMode", N_("GPS measurement mode")},
< {EXIF_TAG_GPS_DOP, "GPSDOP", N_("Measurement precision")},
< {EXIF_TAG_GPS_SPEED_REF, "GPSSpeedRef", N_("Speed unit")},
< {EXIF_TAG_GPS_SPEED, "GPSSpeed", N_("Speed of GPS receiver")},
< {EXIF_TAG_GPS_TRACK_REF, "GPSTrackRef", N_("Reference for direction of movement")},
< {EXIF_TAG_GPS_TRACK, "GPSTrack", N_("Direction of movement")},
< {EXIF_TAG_GPS_IMG_DIRECTION_REF, "GPSImgDirectionRef", N_("Reference for direction of image")},
< {EXIF_TAG_GPS_IMG_DIRECTION, "GPSImgDirection", N_("Direction of image")},
< {EXIF_TAG_GPS_MAP_DATUM, "GPSMapDatum", N_("Geodetic survey data used")},
< {EXIF_TAG_GPS_DEST_LATITUDE_REF, "GPSDestLatitudeRef", N_("Reference for latitude of destination")},
< {EXIF_TAG_GPS_DEST_LATITUDE, "GPSDestLatitude", N_("Latitude of destination")},
< {EXIF_TAG_GPS_DEST_LONGITUDE_REF, "GPSDestLongitudeRef", N_("Reference for longitude of destination")},
< {EXIF_TAG_GPS_DEST_LONGITUDE, "GPSDestLongitude", N_("Longitude of destination")},
< {EXIF_TAG_GPS_DEST_BEARING_REF, "GPSDestBearingRef", N_("Reference for bearing of destination")},
< {EXIF_TAG_GPS_DEST_BEARING, "GPSDestBearing", N_("Bearing of destination")},
< {EXIF_TAG_GPS_DEST_DISTANCE_REF, "GPSDestDistanceRef", N_("Reference for distance to destination")},
< {EXIF_TAG_GPS_DEST_DISTANCE, "GPSDestDistance", N_("Distance to destination")},
< {EXIF_TAG_GPS_PROCESSING_METHOD, "GPSProcessingMethod", N_("Name of GPS processing method")},
< {EXIF_TAG_GPS_AREA_INFORMATION, "GPSAreaInformation", N_("Name of GPS area")},
< {EXIF_TAG_GPS_DATE_STAMP, "GPSDateStamp", N_("GPS date")},
< {EXIF_TAG_GPS_DIFFERENTIAL, "GPSDifferential", N_("GPS differential correction")},
---
> #if 0
> /* GPS tags are not supported in this library */
490d455
<
567c532
< {-1, NULL, NULL}
---
> {0, NULL, NULL}
592c557
< for (i = 0; ExifTagTable[i].tag != -1; i++) {
---
> for (i = 0; ExifTagTable[i].tag != 0; i++) {


gemパッケージの中の写真はGOLDRU$Hの金曜限定ランチ1ポンドバーグ(450g)だというのは秘密。

Monday, May 21, 2007

[Ruby and Erlang] そろそろRBridge::Erlangについて一言いっておくか

RBridge::Erlang


を作った。

弾さんもErlangについて一言言うなどここ最近のErlangの盛り上がりは凄い。

Erlangを1週間ほどさわってみて思ったのは次の二つ。
  • Erlangの軽量なプロセスをRubyから扱いたい。
  • Erlang/OTPが実績もありすばらしい(そう)なのでRubyからその恩恵にあずかる


とりあえず、はじめてのErlanモジュール作成ということで悩んだ結果、RubyからErlangのメソッドを呼び出すRBridge::Erlangを作った。現在同期呼び出しのみサポートしている。ErlangとRuby間の通信におけるプロトコルはJSON-RPCである。


ダウンロード
rbridge-0.0.1.tar.gz

デモの動かし方。
コンパイル

% tar xvzf rbridge-0.0.1.tar.gz
% cd rbridge-0.0.1.tar.gz
% make


Erlang側のサーバを起動する。

% erl
1> c(geometry).
2> geometry:start().


後は、Ruby側から呼び出すだけ。

require 'rbridge'
geo = RBridge::Erlang.new(:geometry, "127.0.0.1")
geo.area({:x => 100, :y => 200}) # => 20000

ポートの変更
デフォルトのポートは3793番を使用しているので、使用している際は次のファイルのPortの設定を変更してください。その際は、Ruby側のRBridge::Erlang.newの第三引数に指定したポートを渡す必要あり。

% cat server_root/conf/httpd.conf
ServerName localhost
ServerRoot server_root
DocumentRoot server_root/htdocs
Port 3793
Modules mod_alias mod_auth mod_jsonrpc mod_actions mod_cgi mod_responsecontrol mod_trace mod_range mod_head mod_include mod_dir mod_get mod_log mod_disk_log
DirectoryIndex index.html
JsonRpcAlias /rpc
ErrorLog logs/error_log
TransferLog logs/access_log
SecurityLog logs/security_log


Erlang側のソース
関数を定義して、rbridge:start関数の引数にリストで渡すだけ。
rbridge:startの第一引数はお約束。?MODULE(現在のモジュール名)を渡してください。
第二引数にservice_procレコードのリストを渡す。idempotent=true or falseはGETリクエストによるJSON-RPCを受け付けるかどうか。POSTは現在実装していないのでtrueにしてください。paramsはサービスの引数を定義する。引数のキーの名前と型("bit" | "num" | "str" | "arr" | "obj" | "any" | nil)のいずれかを指定します。


-module(geometry).
-export([area/2, start/0, stop/0]).

-include("include/rbridge.hrl").

%% Get area
area(X, Y) when is_number(X), is_number(Y) ->
X * Y.

%% service_proc#type ::= "bit" | "num" | "str" | "arr" | "obj" | "any" | nil
start() ->
rbridge:start(?MODULE,
[#service_proc{name = <<"area">>,
idempotent=true,
params=[#service_proc_param{name = <<"x">>, type = <<"num">>},
#service_proc_param{name = <<"y">>, type = <<"num">>}]}]).

% Stop RBrige server
stop() ->
rbridge:stop().



TODO:
「RubyからErlang/OTPの恩恵をどうやって授かるか?」について色々と考えてみる。


追記
id:sfujiwaraさんがPerlから呼び出すコードを書いてくれています。
[Perl][Erlang] RBridge::Erlang を Perl から呼ぶ

Comet&Erlang勉強会@ドリコムのお知らせとRails勉強会@東京

Rails勉強会@東京 第18回に参加した。
参加したセッションは
ActiveRecord::QueryCacheMagic Multi Connection
舞波さん・最速配信研究会さん・瀧内さん・Yuguiさん(順不同)といったメンバーが参加されていた。細かいところでの主な論点はRailsのパーフォーマンスの最適化について。大きな部分(Page Cache)での最適化は使いどころが難しい→小さな部分での最適化を積もらせて全体としてパフォーマンスを上げたいが、Railsはそのへんが弱いという話。

・ActiveRecord::QueryCacheは関連についてもキャッシュを管理できるようにしたい。(舞波)
・ActiveRecord::with_scopeはRails2.0で内部的に使用されるようになり外部に公開されなくなる。(日本人は困る人大勢。。)
・バウンスメール処理や大量のメール配信の問題。(ミドルウェアで処理するかDRb)
・ERBが遅い。Cで書かれたERB実装を使用すると早くなる。Erubisとか。
・Routeが複雑&高機能すぎて遅い。ActionCacheは多用するのでRouteが遅いと困る。Routeは基本的な機能だけで十分なのでCで実装したいなぁ。(瀧内)


ShootingStarとErlang
に参加した。
ShootingStar2.0が出ました。
・クラスタリング機能をサポートしています。
・隠しAPIがいっぱいあるよ。問い合わせが多いと瀧内さんが大変そうなのでソース読んでくださいというのがいいのかな?
・ShootingStarのプロジェクトに新たにDoc Writeの安藤さんが追加されたので急速にドキュメント周りが整備されるかも。
・ErlangはRBridge::Erangを紹介。
・メニーコアが進む現在においてErlang+Rubyでどのような可能性があるかについて話し合う。
・画像処理や3Dなどのギリギリまでパフォーマンスが要求されるところでErlangは実用レベルで耐えれるだろうか。
・Erlangの処理系のソース読んで仕組みを理解しないと安心できない。
・Erlangは本当に早いの?


Comet&Erlang勉強会@ドリコム
日時: 5月27日
時間: 13時~
場所: ドリコム 

数少ないErlang勉強会がドリコムで開かれます。
Cometと絡めてC10K問題やErlang/OTPやHiPEについて実際に手を動かしながらプログラムを書くセッションがやりたいなぁ。

---------------------------------------------------
懇親会

結婚されてたとは知らなかったです。

だって、24時間オンラインだから。
どんな生活かと思いきや9時出社20時帰宅してご飯。22時にまた出社。始発で帰宅。一日二度顔合わせれば十分・・・凄い。

数年内にアメリカに渡るので、24時間アドレナリンでっぱなし・トランスしっぱなしとなるような、未来に残る研究開発・サービスつくりを一緒にやりたいです。同じ職場で働く日を楽しみにしてます。 > Hello, world!

Thursday, May 17, 2007

[Rails] Swiftiplyのアーキテクチャとベンチマーク

Swiftply(日本語で何て読む?)はRubyで書かれたHTTPに特化したプロキシフレームワークである。ApacheのModProxyBalancerやHAProxyなどのプロキシといったフレームワークと大きく異なる点として、プロキシとアプリケーションサーバの関係が上げられる。Swiftplyではブラウザからのリクエストを受け付けるSwiftplyサーバとリクエストを処理する複数のSwifplyクライアントから構成される。Swiftplyクライアントは全て同じアドレス・ポートでリクエストを待ち受ける。

Swiftiplyサーバーの設定ファイルの作成
config/swiftiply_config.ymlにサーバの設定ファイルを設置する。(パスは任意でOK)


cluster_address: 0.0.0.0
cluster_port: 80
#daemonize: true
timeout: 3
map:
- incoming:
- www.yourdomain.com
- www2.yourdomain.com
outgoing: 127.0.0.1:8000
default: true


設定項目
  • cluster_address: ブラウザからのトラヒックをListenするアドレス (String)
  • cluster_port: ブラウザからのトラヒックをListenするポート (Integer)
  • daemonize: デーモンで起動する時はtrue (true or false)
  • timeout: Swiftiplyがブラウザからのリクエストを保持する秒数(sec)。expireしたコネクションの場合は503が返される。 (Integer)
  • map: ドメイン(or IP)別にリクエストの宛先を定義するセクション (Hash)
  • incoming: ホスト名を指定する。配列で複数指定することも可能。(String or Array)
  • outgoing: リクエストの宛先を指定する。Swiftplyサーバからのリクエストは同じアドレス&ポートで待ち受ける複数のSwiftplyクライアントによって処理されるのでここで複数の宛先を指定する必要はない。(String)
  • default: ブラウザからのリクエストがどのincomingにもマッチしなかった際にそのパターンが適用される。(true or false)

Swiftply Mongrel単体での起動
swiftiply-X.X.X/bin/mongrel_railsというMongrelの起動スクリプトが用意されているのでそのスクリプトを使用する。

% SWIFT=1 /usr/bin/mongrel_rails start -e production -p 10000
% EVENT=1 /usr/bin/mongrel_rails start -e production -p 10000

SWIFTまたはEVENTという環境変数を指定することでMongrelをそれぞれのモードで起動できる。SWIFTモードではSwiftplyサーバを立ち上げていないと動作しない。

クラスターとして使用する
初めにサーバを起動する。
初めに示した上記の設定ファイルを使用した場合、80番ポートと8000番ポートがListen状態になる。

% swiftiply -c config/swiftiply_config.yml

8000番ポートに転送されたリクエストのデータを処理するMongrelを起動する。swiftiply_mongrel_raiilsというスクリプトが用意されているのでこれを利用する。(環境は強制的にenvironmentになる)

$ swiftiply_mongrel_rails start -n 10 -p 8000 -d


プロセスIDファイルが#{RAILS_ROOT}/log/dog#{n}.pidに生成されるので再起動する時はmongrel_railsスクリプトを使用する。(swiftiply_mongrel_railsは現在起動のみ)

% mongrel_rails restart -P log/dog1.pid


Apache + Mongrel Cluster環境とSwiftiplyでabを使用しベンチマークを取得した。
リクエスト数を500で固定し同時接続数を10~200まで上げて静的ページと動的ページのRequest per secoundの値を比較した。
CPUはIntel(R) Celeron(R) CPU 2.66GHz, メモリは2GB構成。



静的ページにおいてはかなり高速化している。

TODO
ソースリーディングとアーキテクチャについて理解を深める。
mongrel_clusterのように簡単に扱えるスクリプトを作成し実用で使えるかどうか検討する。

Wednesday, May 16, 2007

[Ruby] Ruby/EventMachineでネットワークプログラミング

Ruby/EventMachine

Ruby/EventMachineはシングルスレッドで高速に動作するネットワークプログラミングのためのライブラリでIPレイヤを扱うコア部分はC++で実装されている。(Pure Ruby版も用意されている。)

イベントドリブンなアーキテクチャで実装されており、ユーザはIP層を全くほとんど意識することなくプロトコルの実装に集中できるように設計されている。ユーザが書くソースコードでは決まったクラスを継承し決まったメソッドをオーバーライトするだけでよい。

現在の実装は、Unix系のシステムを使用している場合はslect(2)システムコールで実装されている。将来のバージョンではepoll(4)に置換したいとのこと。

文字数を返すサーバ


require 'rubygems'
require 'eventmachine'

module CharacterCount < EventMachine::Connection
def post_init
puts "Received a new connection"
@data = ""
@line_count = 0
end

def receive_data(data)
puts "Received data: #{data}"
send_data "#{data.length} (characters)\r\n"
close_connection_after_writing
end
end

EventMachine::run do
host, port = "127.0.0.1", 3793
EventMachine.start_server(host, port, CharacterCount)
puts "Now accepting connections on address #{host}, port #{port}"
EventMachine.add_periodic_timer(10) {$stderr.write '*'}
end



EventMachine::Connection#post_initがクライアントからの接続を受け付けた時に呼び出され、受信したデータはEventMachine::Connection#received_dataの引数に渡される。post_initはEventMachine内のループでリクエストを受け付けるたびにクラスがインスタンス化され呼び出される。ユーザのコード内でこのクラスをインスタンス化することは決してない。

日付を返すメソッド(get_date)を定義したサーバ。

require 'rubygems'
require 'eventmachine'

# This is simple protocol which get date.
class GetDateProtocol < EventMachine::Connection
def receive_data(data)
puts "Received data: #{data}"
if pattern = data.split("get_date: ")[1]
send_data Time.now.strftime(pattern)
elsif data.index("exit") == 0
close_connection_after_writing
else
send_data "ERROR: This method isn't implemented.\r\n"
end
end

def unbind
puts "Connection is closed."
end
end

EventMachine::run do
host, port = "127.0.0.1", 3793
EventMachine.start_server(host, port, GetDateProtocol)
puts "Now accepting connections on address #{host}, port #{port}"
end

unbindは接続が切断された時に呼び出される。その他のメソッドについてはeventmachin.rbを参照。

ネットワーク部分の大部分がC++で書かれており高速に動作しかつ、プロトコルの実装に集中することができる。Ruby/EventMachineのドキュメント中ではC10K問題がライブラリを作るきっかけとなったことについて触れられているが、大本命はやはりErlangかな。

Wednesday, May 09, 2007

[Erlang] 実行時間計測

Eralngで簡単な実行時間計測を行うモジュールを作った。

statistics(runtime), statistics(wall_clock)の一度目の呼び出しはそれぞれCPU時間・実経過時間を0に初期化する。返り値は次の要素から成るタプル(引数にwall_clockを指定)で構成される。


{Total_Wallclock_Time, Wallclock_Time_Since_Last_Call}


prof.erl

-module(prof).
-export([start/0, stop/1, test/0]).

%% prof:start() -> B.
%% Start the profiler.
start() ->
statistics(runtime),
statistics(wall_clock),
{_, T1} = statistics(runtime),
{_, T2} = statistics(wall_clock),
{ok, {T1, T2}}.

%% return -> {Runtime, Wall-clock time}
stop(B) ->
case B of
{ok, {OldT1, OldT2}} ->
{_, T1} = statistics(runtime),
{_, T2} = statistics(wall_clock),
{T1- OldT1, T2 - OldT2};
Other ->
{error, Other}
end.

%% test()
%% Profile test
test() ->
B = start(),
lists:foreach(fun (X) -> math:pow(X, 10) end, lists:seq(1, 1000000)),
{Time1, Time2} = stop(B),
io:format("time=~p (~p) msec~n", [Time1, Time2]).



実行結果

1> c(prof).
{ok, prof}
2> prof:test().
time=300 (1194) msec
ok
3> prof:test().
time=370 (1238) msec
ok


症状
プロセス数を増やしすぎて、CPU時間が短いのに実経過時間が極度に大きくなる場合は、物理メモリが不足しスワップが発生しているサインである。

Tuesday, May 08, 2007

株主への理解

ドリコム内藤社長と対談13(株式分割と資本政策)

とりあえず、「バフェットからの手紙」第三章 普通株を読み、もう一度株価と株式分割について熟考していただきたい。

現状の株主戦略のままでは、中長期的なオーナーの考えを持って株式を購入する投資家を獲得するのは無理である。投機家により株式の売買が繰り返され、企業の内在価値を反映しない株価が続く限り中長期的な株主は損害を被ることになる。

ウォーレンバフェットが示すコーポレートガバナンスを引用すれば、現状の第二形態から第三形態にシフトする改革の時ではないだろうか。

Friday, May 04, 2007

[Erlang] listsモジュールの関数一覧 1



Programming Erlang(PDF)を購入した。

形式コメントからドキュメントを生成するErlang Docのような物が存在するかも知れないが、知らないのでリストに関する操作を提供するモジュール/usr/lib/erlang/lib/stdlib-1.14.2/src/lists.erlからリスト関数を一つずつ見ていきここにまとめることにした。Erlangの規約にのっとり関数名/Arity(関数の引数の数)で示す。

追記
Programming ErlangのAppendix A "Documenting Your Program"を参照。


また、Erlangではモジュールは自作のモジュールも含めてコンパイルされる。標準ライブラリとして提供されているモジュールは/usr/lib/erlang/lib/stdlib-1.14.2/ebin/いかに(環境によって異なる).beam(Bogdan's Erlang Absctract Machine)拡張子で保存されている。

Tips
Erlangインタプリタ上で一度束縛した変数の束縛を解除するにはf().関数を使用する。

listsモジュールの関数一覧

append/2
二つのリストを連結する。

13> lists:append([1,2], [2,3]).
[1,2,2,3]


subtract/2
集合Aから集合Bを引いた差集合を返す。

14> lists:subtract([1,2], [2,3]).
[1]


reverse/1
リストを逆順にする。

15> lists:reverse([1,2,3,4]).
[4,3,2,1]


nth/2
リストの先頭からN番目の要素を取り出す。

6> lists:nth(3,["UIE", "Japan", "Inc"]).
"Inc"


nthtail/2
リストの先頭N要素を取り除いた残りのリストを返す。

20> lists:nthtail(1, ["UIE", "Japan", "Inc"]).
["Japan","Inc"]


prefix/2
リストAがリストBの先頭要素と等しければtrue, 異なればfalseを返す。

35> lists:prefix(["UIE"], ["UIE", "Japan", "Inc"]).
true
36> lists:prefix(["UIE", "Japan"], ["UIE", "Japan", "Inc"]).
true


suffix/2
リストAの末尾要素がリストBの末尾要素と等しければtrue, 異なればfalseを返す。

38> lists:suffix(["Inc"], ["UIE", "Japan", "Inc"]).
true


last/2
配列の最後の要素を返す。

44> lists:last(["UIE", "Japan", "Inc"]).
"Inc"


seq/2 :: seq(MIN, MAX)
整数値MINからMAXまでの順序数を返す。

45> lists:seq(10,20).
[10,11,12,13,14,15,16,17,18,19,20]


seq/3 :: seq(MIN, MAX, Incr)
整数値MINからMAXまでの順序数を返すが増分値を整数Incrの値とする。

48> lists:seq(10,20,3).
[10,13,16,19]


sum/1
リストの合計を返す。

51> lists:sum([10,20,30,40]).
100


duplicate/2 :: duplicate(N,X) XをN個持つリストを返す。

55> lists:duplicate(10, 2).
[2,2,2,2,2,2,2,2,2,2]


min/1
リスト中の最小値を返す。

56> lists:min([1,2,3,4,5]).
1


max/1
リスト中の最大値を返す。

57> lists:min([1,2,3,4,5]).
5


list/2
リスト中の部分リストを返す。(開始要素は先頭要素)

60> lists:sublist([1,2,3,4,5], 3).
[1,2,3]


list/3 :: sublist(List, Start, Length)
リスト中の部分リスト(Start..Start+Length)を抽出する。

58> lists:sublist([1,2,3,4,5], 2, 3).
[2,3,4]


delete/2 :: delete(Item, List) -> List
リスト中からItemを削除する。

62> lists:delete(3, [1,2,3,4,5]).
[1,2,4,5]


zip/2
引数に渡した各リスト(要素数は2つ)の要素からなるタプルのリストを返す。
 
63> lists:zip([1,3,5,7], [2,4,6,8]).
[{1,2},{3,4},{5,6},{7,8}]


unzip/2
zip/2と可逆の操作を行い結果をタプルで返す。

64> lists:unzip([{1,2},{3,4},{5,6},{7,8}]).
{[1,3,5,7],[2,4,6,8]}


zip/3
引数中のリストからタプルの組を生成してリストで返す。

69> lists:zip3([1,4,7], [2,5,8], [3,6,9]).
[{1,2,3},{4,5,6},{7,8,9}]


unzip/3
zip/3と可逆の操作を行い結果をタプルで返す。

70> lists:unzip3([{1,2,3},{4,5,6},{7,8,9}]).
{[1,4,7],[2,5,8],[3,6,9]}


zipwith/3 :: zipwith(F, X, Y)
XとYの要素を順に抽出しF(X1,Y1), F(X2,Y2)...なるリストを返す。

76> Mod = fun(X, Y) -> X * Y end.
#Fun
77> lists:zip(Mod, [1,3,5,7], [2,4,6,8]).
[2,12,30,56]


zipwith3/3 :: zipwith(F, X, Y, Z)
X, Y, Zの要素を順に抽出しF(X1,Y1, Z1), F(X2,Y2, Z2)...なるリストを返す。

82> Volume = fun(X, Y,Z) -> X * Y * Z end.
#Fun
83> lists:zipwith3(Volume, [1,4,7], [2,5,8], [3,6,9]).
[6,120,504]


まだまだあるが、長くなったのでとりあえずココまで。

Thursday, May 03, 2007

GW中に読む4冊

「Life is beautiful: 21世紀の錬金術:Web2.0バブルで一儲けする方法」が人気エントリになることや、このブログでもGoogle Analysticsでの解析結果でコンテンツ上位5位以内に「IPOから学んだ事柄」が必ず入っており、IPOという言葉がITベンチャーで働く者にとって考えさせられる大きな問題となっていることはほぼ間違いない。

真にコーポレートガバナンスを理解しオーナーの利益と従業員含めた企業の利益の両方を追求するための哲学なき経営者が上場すべきでは無いと思う。そこでGW中に読む本として、オハマの賢人と敬愛を込めて呼ばれる、ウォーレン・バフェット氏に関する本を中心として4冊を購入した。金融・会計・経済と言った(私にとっては)専門分野とは異なる分野のため、多くの専門用語に時間を割かれながらもどれも非常に勉強になる本である。

尊敬できる経営者が運営する「企業を買う」(数十年単位の)バリュー投資により富を築いたバフェット氏の本と世界トップの頭脳を集めコンピュータ武装した天才集団LTCMによる裁定取引による荒稼ぎから破綻まで。この二冊を読み比べるだけでも学ぶべきことは非常に多い。









バークシャー・ハザウェイ社の年次報告書にて株主に向けて書かれる投資家必読ともいわれる「会長からの手紙」。それを聞きたいがばかりにバークシャーの株主になる人もいるというから驚きだ。その「会長からの手紙」を一冊にまとめたのが「バフェットからの手紙」であるが会計に関する用語が多く出てくるため、まずは上に示した順序で「最強の投資家バフェット」を読んでから、時間をかけてじっくりと「バフェットからの手紙」を購読するのがおすすめ。

バフェットからの手紙は、哲学書として一生手元においておき繰り返し参照したい一冊である。

Wednesday, April 25, 2007

Web2.0時代の起業

21世紀の錬金術:Web2.0バブルで一儲けする方法

学生4年間で学んだことそのままです。
外部の人間や経験ある人間から見れば周到に仕組まれた道を進んでいることが明白でも、内部で働く人間には気づきにくいこと、そして上場が簡単にできてしまう日本の新興市場が問題でしょうか。もし、Web2.0ブームに乗ってアイデア一つ・WEBサービス一本で起業するならばExit戦略はM&A(買収)されることに見据えるのが普通だろう。

関連
ポールグレアムはハッカーでなくて

Tuesday, April 24, 2007

[Rails] SlowQueryLoggerでRSpecのSpecファイルも実行するように変更

specの場合はmodelテストのほうが良さげ

いつかやろうと放置プレイしていたところを指摘してもらったので実装しました。

SlowQueryLogger


Index: slow_query_logger.rb
===================================================================
--- slow_query_logger.rb (revision 5)
+++ slow_query_logger.rb (revision 6)
@@ -116,10 +116,11 @@

if __FILE__ == $0
# get all controlelrs name
- controller_pattern = Pathname.new(File.join(RAILS_ROOT, 'app', 'controllers', '*.rb'))
- controllers_list = FileList.new(controller_pattern.to_s)
- controllers_list.map! { |path| File.basename(path).sub('.rb', '') }
-
+ controller_pattern = Pathname.new(File.join(RAILS_ROOT, 'app', 'controllers', '*.rb'))
+ model_pattern = Pathname.new(File.join(RAILS_ROOT, 'app', 'models', '*.rb'))
+ controllers_list = FileList.new(controller_pattern.to_s).map! { |path| File.basename(path).sub('.rb', '') }
+ models_list = FileList.new(model_pattern.to_s).map! { |path| File.basename(path).sub('.rb', '') }
+
# Clear the slow query log
ActiveRecord::Base.slow_query_logger.clear

@@ -127,14 +128,19 @@
unittest_functional_test_file = File.join(RAILS_ROOT, 'test', 'functional', "#{controller}_test.rb")
rspec_controller_spec_file = File.join(RAILS_ROOT, 'spec', 'controllers', "#{controller}_spec.rb")

- # check for unit test or spec file of rspec
+ # checks for unit test or spec file of rspec
if File.exists?(unittest_functional_test_file)
require unittest_functional_test_file
elsif File.exists?(rspec_controller_spec_file)
require rspec_controller_spec_file
-# rails_spec = Pathname.new(File.join(RAILS_ROOT, 'script', 'rails_spec')).cleanpath.to_s
-# cmd = rails_spec + " " + Pathname.new(rspec_controller_spec_file).cleanpath.to_s
-# system cmd
end
+
+ # checks for file of RSpec model's spec
end
+
+ # Checks for file of RSpec model's spec
+ models_list.each do |model|
+ rspec_model_spec_file = File.join(RAILS_ROOT, 'spec', 'models', "#{model}_spec.rb")
+ require rspec_model_spec_file if File.exists?(rspec_model_spec_file)
+ end
end



modelのSpecファイルが存在する場合には、コントローラのSpecと合わせてそちらも実行するように変更しました。RSpecでBDDを行うと、主たるロジックは全てモデルに実装してコントローラからはモデル中に定義したメソッドを呼び出すだけになる。

先日実装したActiveRecord::QueryCacheでキャッシュ戦略を管理する場合にも、モデル中のメソッド内で呼び出しているAR#find一つ一つに対してキャッシュ指定することになる。

これは細かい指定ができていいかもしれないが、モデルのメソッド(でいくつかの複雑で重いSQLを実行し加工した)結果自体をキャッシュしたいことがある。ARと直接関連するわけではないので、キャッシュの削除戦略が複雑になる。現在考え中。

こんなの?

class IndexController < ApplicationController::Base
def index
@me = session[:user]
@my_loves_friends = cache(:key => "index_controller_loves_friends", :expire => 10.minutes) do
@me.find_loves_users(:limit => 10)
end
end
end

Monday, April 23, 2007

[Rails] ActiveRecord::QueryCacheにキャッシュの削除に関する指定を実装した。

はてぶやdel.icio.usで意見をもらったので、ActiveRecord::QueryCacheにキャッシュの削除に関する設定をできるように実装した。

使用方法
:expire_methodsでキャッシュを削除するメソッドを指定する。指定できるメソッドは、ActiveRecord#after_○○のメソッド名。


class Person
query_cache :expiry => 3.minutes, :expire_methods => [:create, :update, :destroy]
end


実装

def query_cache(options)
options.assert_valid_keys([:expiry, :expire_methods])
write_inheritable_attribute('query_cache_expiry', options[:expiry] || 0)
[options[:expire_methods]].flatten.each do |method|
__send__("after_#{method}") { __send__(:delete_all_query_cache) }
end unless options[:expire_methods].nil?
end


memcachedを本格的に運用するためには超えなければいけない壁があるんだけど、それは面白そうなので社内勉強会ネタとして開催してみよう。

Thanks for コメントくれた人

Sunday, April 22, 2007

[Rails] ActiveRecord::QueryCacheを実装した。CacheStoreにはmemcachedを使用。

ActiveRecord::QueryCache

ActiveRecordの富豪っぷりに困っている。キャッシュの仕組みが用意されているみたいだが過去の遺物となり使用されていない様子。(See Class::ActiveRecord::QueryCache) だから作った。

ActiveRecordの富豪っぷり

  • ActiveRecord#findではフレーム関数を大量に呼び出し深いスタックを生成する。
  • 毎度引数+同じ値で呼び出されるのに毎度SQLの生成を行う。

そこでキャッシュ戦略でも比較的基本的な、クエリ単位でのキャッシュをおこなうクエリキャッシュとしてActiveRecord::QueryCacheを実装した。引数をキーにしてキャッシュを保存するため、SQLの生成も行わず高速に結果をフェッチすることができる。今後は、エンタープライズでのO/R Mapplerのキャッシュ戦略などを調べてもう少し拡張していく予定。

ActiveRecord::QueryCache

仕様
  • SQL単位ではなくActiveRecord#findに渡された引数をキーとしてキャッシュを保存する。
  • キャッシュの保存先にはmemcachedのみ対応。(2007/4/22現在)
  • メソッド単位でキャッシュのexpiryを設定することはできない。クラス単位で指定する。(改良するかも)


インストール
% sudo apt-get memcached
% sudo gem install memcached-client
% svn co http://shindaita.stiq.net/svn/cache_on_rails/query_cache.rb /path/to/raisapp/lib


Railsで使用する場合
# config/enviroment.rb
require 'query_cache'
CACHE = MemCache.new 'localhost:11211', :namespace => 'rakuto.blogspot.com', , :multithread => true


サンプル
CACHE = MemCache.new 'localhost:11211', :namespace => 'rakuto.blogspot.com'

class Person < ActiveRecord::Base
query_cache :expiry => 10.minutes
end

p Person.find(:all, :conditions => "name like '%'", :cacheable => true)


キャッシュが有効だと引数からSQLを生成することも無く、DBへ参照問い合わせを行うことも無いため高速に結果をフェッチすることが可能である。

キャッシュを削除するメソッドは現在一つだけ用意している。ActiveRecord#delete_all_query_cacheメソッドを使用すると、そのクラスで使用しているキャッシュが全て削除される。(仕組み的にはキーを配列で記録しておいて、その値をフェッチしてキーに該当するキャッシュを全て削除する)

Person.delete_all_query_cache


ActiveRecordで作成, 更新 or 削除があったときにキャッシュを削除する指定を実装。
[Rails] ActiveRecord::QueryCacheにキャッシュの削除に関する指定を実装した。

エラー処理 (2007/4/23日追加)
memcached関連でエラーが発生した場合でもロギングして処理を続行する(通常通りDBへアクセスを行う)サイレンスモードを実装。デフォルトではtrueに設定。

class Person
query_cache :expiry => 3.minutes, :expire_methods => [:create, :update, :destroy], :silence => false
end


TODO
  • キャッシュ戦略についてもう少しバリエーションを増やしたい。
  • memcached以外のCacheStoreへの対応。
  • キャッシュの削除戦略が普通はどうするものなのかよくわからない。詳しい人教えてください。


Bug Fix

AR#findに第一引数(:all, :first)のみ渡した場合のbug fix. (Revision 8)


Saturday, April 21, 2007

[Rails] Changeset 6197でランダムなsession_keyを使用するようになった変更へのMobile on Railsの対応

こちらのChangeset 6197でRailsアプリ名を含むランダムなsession_keyを使用するようになったので、Mobile on Railsもそれに対応した。

Mobile on Rails

Index: workspace/mobile_on_rails/lib/mobile/controller.rb
===================================================================
--- workspace/mobile_on_rails/lib/mobile/controller.rb (revision 65)
+++ workspace/mobile_on_rails/lib/mobile/controller.rb (revision 66)
@@ -31,7 +31,7 @@

def rewrite_options_with_session_id(options)
if request && request.mobile?
- session_key = ::ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_key] || '_session_id'
+ session_key = self.class.read_inheritable_attribute("session_options")[0][:session_key] || '_session_id'
if request &amp;& request.session.session_id
options = options.dup
options.update session_key => request.session.session_id



Changeset 6184によるとRailsのデフォルトのSessionStoreとして新たにCookieベースのSessionStoreが実装された様子。Rails中でセッションを使用する場合、flashに代入するエラーメッセージやログイン後のユーザオブジェクトなどがほとんどのため、4kbyteの制限内に収めることができる。その場合は、PStoreよりもCookieStoreの方が実行速度が速いとの事。

Tuesday, April 17, 2007

SlowQueryLoggerのブックマークが

限りなく少ないのに笑った。

SlowQueryLogger
仕方が無いので自己リンク。

2はてぶ
1クリップ
8デリ

テストを実行してEXPLAIN情報からボトルネックとなるクエリをソース中で使用されている箇所とともに、書き出すアイデアは面白いと思ったのになぁ。。
VaryなCacheへの対応・複数DB対応・ボトルネック測定など組み込んで洗練させて、
Scaling Rails Suiteとして出せばもっとうける?

P.S
yuin元気かな?

Sunday, April 15, 2007

IPOから学んだ事柄

経営者像に正解は無いし実に色々と存在する。業種により成果をあげる経営者像は異なるだろうし、時代によっても変化するだろう。忠誠を激しく要求するようなスティーブジョブスのような経営者もいれば、ある意味無駄なルールは無視できるエッジのきいた人間を望むようなCEOもいる。

おそらくこれは経営者のバックグラウンドに依存するところが大きい。
UIEJの社長は営業出身でシリコンバレーで働きグリーンカードも持っている。そして、人脈や恩義・忠誠を非常に大事にしているように感じる。UIEのCEOは今でもプログラマであり型破りな意見を言う。相手が偉い人であったりクライアントであろうと自分の考えは突き通す人に見える。



ドリコムを辞めて1年になるがその間の変化はとても大きいようだ。
外野からの見物になるため、メディアを通じて誇張された認識なども含むかもしれない。しかし、「会社はファンのための物」と語るCEOは、外部ではなく社内にファンを作れているのだろうか?

上場企業で自由な社風という甘い言葉に誘惑されて入社した世間知らずの新卒の"ファン"などではなく、「CEOが示すビジョンを実現するためなら、倒れようとプログラムを書きつづける」というような明日の売り上げとなる技術を開発する優秀な技術者のファンを獲得できているのだろうか?

私がドリコムが一番失敗したと思うことはIPOではなく人事に対する散財だ。
経験が豊富な人間や優秀な技術者が転職したいと思える会社つくりではなく、なぜ新卒の採用なのか?魅力のある会社を作りプロモーションをかければ優秀な人間は自然と集まってくる。IPOで得た資金を散財しただけに見合う社員を集めれたのだろうか?その資金を優秀な技術者が集まる会社の風土作りへ当てていたらどういう社員が転職してきただろうか?

過去に成功したビジネスを捨てきることができず、売り上げが上がらない→営業力が足りないとの意思決定→PUSHの営業体制のため人材を整える→急速な人材確保のためにIPOで得た資金が大量に使われるというスパイラルにはまり、本来のコアコンピタンスであった技術や技術者が持つマインドに対するトップマネジメントの理解などどこかに忘れ去られているように感じる。

今の売り上げを確保するための社員だけでなく、明日の売り上げを作り上げる技術者が必要である。パイの合計が決まっている市場でゼロサムゲームをしていては企業の未来は無い。パイが無くなれば新たなパイがある市場に飛びつくことになる。

ドリコムに優秀な技術者がいるが、CEOの示すビジョンで彼らのマインドを魅了し引き止める事ができなくなった際には、優秀な技術者の大規模な流出が起こりえるかもしれない。そうなった時には、目先の売り上げに飛びつき、株式交換により外部の小さなベンチャーの買収を繰り返し、自社の営業力を使って売り上げを計上し上場させ有価証券売却益を本社の売り上げに付け替えるという図式しか考えられない。

私はそのような企業に何の魅力も感じない。

どうしても気になるもう一点が企業の情報統制の甘さである。
毎日が文化祭みたいな会社と書き、社内の写真やミーティングの内容などを赤裸々につづっている新卒社員のブログを時々拝見するが、まともな情報管理体制を引けているのか疑問である。

自由な社風で自分達が主役などという甘い言葉は、経験もあり実績もある優秀な社員が集まった上で初めて形になるものだろう。自由を履き違えてはいけない。

大和証券では新卒社員が5分遅刻した際に、上長がブチきれ机を蹴飛ばしその社員の胸ぐらをつかみかかり罵倒し、その後見せしめとしてその日一日中新卒社員で「遅刻に対して」というお題でMTGが開かれた話を友達から聞いた。新卒社員に対してこの上長が取った行動は正しかったと思う。


PS
私がIPOから学んだ事は、IPOをするために何をするかではなくIPOで得ることのできる資金で何をするかを見据えIPOすることだ。言われてみれば当たり前のことである。

写真は今日自宅に届いたテーブルの写真(写真は別のショッピングサイトのもの)。赤色がポイントとなっていてとてもカッコイイ。

追記
社内のマネジメント体制が確立していない企業が、なぜ企業にも大きな教育コストを割くことになる新卒の採用にこだわるのか?と考えた時の答えは、一つしか思いつかなかった。「新卒を採用しているという事」自体が急成長中の企業であるということを示すためのプロモーションだったのだろう。明らかにプロモーションすべき方向を誤ったように見える。企業のプロモーションも手がけた人事であっただけに、人事担当者の責任は重い。

Thursday, April 12, 2007

[Rails] Unit::TestかRSpecのSpecファイルを実行しボトルネックとなるクエリを書き出すSlowQueryLoggerを作った

久々に面白いモノを作った。

SlowQueryLogger

RailsではActiveRecordを利用してガンガン富豪プログラムを書きがちなため、適切にインデクスを張っていないとすぐに重たくなるので、Rails中で発行された遅いクエリをロギングするプログラムを作った。

ActiveRecordの参照系クエリを発行するメソッドをEXPLAINにより実行解析情報を取得しロギングしてから通常の参照クエリを発行するように拡張し、Rails付属のUnit::TestのfunctionalテストまたはRSpecのコントローラーのSpecファイルを実行する。最終的には、filesortか一時テーブルを使用しているクエリがログファイルに書き出される。

つまり、functional testを定義したファイルかRSpecのcontrollerのスペックファイルがあれば特に何も用意する必要はない。


現在MySQL限定だが、次のような情報もあわせてロギングするためSlow Query Logを見るよりもずっと解析が容易になる。

  • 参照問い合わせが呼び出されたソースファイルと行番号
  • 実行されたSQL
  • EXPLAIN情報


インストールとデプロイ
#{RAILS_ROOT}/script/いかに配置後実行するだけ。

% svn co http://shindaita.stiq.net/svn/slow_query_logger/
% mv slow_query_logger/slow_query_logger.rb /railsapp/script/
% chmod +x /railsapp/script/sloq_query_logger.rb

実行

% ./script/slow_querry_logger.rb

仕様
  • functional testとコントローラのRSpecファイルが両方存在する場合はfunctional testを実行する。
  • 実行解析情報のExtraフィールドにfilesort or temporaryが存在する時にロギングする。
  • 現在MySQLのみ。
  • 実行の度にログファイルは初期化される。


log/mysql_slow_query.logの形式
カラーで出力するとかはやらない。

/var/www/rails_app/config/../app/models/user.rb:250:in `find_hoge'
SQL: EXPLAIN SELECT entries.`id` AS t0_r0, entries.`user_id` AS t0_r1, entries.`title` AS t0_r2, entries.`body` AS t0_r3, entries.`created_on` AS t0_r4, entries.`updated_on` AS t0_r5, entries.`group_id` AS t0_r6, entries.`unread_flag_for_self` AS t0_r7, entries.`comments_num` AS t0_r8, entries.`deleted_at` AS t0_r9, entries.`deleted_flag` AS t0_r10, entries.`last_updated_on` AS t0_r11, entries.`last_comment_id` AS t0_r12, entries.`photo_key` AS t0_r13, groups.`id` AS t1_r0, groups.`name` AS t1_r1, groups.`owner_id` AS t1_r2, groups.`public_level` AS t1_r3, groups.`category_id` AS t1_r4, groups.`photo_id` AS t1_r5, groups.`introduction` AS t1_r6, groups.`created_on` AS t1_r7, groups.`updated_on` AS t1_r8, groups.`users_num` AS t1_r9, groups.`official_flag` AS t1_r10, groups.`entry_num` AS t1_r11, groups.`deleted_flag` AS t1_r12 FROM entries LEFT OUTER JOIN groups ON groups.id = entries.group_id WHERE (( entries.deleted_flag = false ) AND ( group_id IN (NULL) )) ORDER BY entries.updated_on DESC LIMIT 5
select_type : SIMPLE
key_len : 5
type : ref
id : 1
Extra : Using where; Using filesort
possible_keys : entries_group_id_index,index_entries_on_group_id_and_created_on
table : entries
rows : 23
ref : const
key : entries_group_id_index

/var/www/rails_app/config/../app/models/user.rb:260:in `find_hoge_hoge'
SQL: EXPLAIN SELECT events.`id` AS t0_r0, events.`group_id` AS t0_r1, events.`title` AS t0_r2, events.`date` AS t0_r3, events.`supplement_date` AS t0_r4, events.`location_id` AS t0_r5, events.`supplement_location` AS t0_r6, events.`user_id` AS t0_r7, events.`description` AS t0_r8, events.`created_on` AS t0_r9, events.`updated_on` AS t0_r10, events.`deleted_at` AS t0_r11, events.`deleted_flag` AS t0_r12, events.`comments_num` AS t0_r13, groups.`id` AS t1_r0, groups.`name` AS t1_r1, groups.`owner_id` AS t1_r2, groups.`public_level` AS t1_r3, groups.`category_id` AS t1_r4, groups.`photo_id` AS t1_r5, groups.`introduction` AS t1_r6, groups.`created_on` AS t1_r7, groups.`updated_on` AS t1_r8, groups.`users_num` AS t1_r9, groups.`official_flag` AS t1_r10, groups.`entry_num` AS t1_r11, groups.`deleted_flag` AS t1_r12 FROM events LEFT OUTER JOIN groups ON groups.id = events.group_id WHERE (( events.deleted_flag = false ) AND ( group_id IN (NULL) )) ORDER BY events.updated_on DESC LIMIT 5
select_type : SIMPLE
key_len : 5
type : ref
id : 1
Extra : Using where; Using filesort
possible_keys : index_events_on_group_id,index_events_on_deleted_flag_and_updated_on
table : events
rows : 1
ref : const
key : index_events_on_group_id


ソース中の51行目の以下の部分を変更すればロギングするクエリを変更できます。(Extraカラムに対する正規表現パターン指定)

LOGGING_PATERN_FOR_EXTRA_FIELD = /(filesort|temporary)/


SlowQuerryLogger使って適切にインデクス張って、ログファイルが出力されなくなるようにしましょう。

参考
5.2.1. EXPLAIN 構文(SELECT に関する情報の取得)
7.2.1. Optimizing Queries with EXPLAIN
第5章 MySQL の最適化
Chapter 7. Optimization

Wednesday, April 11, 2007

世田谷にある地中海料理屋DOMANI

通勤途中にあり引っ越してきた時から気になっていた海路ドマーニというお店に行って来ました。帰りに前を通る度にガーリックとオリーブオイルのとてもいい匂いがします。

シャルドネ (ボトル) 3990円


牛肉のカルパッチョ 970円


アサリのガーリックオイル 1890円


フキノトウとサザエのオイルパスタ 1600円
サザエの苦味をベースにしたオイルパスタ。


うにのクリームソースパスタ 1680円


バニラアイスにエスプレッソ 800円


どれもおいしかったけどせもりなのパスタにあったような感動が無かった。
また世田谷価格なのか価格設定が20%高い気がした。後、少し味が濃かった。

ただ一人でボトル一本あけてまだ頼もうとすると同じワインをグラス一杯分サービスしてくれた。こういう時いつもお店の人に覚えてもらえるので得だなと思う。

地中海料理でおいしかったのは京都のノルマンディがとてもおいしかった。もう随分と昔の気がするがBOCCA del VINOと共にまた行きたい。

渋谷のおすし屋

渋谷にあるすし善というお店に連れて行ってもらった。

常連さんを大切にするためマスメディア等での紹介は行っていないみたい。このようなお店はネットなどでは見つけられないため、常連として通っている人に連れて行って貰うしか知る術がありません。こんな名店に連れて行ってくれた方にとても感謝します。

どの品もとてもおいしかった。
何よりもうれしかったのが日本酒が豊富でとてもおいしい事とお酒がすすむおつまみをたくさん出してくれること。日本酒は酔鯨(高知)以外初めて飲むものばかりでした。酔鯨も大好きなので注文しました。

黒龍
石鎚
久平次
鯨酔(高知)

鯛(おろしポン酢)
ウニ(ご飯と共に)
タコ(スダチで)
イカ(生姜醤油 or わさび醤油)
とり貝(刺身とにぎり)
だし巻き(絶品) 
赤貝(刺身)
こはだ

まだまだ食べましたが忘れてしまいました。本当にどれも絶品でした。
出し巻きといえば京都の八条口にある佳辰というお店のうまきも絶品ですが、それを超えるぐらいおいしかった。

大将もとてもいい人です。
ここで一つラッキーえびすビールと呼ばれるえびっさんが鯛を両手に抱えているビールが存在することを知りました。大将が7年やっていてはじめて見たとのことなのでお目にかかれる機会はほとんど無いかもしれません。

また是非行きたいお店です。

Sunday, April 08, 2007

[ruby] 特異メソッドに別名をつけて退避する

特異メソッドをalias_methodを使用して別名をつけようとするとエラーになる。
だが、同じブロック内でrespond_to?(:find)を実行するとtrueが返される。


undefined method `find' for class `ActiveRecord::Base' (NameError)



module EffectiveRails
module ActiveRecord
def self.included(klass)
klass.send ClassMethods
klass.class_eval do
p respond_to?(:find) # => true
alias_method_chain :find, :explain
end
end

module ClassMethods
def find_with_explain(*args)
# hogehoge
find_without_explain(*args)
end
end
end
end

ActiveRecord::Base.class_eval do
include EffectiveRails::ActiveRecord
end



Rubyの少々ややこしいクラスとオブジェクトの仕組みによるものだが、特異メソッドに別名をつけて退避したい場合は、alias_method (_chain)メソッドではなくてaliasをinstance_evalのブロック内で使用するとよい。


module EffectiveRails
module ActiveRecord
def self.included(klass)
klass.send ClassMethods
klass.instance_eval do
alias :find_without_explain :find
alias :find :find_with_explain
end
end

module ClassMethods
def find_with_explain(*args)
# hogehoge
find_without_explain(*args)
end
end
end
end

ActiveRecord::Base.class_eval do
include EffectiveRails::ActiveRecord
end



同じような現象でハマッた方も多いのではないだろうか?

Monday, April 02, 2007

AppleとEMIの提携発表Webキャスト

2 April press conference

日本時間で言えば本日PM9時頃~かな。
ビートルズの配信契約の話ではなく、EMIがDRM撤廃を発表するとの事。
スペシャルライブも行われます。

追記
先ほど終了。
EMIアーティストのミュージックビデオもDRMフリーで提供されるほか、
2007年度はデジタルコンテンツの著作権が大きく変わりそうな年。年内にiTunes Storeの50%がDRMフリーかつ高音質で提供されると予測するスティーブジョブス。


ジョブズとEMI、DRMフリーダウンロードサービスを発表

Sunday, April 01, 2007

私がUIEJに就職した7の理由

  1. CEOがプログラマ
  2. 会社のビジョンを経常利益などではなく、製品・技術に信念を持って語る。
  3. Microsoft/NVIDIAなどグローバルな大企業での経歴を持つ人が多い。
  4. 収入を下げてでもやりたい事のためにUIEJに転職した人が多い。
  5. スタートアップ。企業0.1から企業1.0へする過程に関われる。
  6. CEOと別に経営を担当するプレシデントがいる。(UIE)
  7. グローバルでビジネスを展開しており、アメリカに渡る機会を得やすい。
人の心をつき動かすのは、未来を創造するような何か大きな事ができるという期待とプロジェクトを進める人たちの強烈な情熱である。お金に振り回されるのではなく自分達の世界観を実現するためにお金を振り回すんだよ。

Friday, March 30, 2007

[雑記] コンシューマー事業で闘うということ

コンシューマー事業では、ユーザは製品・サービスを通じて企業を鏡写しに見ることになる。

流行や趣向の変化が激しいこの戦場では、ある時期に出されたサービスだけでなくサービスを通じて形成される企業のブランドを高めることがとても大事である。コンシューマー事業のトップに立つ人間が中途半端なビジョンしか頭になく、自信と信念を持ってビジョンを対外的に示せないとなればコンシューマ市場での勝負は必ず敗北する。

コンシューマ事業では単一の製品やサービスを購入することは、個人が購買を決めた製品を提供する企業を支持することに等しい。その戦場でユーザを虜にし勝つために必要な 事は「自分達が提供するビジョンを信念を持ってプロモーションできるリーダー」と「不可能に思えるビジョンを可能にしてしまう情熱溢れる天才エンジニア」である。

自分の作るサービスや製品が市場でどのように普及し使われるかを一番理解し示さなければいけないのはコンシューマー事業を率いるトップだが、皮肉にもユーザの方が企業の価値やブランドを企業内部の人間より正確なものさしで見ていたりする。

Monday, March 26, 2007

ActiveRecordには最適化する余地がたくさんある。

UIEJでもisamu136さんと盛り上がっていたのですが、ActiveRecordが富豪過ぎる件。えぐちさんも同じことを書いているのを発見。

ActiveRecordのupdateが富豪過ぎる件

  • AR#saveやAR#update_attributesはレコード数が増えるとインデクスの更新がバカにならない。
  • :stringでは無駄に可変長カラムを使用する。
  • replace構文の使用
  • デフォルト値をなるべく設定する。

複数のDB間の差異を埋めるための抽象化で犠牲になっている部分も多くて、個人的にはほぼMySQLのみを使用するのでMySQLに特化させるAR拡張が欲しいところ。

参考
MySQLの最適化

[EffectiveRails] Vary(多様)なキャッシュに対応するcache_on_rails.rbを作った。




昨日買ったBeauty Flowを聞いているとカッコよくて気分がのってきたので、イマイチ使いづらいRailsのキャッシュ周りをEffectiveにしてみた。

cache_on_rails.rb
cache_on_rails API



使用方法

上記のスクリプトを#{RAILS_ROOT}/lib以下に設置してconfig/enviroment.rbの下のほうに次の一行を追加するだけ。


require 'cache_on_rails'


詳しくはソース中のコメント参照。

次の二点を拡張した。
  • HTTPリクエストヘッダの値を用いてキャッシュするコンテンツを切り替える。
  • ブロックを評価した値をキャッシュのキーとして利用する。返り値が同じ場合は同一のキャッシュされたコンテンツが利用される。


一つ目はHTTPリクエストヘッダの値をキャッシュを生成する際のキーとして利用する。例えばユーザエージェント毎に異なるキャッシュを生成する際は次のように指定する。

ユーザーエージェント別にキャッシュを生成する。

class IndexController < ActionController::Base
caches_action_with_vary :index, :vary => :UserAgent
end

class IndexController < ActionController::Base
caches_action_with_vary :index, :vary => [:UserAgent, :AcceptLanugage]
end


:vary => #{リクエストヘッダ名 or リクエストヘッダ名の"-"を削除して大文字で接続したもの}でキャッシュのキーに影響を与えるヘッダ名を指定する。:vary => [:UserAgent, :AcceptLanguage]のように配列で指定することで複数のリクエストヘッダが一致するリクエストに応じたキャッシュを生成できる。

レスポンスヘッダには、:vary => #{HTTPリクエストヘッダ名}で指定したHTTPリクエストヘッダ名がVaryヘッダに指定して返される。Varyヘッダは大文字・小文字を考慮しない。

Vary: user-agent, accept-language


しかし、これだけではRailsのキャッシュの使い心地はまだまだなので、ロケール別・PCのキャリア別・PC/携帯別といった感じに自由にキャッシュを生成できるようにした。ブロックを評価した値をキャッシュのキーとして利用する。ブロック引数にはコントローラが渡される。

以下の例は、gettext/railsと組み合わせて、ロケール毎にキャッシュが生成されるようにする。

class IndexController < ActionController::Base
caches_action_with_vary :index do |controller|
Locale.current.language
end

end

次の例は、Mobile on Railsを使用して携帯とPCのそれぞれでキャッシュを生成する。

class IndexController < ActionController::Base
caches_action_with_vary :index do |controller|
!! controller.request.mobile? # => true or falseがキャッシュのキーとして利用される。
end

end
ブロックの評価値をキャッシュのキーとして利用するので、キャリア毎のキャッシュを保存する事も簡単である。

Cache on Railsの実用性はこれから拡張していくけど、アイデアとしては面白いと思うがどうかな?

絵に描いた餅

大学も卒業し生まれ育った京都も今月一杯。

チャーチルの「過去にこだわる者は未来を失う」という言葉があるが、現在自分が何をすべきかに焦点を合わせて最高の力を注ぎ込めない人間は大きな事を成し遂げられない。



人の精神は非常に繊細にできている。
過去の事を引きずり悩み迷う。周りの人の気持ちや受けた恩義などが意思決定において影響を及ぼす。それらの過去が生活におけるありとあらゆる所で影響し判断を鈍らせる。これらの答えもゲーム理論が明快な答えを示してくれるだろうか?

「大学4年間で何を失ったか?」と聞かれれば「大人になったこと」。

  • 人の気持ちを考え発言するようになった。
  • 一歩引くことも覚えた。
  • キツイ言葉もオブラートにつつんで言えるようにもなった。
  • いい奴と思われるように行動する。
  • 周りに合わせる。
大学4年間で「他の人が扱いやすい人間」へと成長した。
全くつまらない人間だ。組織の中で歯車として働くならいい歯車になれるかもしれない。
真に大切な事は、周りの人に気を使うことではなく、絶対目標において貢献することだ。
わりかし自己を抑えて遠慮して生きてきましたが、4月からこういったつまらない振る舞いは全て辞めることにします。

中途半端では何一つ楽しめない。
何かを成し遂げるには、周りから異常者とも見られるくらいとことんやらなければいけない。過去の経験や感情など周りの外的要因に惑わされている人間に誰が惹かれるだろうか。未来を見て信念を持ち、楽しいと思える事に向かって異常なエネルギーも持って突き進まなければならない。


昔、クラブの顧問の先生とよく言い争いをした。闘争することが大好きだったが、その時にいつも言われたのが、「絵に描いた餅」だった。顧問に屈しないその姿もあって、ヒトラーと冗談じみたあだ名がついた。「絵に描いた餅」にならないように4年間プログラムを書き続けてきた。本当のお楽しみはこれからだ。

Friday, March 23, 2007

TBWA - 1984の製作を手がけた広告代理店

memo.

TBWA
TBWA Japan

1984の製作を手がけた広告代理店。

via Wikipedia
新会社にはTBWAが40%、博報堂ジーワンが60%出資している。
なるほど。

アメリカは悪の帝国と戦うヒーローが好きだね。オラクルでもアップルでもマイクロソフトを悪の枢軸扱いして自分達はヒーローを演出する。会社の演出はとても大事。自社のプロモーションのために、敵を作り悪役に仕立てるところはとってもステキ。

敵が強ければ強いほどヒーローの活躍が目立つ。
オフライン上の全ての情報までも自社に集めGoogle政府をもくろむGoogle帝国も敵としては申し分ないと思う。

Thursday, March 22, 2007

[Rails] SoftBank携帯からの制御文字を含む入力の変換に関するバグFix (Mobile on Rails)

SoftBank携帯からフォームを通して入力された文字列中に制御文字列が含まれる際に変換がおかしくなるバグを直しました。(Revision 49)

Mobil on Rails

また、SoftBank(Vodafone)の3G携帯にはSJISの外字領域を使用して絵文字を送出する機種が存在します。それらの機種については現在未対応(絵文字は全て削除される)です。

SoftBankの絵文字のSJIS(hex)データを載せたドキュメントがPDFしかなくセキュリティ上の理由でテキストに変換することもできないため、現在キャプチャを取って画像として起こしてOCRでテキストデータへの変換をするというバッドノウハウで対応中。


Index: lib/mobile/emoji/softbank.rb
===================================================================
--- lib/mobile/emoji/softbank.rb (レビジョン 48)
+++ lib/mobile/emoji/softbank.rb (レビジョン 49)
@@ -13,9 +13,15 @@
bytes = str.unpack('C*')
len = bytes.length
ret = ''
while i < len do
- if bytes[i] != 0x1b
- ret += [sprintf("%x", bytes[i])].pack('H2')
+ if bytes[i] != 0x1b # Not emoticons
+ # Multibyte characters
+ if (bytes[i] > 0x80 && bytes[i] < 0xa0) || (bytes[i] > 0xdf && bytes[i] < 0xf0)
+ ret += [sprintf("%x%x", bytes[i], bytes[i+=1])].pack('H4')
+ else # Single byte character
+ ret += [bytes[i]].pack('C')
+ end
else
emoji_bytes = []
emoji_bytes << bytes[i] while bytes[i+=1] != 0x0f




Index: test/emoji_test.rb
===================================================================
--- test/emoji_test.rb (レビジョン 48)
+++ test/emoji_test.rb (レビジョン 49)
@@ -81,6 +82,7 @@

def test_softbank_convert_internal_encode
assert_equal Emoji::SoftBank::Convert.convert_internal_encode(@sb_binary), @sb_internal
+ assert_equal Emoji::SoftBank::Convert.convert_internal_encode(@sb_binary_with_ascii), @sb_internal_with_ascii
assert_equal Emoji::AU::Convert.convert_internal_encode(@m_sb_binary), @m_sb_internal
end

Tuesday, March 20, 2007

[Rails] WILLCOMの絵文字入力&変換に対応(Mobile on Rails)

Mobile on Rails

Mobile on Rails RDoc

Mobile on RailsをWILLCOM絵文字の入力と各キャリアへの絵文字の変換に対応させて、いくつかのバグFixを行いました。絵文字の変換は完全に私の主観です。

WILLCOMは絵文字数が少ないため、他のキャリアからの変換の際にゲタ文字になることが多いです。
Mobile on Railsの絵文字変換表を更新し次のRakeタスクを実行することで更新された絵文字変換テーブルを使用できます。


% rake updatedat


絵文字変換表のみ使用される場合にはこちらよりダウンロードしてください。
そういえば、WILLCOMもウノウの人が作っていたかな?

参考

WILLCOM コンテンツ作成マニュアル(リファレンス編)

Thursday, March 15, 2007

ドリコムズと飲み会

本気でスカウター開発合宿しませんか? > ドリコム

主な論点。
・DB Hacks (ダンボールハック)
・次の10年のキラーアプリはスカウター
・集中力マネージメント
・人の無意識の行動の可視化

-------------------------------------
ダンボールプロトタイピング 2.0
を出版するとかしないとか。

監修
・ダンボールハウスの人
・ダンボールガンダムの人

目次
・ダンボールの歴史
・ダンボールを安く仕入れる方法
-------------------------------------

-------------------------------------
スカウター

アクセサリーのようにスカウターをガジェットとして持ち歩く時代がきっと来る!
プロモーションは、CanCamで。
-------------------------------------

-------------------------------------
集中力マネージメント

プログラマのアウトプットの量は集中力をどれだけ持続できるかの差だよね。という話。
ここからここは集中時間のように決めると、その時間帯での個人へのあらゆる割り込みが一旦キューにエンキューされて一切割り込みが入らなくなる。プライオリティ付けも可能。

コンシェルジェ・キュー。
-------------------------------------

-------------------------------------
無意識行動の可視化

それキーロガーで。
-------------------------------------

Sunday, March 11, 2007

Happy Birthday songs

東京で迎える誕生日。22歳になりました。
今まで出会った人達全てに感謝。

Happy Birthday (Bonus Track)

Celebration

Happy Birthday (Bonus Track)は、内装が面白いCELEBで流れていました。かっこいいハッピバースデーソング。(iTunes Music Storeで買えます。)
バラガン邸宅でもいいなぁと思ったけど、建物の中に水面があって水が流れている音がする感じいいよな。

「方向を定めて迷わず見失わずがむしゃらに突き進む事で本当にやりたい事を実現できる」って言葉が頭に残っていて「俺、迷いがあるな」と気づきました。残り少ない人生を迷いを振り切りバカになって突き進むのみです。

ごちさそうさまでした。

Saturday, March 10, 2007

[Rails] 携帯からのリクエストの際に呼び出すアクションを切り替える

Mobile on Railsでは、携帯からのリクエストの時には、#{RAILS_ROOT}/app/views_mobile/以下からテンプレートを読み込みますが、呼び出すアクションも変更したい時があるのでアクションの切り替えを試しに実装してみました。

携帯からのリクエストの際の仕様

  • __mobile_#{action_name}というメソッドがコントローラ内に定義されている場合はそちらを呼び出す。
  • __mobile_#{action_name}を呼び出した際もviewのテンプレートパスは通常通り#{RAILS_ROOT}/app/views_mobile/#{action_name}.rhtml
  • __mobile_#{action_name}が定義されていない際は、action_nameが呼び出される。

携帯からのリクエストの際に別のアクションを実行したい場合のみ、__mobile_#{action_name}を定義することでそちらのアクションが実行されます。定義しない際は通常のアクションが呼び出され、携帯用のviewが表示されます。

インストール方法

% ./script/plugin install -x http://shindaita.stiq.net/svn/mobile_on_rails/trunk/mobile_on_rails/


変更箇所は、コントローラのActionController::Base#perform_actionとActionView::Base.full_template_path.


Index: mobile_on_rails/lib/mobile/controller.rb
===================================================================
--- mobile_on_rails/lib/mobile/controller.rb (revision 166)
+++ mobile_on_rails/lib/mobile/controller.rb (working copy)
@@ -7,15 +7,27 @@
module ActionController #:nodoc:
# This module is offer fixtures for perform request from mobile.
module Mobile
+ # prefix which is add action method name when request is from mobile
+ PREFIX_OF_REQUEST_FROM_MOBILE = "__mobile_"
+
def self.included(base)
base.class_eval do
after_filter :after_filter_for_mobile
before_filter :before_filter_for_mobile
alias_method_chain :rewrite_options, :session_id
+ alias_method_chain :perform_action, :mobile
end
end

protected
+ def perform_action_with_mobile
+ if request && request.mobile?
+ _action_name = "#{PREFIX_OF_REQUEST_FROM_MOBILE}#{@action_name}"
+ @action_name = _action_name if self.class.action_methods.include?(_action_name)
+ end
+ perform_action_without_mobile
+ end
+



--- mobile_on_rails/lib/mobile/view.rb (revision 166)
+++ mobile_on_rails/lib/mobile/view.rb (working copy)
@@ -3,7 +3,8 @@
protected
def full_template_path_with_mobile(template_path, extension)
if controller.kind_of?(ActionController::Base) &amp;& controller.request.mobile?
- full_template_path_without_mobile("../views_mobile/#{template_path}", extension)
+ _template_path = template_path.sub('/__mobile_', '/')
+ full_template_path_without_mobile("../views_mobile/#{_template_path}", extension)
else
full_template_path_without_mobile(template_path, extension)
end

Mobile on Railsでぐぐってもこのブログが全くヒットしないのが面白い。
ξ;゜ー゜)ξ { 遅レス。強し。

Thursday, March 08, 2007

STARZ FILE - Freewayがイイ




STARZ FILE

iTunes Music Storeでダンスミュージックのニューリリースを聞きながら、気に入ったのを買っていた時に見つけたアルバムSTARZ FILEがカッコイイ。中でも1曲目のFreewayがかなりイイ!先週旅行行く前に作ったドライブ用のプレイリストが、STUDIO APARTMENT, FPM, Clazziquai Projectなどで夜っぽい感じだったのでFreewayを追加した。

Freewayの心地良い音とラップ好きだな。

Wednesday, March 07, 2007

[Rails] Mobile on Railsの変更

卒論も終了したので、
絵文字変換をメインに作っていてコントローラ等のソースが"とりあえず実装"だったため書き直しました。
ディレクトリ構成が変わっています。

Mobile on Rails

バージョンアップしましたのでアップデートよろしくお願いします。
レポジトリのパスが変わっているのでご注意ください。

TODO

  • WILLCOMの絵文字への対応
  • セキュリティ周りの対応
    • 基本はルーターでゲートウェイのIPチェック
    • アプリレベルでゲートウェイのIPチェックはパフォーマンスが心配
    • アプリレベルでは外部サイトへのリンクはセッションIDを付加しない
  • キャッシュなど


Tips: 携帯からのリクエストとPCのリクエストでレイアウトを切り替え

layout lambda { |controller| controller.request.mobile? ? 'chtml_main' : 'pc_main'}