Categories
open all | close allTags
デュアル・コア | パソコン | RESTful | Aptana | テスト | Migration | モデルSearch
Archive for April 2008
愚痴りたい気持ちはやまやまですが,とにかく動くようにすることが先決なので,なんとか解決法を考えました。
プラグイン・クラスの一覧を取るアルゴリズムですが,まずはClassレベルにおける定数を調べ,そこでModuleに属するものを選び,さらに,Module内の定数で「Base」を含んでいるものを選びます。この中から,Base付きのクラスの先祖のクラスを調べ,そこにFoodynPlugin::Baseがあるかどうかで判断しています。本来ここはクラスレベルで判定できるはずですが,上記の理由によって,わざわざ文字列に変換して比較しています。
美しくはありませんが,ともかくこれでスキン変数が動くようになりました。なんだかどっと疲れました。
結局,プラグインの子クラスの一覧を取る機能は次のように実現しました。
最初にpluginlistにArrayを入れてサイズを調べるといったまだるっこしいところをしているのは,またもやリロードのときにpluginlistが空になってしまうという問題に対応するためです。
中身はClassでの定数の配列を獲得し,それがModuleの名前かどうかを調べ,次にさらにそのモジュールでBaseという定数が定義されているかどうかを調べて,最後にFoodynPlugin::Baseと比較しています。
ところが,リロードすると最後の比較のときにtrueにならなきゃいけないところがなぜかnilに。理解できない。こまったなあ。
プラグインが存在すると,そのメイン部分のクラスを返します(ここもインスタンスを返すようにしたほうがいいかも)。そうするとパーサ側でこのプラグインのインスタンスに対してdo_skin_varメソッドを発行するわけです。
これで動くのですが,よく分からないのは,ページをリロードすると@@pluginlistがなくなってしまうこと。ですから1回目はスキン変数を表示しますが,リロードすると表示しません。クラスはロードしたままだけど,変数は消してしまうということでしょうか。そうだとするとinheritedに頼ることはできないので,このロジック自体考え直す必要がありそうです。ちょっといやらしい実装です。
Subversionのレポジトリ利用など,Railsの機能が利用できるから
・インストール後にFoodyn CMSの管理画面で利用のオンオフをする(デフォルトオフ)
プラグインはFoodynPlugin::Baseクラスの子クラスとして作りinit.rbでrequireしておく。このクラスはスタブであって,本体のコードは入らない。管理画面ではクラス一覧を獲得してそれを表示する。一覧の中でプラグイン・モデル(後述)に登録されていないものがあったら,自動的に登録する。
プラグインの本体はFoodynPlugin::Mainクラスの子クラスとして記述する。
管理画面で「オン」すると,インストールを実行する。具体的にはイベントの登録,スキーマの更新(プラグイン独自のテーブル,プラグイン・オプション)を行う。
「オフ」にするとイベントは削除。スキーマはそのまま。「アンインストール」はイベントおよびデータを削除。init.rbも削除(ここまでしないと一覧時にまた表示されてしまうから)。
「更新」はプラグインのコードを上書きしてから実行できる。イベントとスキーマ,プラグイン・モデル内のバージョン情報を書き換える。
・~~変数はNucleusと同様do_skin_varなどで実装する
Ruby的にはパーサを拡張するような実装も可能だがスキン・エンジン依存になるため,なるべくやらない。
・イベントはevent_~~などのメソッド実装で登録
管理画面上でプラグインをオンにするときに,そのプラグインのメソッドを舐めて調べる。
・PreItemを使うプラグイン用にPostUpdate系のイベントを用意
アイテム表示のたびにPreItemで走査するのは無駄なので決まりきった変換(例えばWiki系の入力補助など)はアイテム登録/更新時に変換する。変換前のデータを保持するためアイテムの本文用にフィールドを追加。
・プラグイン・オプション
プラグイン・オプションはブログ・オプションであればブログの拡張テーブル,アイテム・オプションであればアイテムの拡張テーブルに保存する。プラグイン本体のオプション用テーブルもある。
・独自管理画面
Admin用のクラスを別に持たせる。Nucleusのようにプラグインに直にリクエストが行くのではなく,どこかで受けてから渡すほうがいいだろう。
・アクション
これも一回システム側で受けてからプラグインを呼び出す方向。トラックバックのようなものはRESTのフォーマット追加でやりたいだろうと思うので,RESTのフォーマット追加やアクション追加ができるような仕組みを考えたい。
・スキーマの機能
プラグイン・オプションとプラグインの独自テーブルは,RailsのMigrationと同様の機能で更新する。
・必要なデータ・モデル
プラグイン管理用のモデルは,プラグイン名,バージョン,スキーマ・バージョン,オンオフ情報を登録
イベント管理用モデルはプラグインIDとイベントの名前を登録
プラグインオプションの説明モデルはプラグインID,オプション名,オプションの種類(ブログ,カテゴリー,…),データ・タイプ,デフォルト値,入力補助用データ
プラグインオプションのデータ・モデルは,元モデルごと(ブログ,カテゴリー,…)にテーブルを用意する。
その理由の一つはプラグインの実装をインスタンスでしているのに呼び出し側はクラスを呼んでしまっていたところにありました。
そもそもあるプラグインのインスタンスはシステムに一つしかない必要があります。そのため,いわゆるシングルトンのデザインパターンになります(RubyではSingletonモジュールをクラスに読み込むだけですが)。クラスはもともとシングルトンなので,プラグインをクラスで実装しても構わないということになります。
ところが,実際に書いてみると,クラスでプラグインを実装するとかなり分かりにくくなることが判明しました。具体的には,プラグインのベースクラス(プラグインを実行するかどうかにかかわらず,必ず読み込む部分)では,読み込まれたプラグインの一覧をクラス変数に確保するためにクラスメソッドを用意しています。それとプラグイン本体の部分とがごちゃごちゃになってしまうという分かりにくさがありました。
一方で,プラグインを呼び出すときは,プラグインのクラスに直接メソッドを送った方が話が早いので,ついそう書きたくなってしまうのです。結局プラグインの名前を抽出するメソッドをクラス・メソッドにもインスタンス・メソッドにも置くなど,なんだか汚いコードになってしまっています。少し設計を整理した方がいいのかもしれません。
また,多分後回しにすると思うけどユーザー管理部分も手が付いていない。それになによりもまだ管理画面は皆無に近い状況です。管理画面はRESTfulな設計なので案外一つ動けばスムーズに行くのではないかとも期待しているのですが今の状況ではなんとも。
やらなきゃいけないことに対して,時間が足りなさすぎ… ちょっとしんどいなあ。
で,ようやく移行作業もほぼ落ち着き,コード書きに戻っていますが,このところ取り組んでいるのはプラグイン用のさまざまな機能。まだ完全ではないですが,マイグレーションのところは大分進みました。プラグインではそのプラグイン用のmigrationディレクトリに
といった形で書くだけ。これで適宜フィールドを作ったり,コラムを作ったりテーブルを作ったりします。いろいろなデータタイプに対応するなど,まだいくつか作業はありますが,基本的な仕組みは考えたとおりに動いています。
というわけで,ここ2日ほど,これまでのパソコンからデータを移したり,ソフトをインストールし直すなどの作業をしています。今週末には通常モードに戻る予定。
プラグインの雛形もgeneratorで生成できるようにしたいのですが,ちょっと面倒なので,もっと仕様が落ち着いてから手を付けることになりそうです。
migrationでインデクスを生成させていたら,「名前が長すぎる」というエラーが。インデクスに使うカラム名を名前に入れていくので,カラムが複数とかになるとすぐに長くなってしまいます。とりあえずテーブル名などを短くすることで対処しましたが,名前設定てできるのかな?
タイトルにあるように,主体はWebサービスの設計。FoodynのようなWebアプリケーションについての記載はあまり多くはありません。それでも,これまで考えてきたことを改めて整理したり,RESTfulな考え方をなじませるためにはかなり役に立ったような気がします。例えば,トランザクション処理は,これまでステートレスであるRESTfulにはなじまないものと思い込んでいましたが,トランザクション自体をリソースとして処理する方法が挙げられており,これには目から鱗が落ちる思いがしました。
例として挙げられているコードはほとんどがRuby on Railsなので,読者もRubyとRailsが分かることがある程度要求されそうです。Ruby知らなくても概ね雰囲気はつかめると思いますが。
RESTfulなWebサービスやアプリケーションを開発する人にとっては一度は読むべきものだと思います。
前に書いたように,Railsのマイグレーションに相当する機能をプラグインに対して実現しようとしています。ここでマイグレーションのファイルを調べにいこうとしているのですが,ディレクトリを自動的に検知してファイルを走査するところがうまくいきません。プラグインのクラスがあるファイルに対して相対的なディレクトリを示す方法が見つかっていないからです。プラグインの親クラスで定義すると,親クラスのあるディレクトリに対して実行してしまいます。
マイグレーションのファイルを読み込ませてしまえばなんとかなるとは思うのですが,毎回読み込むのは無駄ですし。うーん,困った。
これをlibディレクトリにtest_db.rbとして保存し,コンソールを開いて a = TestDB.new と a.createtable を実行したらテーブルができました。実際にはテーブルの存在チェックなどが必要ですが,ともかくこれでプラグイン内でテーブルが作れることが分かりました。
ActiveRecord::Schemaのドキュメントを見ると
といった書き方ができるようです。
プラグイン・オプションも含めてプラグイン内マイグレーション機能を入れたら結構使いやすいのではないかといったことを考えています。
例えばプラグイン独自のテーブルを用意したい場合。Migrationを使ってテーブルを生成するのだとすると,db/migrationディレクトリにファイルを作らないといけません。つまりプラグインをインストールした後,script/generateでmigrationファイルを作るといった手順になります。act_as_authenticatedなどはこの方法を使っています。
ただ,プラグインと本体のテーブルは別々に管理したいといったことを考えるとプラグインのコード内でテーブルを作ったり削除したりする,Nucleusに近いスタイルの方が書きやすいかもしれません。このとき,プラグインのコードからcreate_tableなどを呼び出す方法がまだつかめていません。ActiveRecordの中のコードなので,呼び出し方はあるはずだと思うのですが。
検索しても,そういったイレギュラーなやり方は見つからないので,暗中模索といった感じです。
PS. やっぱり一筋縄では行きそうにありません。無謀っぽい。
イベントの処理に取り掛かる
プラグイン関連の処理でイベントの部分を書いています。アクティブなプラグインからイベント情報を収集してデータベースに保存するところは完成。プラグインを呼び出すところまではできているのですが,その後がなぜか動かない。デバッガが使えないと不便です。雰囲気的には変数名が違っているといった単純なものではないかと思っているのですが。美しくない解決法
いろいろ調べるうちに,FoodynPlugin::Baseというクラス・オブジェクトが二つ存在していることが分かってきました。「なんてこった」って感じです。クラスはシングルトンじゃないんでしょうか。愚痴りたい気持ちはやまやまですが,とにかく動くようにすることが先決なので,なんとか解決法を考えました。
def self.list
@pluginlist ||= Array.new
if @pluginlist.size == 0
@pluginlist = Class.constants.select do |c|
cclass = c.constantize
if (cclass.is_a? Module) && (cclass.constants.include? 'Base')
klass = (c+'::Base').constantize
klass.ancestors.any? {|anc| anc.name == 'FoodynPlugin::Base'}
end
end
end
@pluginlist
end
def self.plugin_class plugin_name
if (pl = list.find {|p| p.downcase == plugin_name.downcase})
return (pl + '::Main').constantize
end
end
プラグイン・クラスの一覧を取るアルゴリズムですが,まずはClassレベルにおける定数を調べ,そこでModuleに属するものを選び,さらに,Module内の定数で「Base」を含んでいるものを選びます。この中から,Base付きのクラスの先祖のクラスを調べ,そこにFoodynPlugin::Baseがあるかどうかで判断しています。本来ここはクラスレベルで判定できるはずですが,上記の理由によって,わざわざ文字列に変換して比較しています。
美しくはありませんが,ともかくこれでスキン変数が動くようになりました。なんだかどっと疲れました。
わけが分からない
昨日の続きでまだうまくいきません。というかRuby(Rails?)の動きが理解できません。結局,プラグインの子クラスの一覧を取る機能は次のように実現しました。
def self.list
@@pluginlist ||= Array.new
if @@pluginlist.size == 0
@@pluginlist = Class.constants.select do |c|
cclass = c.constantize
if (cclass.is_a? Module) && (cclass.constants.include? 'Base')
FoodynPlugin::Base > (c+'::Base').constantize
end
end
end
@@pluginlist
end最初にpluginlistにArrayを入れてサイズを調べるといったまだるっこしいところをしているのは,またもやリロードのときにpluginlistが空になってしまうという問題に対応するためです。
中身はClassでの定数の配列を獲得し,それがModuleの名前かどうかを調べ,次にさらにそのモジュールでBaseという定数が定義されているかどうかを調べて,最後にFoodynPlugin::Baseと比較しています。
ところが,リロードすると最後の比較のときにtrueにならなきゃいけないところがなぜかnilに。理解できない。こまったなあ。
現象は分かったけど,どうしたものか
プラグインのスキン変数がうまく動いていなかった件,前進しましたが解決にいたらず。ちょっと困惑しています。プラグインのベースのクラスFoodynPlugin::Baseのクラスメソッドでは次のようなメソッドを用意しています。
def self.inherited subclass
@@pluginlist ||= Array.new
@@pluginlist.push subclass
end
def self.list
@@pluginlist ||= Array.new
end
def self.plugin_class plugin_name
if (pl = list.find {|p| p.pname.downcase == plugin_name.downcase})
return (pl.pname + '::Main').constantize
end
end
def self.pname
self.name =~ /^(.*)::/
return $1 # default name
end
ここで,inheritedというのは,そのクラスを親とする子クラスが定義されるときに自動的に呼ばれるメソッド。これを使って@@pluginlistというクラス変数にプラグインのベース・クラスの配列を入れています。スキンをパースするときに,エンジンが持っているparse_~~というルーチンとマッチしないと,FoodynPlugin::Base.plugin_classメソッドを使って,そのプラグインが存在しているかどうかを調べます(今思ったのですが,このメソッドは割と呼び出し回数が多いので,この中で名前を調べているのはだいぶ無駄な感じがします。名前自体を持たせる方がいいかも)。プラグインが存在すると,そのメイン部分のクラスを返します(ここもインスタンスを返すようにしたほうがいいかも)。そうするとパーサ側でこのプラグインのインスタンスに対してdo_skin_varメソッドを発行するわけです。
これで動くのですが,よく分からないのは,ページをリロードすると@@pluginlistがなくなってしまうこと。ですから1回目はスキン変数を表示しますが,リロードすると表示しません。クラスはロードしたままだけど,変数は消してしまうということでしょうか。そうだとするとinheritedに頼ることはできないので,このロジック自体考え直す必要がありそうです。ちょっといやらしい実装です。
プラグインの仕様/実装案メモ(4/23更新)
・Railsのプラグインとしてインストール可能にするSubversionのレポジトリ利用など,Railsの機能が利用できるから
・インストール後にFoodyn CMSの管理画面で利用のオンオフをする(デフォルトオフ)
プラグインはFoodynPlugin::Baseクラスの子クラスとして作りinit.rbでrequireしておく。このクラスはスタブであって,本体のコードは入らない。管理画面ではクラス一覧を獲得してそれを表示する。一覧の中でプラグイン・モデル(後述)に登録されていないものがあったら,自動的に登録する。
プラグインの本体はFoodynPlugin::Mainクラスの子クラスとして記述する。
管理画面で「オン」すると,インストールを実行する。具体的にはイベントの登録,スキーマの更新(プラグイン独自のテーブル,プラグイン・オプション)を行う。
「オフ」にするとイベントは削除。スキーマはそのまま。「アンインストール」はイベントおよびデータを削除。init.rbも削除(ここまでしないと一覧時にまた表示されてしまうから)。
「更新」はプラグインのコードを上書きしてから実行できる。イベントとスキーマ,プラグイン・モデル内のバージョン情報を書き換える。
・~~変数はNucleusと同様do_skin_varなどで実装する
Ruby的にはパーサを拡張するような実装も可能だがスキン・エンジン依存になるため,なるべくやらない。
・イベントはevent_~~などのメソッド実装で登録
管理画面上でプラグインをオンにするときに,そのプラグインのメソッドを舐めて調べる。
・PreItemを使うプラグイン用にPostUpdate系のイベントを用意
アイテム表示のたびにPreItemで走査するのは無駄なので決まりきった変換(例えばWiki系の入力補助など)はアイテム登録/更新時に変換する。変換前のデータを保持するためアイテムの本文用にフィールドを追加。
・プラグイン・オプション
プラグイン・オプションはブログ・オプションであればブログの拡張テーブル,アイテム・オプションであればアイテムの拡張テーブルに保存する。プラグイン本体のオプション用テーブルもある。
・独自管理画面
Admin用のクラスを別に持たせる。Nucleusのようにプラグインに直にリクエストが行くのではなく,どこかで受けてから渡すほうがいいだろう。
・アクション
これも一回システム側で受けてからプラグインを呼び出す方向。トラックバックのようなものはRESTのフォーマット追加でやりたいだろうと思うので,RESTのフォーマット追加やアクション追加ができるような仕組みを考えたい。
・スキーマの機能
プラグイン・オプションとプラグインの独自テーブルは,RailsのMigrationと同様の機能で更新する。
・必要なデータ・モデル
プラグイン管理用のモデルは,プラグイン名,バージョン,スキーマ・バージョン,オンオフ情報を登録
イベント管理用モデルはプラグインIDとイベントの名前を登録
プラグインオプションの説明モデルはプラグインID,オプション名,オプションの種類(ブログ,カテゴリー,…),データ・タイプ,デフォルト値,入力補助用データ
プラグインオプションのデータ・モデルは,元モデルごと(ブログ,カテゴリー,…)にテーブルを用意する。
プラグインはクラスで実装すべきかインスタンスにするべきか
プラグインのスキン変数の実装でちょっとバグっています。簡単に取れそうなのに,案外手間がかかってしまっています。その理由の一つはプラグインの実装をインスタンスでしているのに呼び出し側はクラスを呼んでしまっていたところにありました。
そもそもあるプラグインのインスタンスはシステムに一つしかない必要があります。そのため,いわゆるシングルトンのデザインパターンになります(RubyではSingletonモジュールをクラスに読み込むだけですが)。クラスはもともとシングルトンなので,プラグインをクラスで実装しても構わないということになります。
ところが,実際に書いてみると,クラスでプラグインを実装するとかなり分かりにくくなることが判明しました。具体的には,プラグインのベースクラス(プラグインを実行するかどうかにかかわらず,必ず読み込む部分)では,読み込まれたプラグインの一覧をクラス変数に確保するためにクラスメソッドを用意しています。それとプラグイン本体の部分とがごちゃごちゃになってしまうという分かりにくさがありました。
一方で,プラグインを呼び出すときは,プラグインのクラスに直接メソッドを送った方が話が早いので,ついそう書きたくなってしまうのです。結局プラグインの名前を抽出するメソッドをクラス・メソッドにもインスタンス・メソッドにも置くなど,なんだか汚いコードになってしまっています。少し設計を整理した方がいいのかもしれません。
デバッガーが~
ちょっとショック。RadRailsの現行バージョンでは,デバッガーが起動するときにエラーになります。1.0.2では直るようなのですが,まだ公開されていないみたいだし。早くしてくれ~。先は長い…
まずはこのブログをFoodynに切り替えることを目標にしているのですが,そのためにはMultipleCategoriesとTagEXからの移行部分やスキン変数も作らないといけないし,NP_Amazonの置き換えもしないといけません。タグやカテゴリーはNucleusと互換性がなくなる(データ移行はできるけど)ので,投稿も独自に持たないといけません。最初はタグやツリー型カテゴリー部分はRailsのプラグインを使うことを考えていましたが,タグとカテゴリーの変換機能などを考えるとやはり自前で作らないと結局いけないような気がして…メモ的にはロジックはできているのですが実装には少し時間がかかります。また,多分後回しにすると思うけどユーザー管理部分も手が付いていない。それになによりもまだ管理画面は皆無に近い状況です。管理画面はRESTfulな設計なので案外一つ動けばスムーズに行くのではないかとも期待しているのですが今の状況ではなんとも。
やらなきゃいけないことに対して,時間が足りなさすぎ… ちょっとしんどいなあ。
プラグイン用マイグレーション
新しいパソコンのCPUはAthlon 64 X Dual Core。安パソコンですが,デュアル・コアだけは譲れないと思って選びました。やっぱりこれまでとは全然違います。メモリも4Gバイト積んだので,Aptana Studioを起動する時間など,10倍近く早くなったような体感です。まだデバッグ機能は使っていませんが,これまでのようにデバッグモードに入るだけで何分も待つようなことはなくなるでしょう。やっぱり快適です。で,ようやく移行作業もほぼ落ち着き,コード書きに戻っていますが,このところ取り組んでいるのはプラグイン用のさまざまな機能。まだ完全ではないですが,マイグレーションのところは大分進みました。プラグインではそのプラグイン用のmigrationディレクトリに
class FpHelloworld::CreateOptions < FoodynPlugin::Migration
def self.up
add_string(:str, :desc=>'string to show', :default=>'Hello World')
add_string(:itemstr, :type=>'item', :desc=>'item string', :default=>'goodbye')
create_table :data do |t|
t.string :item_str
t.integer :item_id
end
end
def self.down
remove_string :str
remove_string :itemstr
drop_table :data
end
endといった形で書くだけ。これで適宜フィールドを作ったり,コラムを作ったりテーブルを作ったりします。いろいろなデータタイプに対応するなど,まだいくつか作業はありますが,基本的な仕組みは考えたとおりに動いています。
21 Apr, 2008 | Foodyn ( 実装メモ , プラグイン , 仕様 , プラグイン , 作業メモ ) | デュアル・コア / Aptana | Andy | Leave comment - 0 -
パソコン環境構築中
あまりの作業効率の低さに業を煮やして,ついにパソコン買いました。低価格パソコンですがCPUはデュアル・コアにしたので,これまでのように何かが動き出すたびに作業が止まるということはなくなるでしょう。メモリも4Gと32ビットとしては限界のところ。次に買うときはフル64ビット環境ですね。というわけで,ここ2日ほど,これまでのパソコンからデータを移したり,ソフトをインストールし直すなどの作業をしています。今週末には通常モードに戻る予定。
テスト
プラグインの動作関係をちゃんとテストを書いて検証しようと思っているのですが,これまで手動テストばかりやっていたので,テストの書き方を全く覚えていません(泣)。この機会にちゃんとしたいところ。プラグインの雛形もgeneratorで生成できるようにしたいのですが,ちょっと面倒なので,もっと仕様が落ち着いてから手を付けることになりそうです。
migrationでインデクスを生成させていたら,「名前が長すぎる」というエラーが。インデクスに使うカラム名を名前に入れていくので,カラムが複数とかになるとすぐに長くなってしまいます。とりあえずテーブル名などを短くすることで対処しましたが,名前設定てできるのかな?
RESTful Webサービス
オライリーから出ている「RESTful Webサービス」を読みました。現在のところRESTfulなアプリケーションの作り方について,まとめられているものとしては一番詳しいと思います。タイトルにあるように,主体はWebサービスの設計。FoodynのようなWebアプリケーションについての記載はあまり多くはありません。それでも,これまで考えてきたことを改めて整理したり,RESTfulな考え方をなじませるためにはかなり役に立ったような気がします。例えば,トランザクション処理は,これまでステートレスであるRESTfulにはなじまないものと思い込んでいましたが,トランザクション自体をリソースとして処理する方法が挙げられており,これには目から鱗が落ちる思いがしました。
例として挙げられているコードはほとんどがRuby on Railsなので,読者もRubyとRailsが分かることがある程度要求されそうです。Ruby知らなくても概ね雰囲気はつかめると思いますが。
RESTfulなWebサービスやアプリケーションを開発する人にとっては一度は読むべきものだと思います。
RESTful Webサービス
Leonard Richardson、Sam Ruby、山本 陽平、株式会社クイープ
オライリー・ジャパン
2007/12/21
¥ 3,990 (定価)
¥ 3,990 (Amazon価格)
39pt (Amazonポイント)
(私のおすすめ度)
★★★ (Amazonおすすめ度)
単行本
通常24時間以内に発送
(価格・在庫状況は10月11日 20:33現在)
Leonard Richardson、Sam Ruby、山本 陽平、株式会社クイープ
オライリー・ジャパン
2007/12/21
¥ 3,990 (定価)
¥ 3,990 (Amazon価格)
39pt (Amazonポイント)
(私のおすすめ度)
★★★ (Amazonおすすめ度)
単行本
通常24時間以内に発送
(価格・在庫状況は10月11日 20:33現在)
うまくいった
ライブラリを呼び出すための$LOAD_PATHに対してクラス名+migrationのディレクトリがあるかどうかを調べていくことで問題解決(多分)。コードはこんな感じ。
def schema_version
match = Regexp.new('^(\d{3,})_(.*)\.rb$')
max = 0
$LOAD_PATH.each do |load_path|
path = load_path + '/' + name.underscore + '/migration'
begin
Dir.foreach(path) do |fname|
if fname =~ match
version = $1.to_i
max = version if version > max
end
end
return max
rescue
end
end
return 0
end行き詰まり
プラグイン関係の実装を粛々と行っているのですが,困ったことが1つあって停滞中。前に書いたように,Railsのマイグレーションに相当する機能をプラグインに対して実現しようとしています。ここでマイグレーションのファイルを調べにいこうとしているのですが,ディレクトリを自動的に検知してファイルを走査するところがうまくいきません。プラグインのクラスがあるファイルに対して相対的なディレクトリを示す方法が見つかっていないからです。プラグインの親クラスで定義すると,親クラスのあるディレクトリに対して実行してしまいます。
マイグレーションのファイルを読み込ませてしまえばなんとかなるとは思うのですが,毎回読み込むのは無駄ですし。うーん,困った。
解決:プラグインからDBのテーブルを作る方法
見つけました。というか独自にテーブルが必要そうなプラグインをインストールして,どうやっているかを見てみました。とりあえず以下のコードで動くことを確認。
class TestDB
def createtable
ActiveRecord::Schema.create_table('test') do |t|
t.column :str, :string
end
end
endこれをlibディレクトリにtest_db.rbとして保存し,コンソールを開いて a = TestDB.new と a.createtable を実行したらテーブルができました。実際にはテーブルの存在チェックなどが必要ですが,ともかくこれでプラグイン内でテーブルが作れることが分かりました。
ActiveRecord::Schemaのドキュメントを見ると
ActiveRecord::Schema.define do
create_table :authors do |t|
t.string :name, :null => false
end
add_index :authors, :name, :unique
create_table :posts do |t|
t.integer :author_id, :null => false
t.string :subject
t.text :body
t.boolean :private, :default => false
end
add_index :posts, :author_id
endといった書き方ができるようです。
プラグイン・オプションも含めてプラグイン内マイグレーション機能を入れたら結構使いやすいのではないかといったことを考えています。
プラグインからDBを使うには?
プラグインのアーキテクチャを考える際に,どうしたらいいのかまだよく分かっていないところがあります。例えばプラグイン独自のテーブルを用意したい場合。Migrationを使ってテーブルを生成するのだとすると,db/migrationディレクトリにファイルを作らないといけません。つまりプラグインをインストールした後,script/generateでmigrationファイルを作るといった手順になります。act_as_authenticatedなどはこの方法を使っています。
ただ,プラグインと本体のテーブルは別々に管理したいといったことを考えるとプラグインのコード内でテーブルを作ったり削除したりする,Nucleusに近いスタイルの方が書きやすいかもしれません。このとき,プラグインのコードからcreate_tableなどを呼び出す方法がまだつかめていません。ActiveRecordの中のコードなので,呼び出し方はあるはずだと思うのですが。
検索しても,そういったイレギュラーなやり方は見つからないので,暗中模索といった感じです。
PS. やっぱり一筋縄では行きそうにありません。無謀っぽい。
