- Author: harajune
- Filed under: IT
Tuesday
May 12,2009
activerecordの様なDBラッパーを作ろうと思って、 @ujm の協力の下module_evalを使って実装してみた。
必要最小限のことしか書いてないのでDBラッパーとしては貧弱だけど、module_evalの動きに注目してみてほしい。
本物のactiverecordも似たような実装をしていたと思う。(うろ覚え)
RUBY:
-
require "rubygems"
-
require "mysql"
-
-
class ModelBase
-
-
DEFAULT_DBUSER = "user"
-
DEFAULT_DBPASS = "pass"
-
DEFAULT_DBHOST = "host"
-
DEFAULT_DBNAME = "name"
-
-
@@options = {"dbhost" => DEFAULT_DBHOST,
-
"dbuser" => DEFAULT_DBUSER,
-
"dbpass" => DEFAULT_DBPASS,
-
"dbname" => DEFAULT_DBNAME
-
}
-
-
def self.get_db_object()
-
@@mysql_obj ||= Mysql::new(@@options["dbhost"], @@options["dbuser"], @@options["dbpass"], @@options["dbname"])
-
end
-
-
def self.table_name(tn)
-
module_eval <<"EOS"
-
class <<self
-
def get_table_name
-
'#{tn.to_s}'
-
end
-
end
-
EOS
-
end
-
-
def self.query(sql, place_holder=[])
-
db_obj = get_db_object
-
stmt = db_obj.prepare(sql)
-
stmt.execute(*place_holder)
-
stmt
-
end
-
-
def self.count(condition="1", options=[])
-
sql = "select count(*) as count from #{get_table_name} where " + condition
-
res = query(sql, options)
-
count = res.fetch
-
count[0].to_i
-
end
-
-
private_class_method :get_db_object
-
end
これを次のように使う。たとえばuserテーブルを扱うクラスはこんな感じ。
RUBY:
-
require "lib/ModelBase.rb"
-
-
class UserModel <ModelBase
-
table_name "users"
-
-
def initialize(id, name, screen_name, location, description, profile_image_url, url, protected, followers_count, friends_count, time_zone, favorites)
-
@id = id
-
@name = name
-
@screen_name = screen_name
-
@location = location
-
@description = description
-
@profile_image_url = profile_image_url
-
@url = url
-
@protected = protected
-
@followers_count = followers_count
-
@friends_count = friends_count
-
@time_zone = time_zone
-
@favorites = favorites
-
end
-
-
def save
-
-
sql = "insert ignore into #{UserModel::get_table_name} (id, name, screen_name, location, description, profile_image_url, url, protected, followers_count, friends_count, time_zone, favorites) values (?,?,?,?,?,?,?,?,?,?,?,?)"
-
UserModel::query(sql, [@id, @name, @screen_name, @location, @description,
-
@profile_image_url, @url, @protected, @followers_count,
-
@friends_count, @time_zone, @favorites])
-
end
-
-
end
ModelBaseクラスを継承し、table_nameメソッドでテーブル名を指示しているわけです。
で、ポイントはModelBaseクラスの次の部分。
RUBY:
-
def self.table_name(tn)
-
module_eval <<"EOS"
-
class <<self
-
def get_table_name
-
'#{tn.to_s}'
-
end
-
end
-
EOS
-
end
UserModelの冒頭でtable_nameとこのメソッドを呼んでいます。
module_evalは、そのメソッドが呼ばれたところをselfにして実行されるメソッドであるため、このtable_nameメソッドの中で定義されているget_table_nameは、UserModelのクラスメソッドとして定義されます。
つまり、UserModel内でtable_nameが呼ばれた時点では、次のコードと同じことをします。
RUBY:
-
class <<UserModel
-
def get_table_name
-
'#{tn.to_s}'
-
end
-
end
こうすることによって、プログラム中でテーブル名をハードコードしなくてもすむようになるとともに、countなどの関数を親クラスで定義できるようになるわけです。
例えば、この方法で作ったクラスで全レコード数を数えるコードはこうなります。
RUBY:
-
require "lib/UserModel.rb"
-
-
UserModel::count
大変すっきりしていますね。
蛇足:
これはtwitterのポストを扱うために書いているプログラムの一部です。
rails使おうかとも思ったのだけど、railsはまだ多機能すぎるためこの程度なら書いてしまった方が悩みが少ない(重いとかファイルの配置とか拡張性とか)かと思って自分で書いています。
しかし、自分でフレームワーク作るかというと・・・・たぶんないですね。
フレームワークは必要じゃない機能もたくさん足すことになります。
なんの目的もなしに機能追加を繰り返していくと、フレームワーク自身のメンテナンスに負荷がかかるようになってしまうし、コードの自由度がどんどん下がっていきます。
それよりは、構造化を心がけながらプログラミングをし、拡張性を担保しながら、必要十分なものを作っていく・・・と、いうのが賢いプログラミングである気がしました。