Categories
open all | close allTags
認証 | RESTful | JustPosted | Migration | CSRF | ドキュメント | モデル | 名称 | Flash | タグ | rake | スキンエンジン | テスト | Subversion | Aptana | デュアル・コア | 国際化 | フォーム | パソコン | アクセス制御Search
ダック・タイピング
アイテムでもブログでも区別しないで権限を調べられるように,両者に同じメソッドを作ってアクセスするようにしています。これもダック・タイピングと呼んでいいのかな?先日デザインパターンの本を読んだのですが,なかなかおもしろかった半面,記述がJavaだったため,Rubyだとどうなるのだろうと気になるところもありました。Javaではインタフェースを中心にデザインパターンを構築することが多いようですが,Rubyだとインタフェースという機能はありません。その代わりにmoduleを使ってミックスインしたり,今回のように特に宣言なく同じAPIを用意して呼び出すといったことができます。こういった特質により,デザインパターンの実装も変わってくると思います。
Web上にはまつもとさんが書かれたものも含め,いくつかRubyによるデザインパターンの実装がありますが,できたら体系だって本として読みたいところ。
ブログRole追加だけで終わってしまった
システム系はどういうRightsが必要か考えるのが結構大変だ。でもやっておかないとね,後から拡張するんでもいいけど。
アクセス制御の枠組み
アクセス制御のためのデータ構造が大体決まりました。たぶんこれで行けるでしょう。
ちょっと分かりにくいので図にしてみました。
アクセスはロールごとに決めますが,ロールにはシステム・レベルのものとブログ・レベルのものがあります。システム・レベルのロールはmemberテーブル内に書きますが,ブログ・レベルのものはteamテーブルで指定します。本当は権限については独立したテーブルを用意し,中間テーブルを作ってマッチングさせるのが拡張性が高いのですが,実行速度を重視して,ロールのテーブル内のフィールドとして各権限を設定するようにしました。また,ブログレベルのロールとシステムレベルのロールを区別するためのissystemというフィールドを作っています。このほか,アイテム単位で制御方法を変えられるようにするためにアイテムとロールによるテーブルも作ります。
クエリーの効率が心配ですが,たぶんこれでやりたいことはできる枠組みではないかと思います。
タグとカテゴリが曲がりなりにも動いたので
そろそろ次のフェーズ(管理画面)に入ろうかと。あー腰が重い。
まずは投稿画面からかな。
設計とメソッド名と内部構造
どうも頭がまだ手続き型言語から抜けていないようで,メソッド名を付けるときに,ついつい「get~~」みたいに動詞を入れてしまいがちです。ということに気付いたのは,あるブログに属するカテゴリー一覧を取得するとき。実はこれまでCategoryクラスの方でget_listというメソッドを作ってブログオブジェクトをパラメータに取得するようにしていました。こうしておくと,ブログが指定されないときの一覧やブログが複数あるときの一覧とかも同じメソッドで取れるというのがメリットなのですが,見直してみるとCategory.get_list ... と入るのはあまりきれいじゃないです。やはり blog.categories と書いた方がスマート。
これだと順序付けができないのかと思って,この記述を使っていなかったというのもあるのですが,APIを見たら has_many のリレーション指定の中で :order を使って指定できることが分かったので,今回はそちらに切り替えました。
ただ,しっくりこない面がないわけではありません。これだとBlogクラスの中でCategoryのテーブルの内部情報を使って :order のところを書くことになるため,Categoryの実装に完全に依存してしまいます。Categoryの内部構造が変わったらBlogクラスも変えなければいけないのでメンテナンス性が悪くなります。
どうするのがベストなんでしょう。
美しくない解決法
いろいろ調べるうちに,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に頼ることはできないので,このロジック自体考え直す必要がありそうです。ちょっといやらしい実装です。
プラグインはクラスで実装すべきかインスタンスにするべきか
プラグインのスキン変数の実装でちょっとバグっています。簡単に取れそうなのに,案外手間がかかってしまっています。その理由の一つはプラグインの実装をインスタンスでしているのに呼び出し側はクラスを呼んでしまっていたところにありました。
そもそもあるプラグインのインスタンスはシステムに一つしかない必要があります。そのため,いわゆるシングルトンのデザインパターンになります(RubyではSingletonモジュールをクラスに読み込むだけですが)。クラスはもともとシングルトンなので,プラグインをクラスで実装しても構わないということになります。
ところが,実際に書いてみると,クラスでプラグインを実装するとかなり分かりにくくなることが判明しました。具体的には,プラグインのベースクラス(プラグインを実行するかどうかにかかわらず,必ず読み込む部分)では,読み込まれたプラグインの一覧をクラス変数に確保するためにクラスメソッドを用意しています。それとプラグイン本体の部分とがごちゃごちゃになってしまうという分かりにくさがありました。
一方で,プラグインを呼び出すときは,プラグインのクラスに直接メソッドを送った方が話が早いので,ついそう書きたくなってしまうのです。結局プラグインの名前を抽出するメソッドをクラス・メソッドにもインスタンス・メソッドにも置くなど,なんだか汚いコードになってしまっています。少し設計を整理した方がいいのかもしれません。