Categories
open all | close allTags
タグ | 国際化 | パソコン | Subversion | JustPosted | RESTful | 名称 | Migration | rake | テスト | モデル | CSRF | 認証 | アクセス制御 | フォーム | デュアル・コア | Aptana | ドキュメント | Flash | スキンエンジンSearch
アクセス制御の枠組み
アクセス制御のためのデータ構造が大体決まりました。たぶんこれで行けるでしょう。
ちょっと分かりにくいので図にしてみました。
アクセスはロールごとに決めますが,ロールにはシステム・レベルのものとブログ・レベルのものがあります。システム・レベルのロールはmemberテーブル内に書きますが,ブログ・レベルのものはteamテーブルで指定します。本当は権限については独立したテーブルを用意し,中間テーブルを作ってマッチングさせるのが拡張性が高いのですが,実行速度を重視して,ロールのテーブル内のフィールドとして各権限を設定するようにしました。また,ブログレベルのロールとシステムレベルのロールを区別するためのissystemというフィールドを作っています。このほか,アイテム単位で制御方法を変えられるようにするためにアイテムとロールによるテーブルも作ります。
クエリーの効率が心配ですが,たぶんこれでやりたいことはできる枠組みではないかと思います。
またもやわき道にそれる
管理画面のUIを考えているのですが,そのときにどうしてもアクセス制御のところを入れておかないと気持ち悪くて先に進めません。Foodyn CMSではロール・ベースのアクセス制御を採用する予定です。大体のアイディアはあるのですが,基本的な仕組みのところは作っておかないと管理画面のコーディングができません。簡単なロール・ベースのものなら,世の中にいくらでもサンプルがあると思います。それを使うのも一つの手ですが,今考えているのはブログのチームの概念と組み合わせた制御ができないかどうか。
例えばAというブログがあって,a,b,cの3人がメンバーだとします。またBというブログがあってc,d,eの3人がメンバーだとします。このときにcはAではadminだけどBでは閲覧しかできない,といったアクセス制御をどうやって実現すればいいのか。また,aはAでアイテムが追加できるユーザーで,dはBでアイテムが追加できるユーザーだったとき,この二人を同じロールにできるのか。結構めんどくさそうです。
プラグインの仕様/実装案メモ(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,オプション名,オプションの種類(ブログ,カテゴリー,…),データ・タイプ,デフォルト値,入力補助用データ
プラグインオプションのデータ・モデルは,元モデルごと(ブログ,カテゴリー,…)にテーブルを用意する。
プラグイン用マイグレーション
新しいパソコンの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 -
プラグインからDBを使うには?
プラグインのアーキテクチャを考える際に,どうしたらいいのかまだよく分かっていないところがあります。例えばプラグイン独自のテーブルを用意したい場合。Migrationを使ってテーブルを生成するのだとすると,db/migrationディレクトリにファイルを作らないといけません。つまりプラグインをインストールした後,script/generateでmigrationファイルを作るといった手順になります。act_as_authenticatedなどはこの方法を使っています。
ただ,プラグインと本体のテーブルは別々に管理したいといったことを考えるとプラグインのコード内でテーブルを作ったり削除したりする,Nucleusに近いスタイルの方が書きやすいかもしれません。このとき,プラグインのコードからcreate_tableなどを呼び出す方法がまだつかめていません。ActiveRecordの中のコードなので,呼び出し方はあるはずだと思うのですが。
検索しても,そういったイレギュラーなやり方は見つからないので,暗中模索といった感じです。
PS. やっぱり一筋縄では行きそうにありません。無謀っぽい。
Foodyn CMSとは
名称決定(ほんとかな?)を受けて,改めて(内輪向けでない)紹介を書いておきます。
Foodyn CMSとは
●基本仕様
・Ruby on Railsをプラットフォームとするブログ・ベースのCMSです。オープンソースで公開します(ライセンス未定)。
・PHPで実績があるNucleus CMSのデータとほぼ互換性を持ちます。
・複数言語をサポートします。
・Nucleusと同様,複数ユーザー,複数ブログの運用が可能です。
・画面表示をつかさどる「スキン」のシステムもNucleus CMSとほぼ互換性があります。
・Nucleusの特徴の一つである,豊富なプラグイン・サポート機能を用意します。
●アドバンス仕様
・Nucleusではプラグインで実現している,複数ブログをまとめた表示機能,ページスイッチ,ツリー型カテゴリー,タグ,自由な名前を使ったURLといった機能を標準で用意します。
・Nucleusよりもきめ細かな,ユーザー管理機能を持ちます。例えば記事を書く人とチェックをする人が別,といった運用ができます。
・「スキン」のエンジンは複数利用できます。また,ある「スキン」から別のスキン・エンジン用のスキンを生成して実行することもできます。スキンのアクセス権はエンジンごとに設定できるので,例えば「信頼できないユーザーにはスキンの選択と色の調整しかさせない」といったことが可能になります。
・入力画面ではWYSIWYGエディタを利用できます。
作者「Andy」について
・Ruby on Railsでの開発は初めて。Rubyもほぼ初めてに近いです。
・PHPではNucleus CMSのコア開発メンバーに入っているほか,mbstringエミュレータというPHPの漢字処理機能のエミュレータを開発。Nucleus日本語版など日本語の処理機能を持つプログラムがmbstringが入っていないサーバーでもほとんど無変更で動くようにした実績があります。これはPHPで書いた最初のプログラムでした。
・Foodyn CMSの開発を始めたのは,自分で仕様・実装を決めて作ってみたくなったこと,Railsが話題になっており,使ってみたかったこと,などが動機です。Nucleusが嫌になったわけではありません。
・普通のサラリーマンであり,エンジニアでもありません。開発は夜中にこつこつやっています。
というわけで開発協力者募集中です。連絡は webmaster アット matsubarafamily.com までお願いします。
スキン・エンジンの仕様・実装メモ
現在のスキン・モデルはNucleusのDBベースのものしか取り扱えない。これをファイルベースのスキンなどにも利用できるようにする必要がある。ユーザーからするとスキンは名前で指定するだけだから,今のスキン・クラスとは別に統合的にスキンを管理するためのモデル・クラスが必要になる。そのクラスではスキン名とエンジン名のマッチングだけを行う。実際にスキンを読み込むところなどはスキン・エンジンの責任になる。今の実装ではスキン・データを読み込むところがコア側にあるので,責任範囲を変えないといけない。つまり,スキン・エンジンはスキン名を与えられたら,描画内容を返すこと,である。またスキン・エンジンは実際の描画を他のエンジンに任せることができる。すなわち,高級言語からアセンブラに変換するようなもので,結果を他のエンジンに引き渡して描画できる。
このようにするには,スキン・エンジンをモデルとして登録する際に,下位のエンジンが何かというフィールドを用意しておけばよい。
スキン・エンジン自体もRailsのプラグインとして登録可能であり,プラグインと同様にスキン・エンジン用のスタブ・クラスを用意する。後の実装はほとんど自由にできるはずだ。
REST化についての考察(まとめ)
ここではRucleus(仮称)でなぜRESTの設計を取り入れることにしたのか,どういう問題があるのかをまとめておきます。RESTの意味とそのメリット
Rails 2.0の目玉の一つがRESTfulな設計が簡単になったことですが,RESTの機能自体はRails 1.2で導入されています。僕自身は「RESTってWebサービスのインタフェースの1つでSOAPほど面倒くさくないやつでしょ」ってくらいの認識しかなかったのですが,RESTについて調べると,そういうものではなくてWebアプリケーションの設計思想に近いものだということが分かってきました。
RESTfulなアプリケーションやサービスではURLはリソースを示します。例えばブログやアイテムがリソースです。Railsではモデルすなわちデータベースのテーブルがリソースになります。これに対して「何をするか」という「アクション」はHTTPのverb,すなわち「GET」や「POST」などで表現します。これをリソースのコントローラに対して適用します。つまり,モデルのクラス1つに対してコントローラのクラス1つが対応するような形になります。なおverbとしてHTTPの仕様では「DELETE」,「PUT」(データの更新)が定義されていますが,現在使われているブラウザが送信するのはGETとPOSTしかないため,Railsではフォームに特別なフィールドを作ってDELETE,PUTを実現します。
RESTのもう1つの特徴に内部状態に依存しないということがあります。クッキーやセッション・データによって表示内容を変えることがなく,URL(正確に言えば,HTTPヘッダに含まれる情報)で結果が決まります。これによって,ページ・キャッシュが実現しやすいというメリットがあります。サーバーに内部情報を保存する必要がないため分散処理も容易になります。
また,「respond to」による各種フォーマットのサポートという特徴もあります。例えばRailsのRESTサポートではXMLによる出力を標準的に利用できるようになっています。
これらRESTの特徴はNucleus(Rucleus)となじみがいいものです。例えば,初期の設定ではURLの解釈においてアプリケーションというコントローラにブログやカテゴリーといったリソースの情報を渡して「item」をアクションとしてIDを付けてアクセスするといった形にしていました。それよりはブログやアイテムをリソースとしてアクセスすると考えた方がすっきりします。また2番目の特徴は性能を考えたときにクリティカルになってきます。最後の各種フォーマットのサポートも,スキンを切り替えることでさまざまな出力を行うNucleusのスタイルと相性がいいです。
認証の問題
では,何が問題になって悩んでいたかというと,認証関係とURL周りです。まず,認証ですが,RESTではNucleusが従来やっているようなクッキーを使った認証というのは問題になります。BASIC認証やダイジェスト認証,WSSE認証といったHTTPで定められた認証が必要です。この中でWSSE認証はブラウザがサポートしていないため,JavaScriptでなにやらやる必要があるなど,採用には問題点があります。次にBASIC認証やダイジェスト認証を使ったときですが,いくつかの課題が生じます。①1つのURLでログイン・ユーザーとゲストで違ったページを表示するのが難しい,②ログイン時にダイアログ・ボックスが出るのが格好悪い,独自のページでログインさせたい,③ログアウトが困難,といったものです。
こちらのページにはこれらの条件を満足させてダイジェスト認証を使う方法が記載されていますが,詳しく読んでみると,ブラウザによってやり方を変えなければいけないなど,現実に採用するには難しい部分がかなりありそうです。上の問題で①が機能的に一番ほしいところですが,逆に言えばここだけ我慢すればいいという見方もできます。
URLの問題とその解決法
最後の問題となったのがURL周りです。冒頭に書いたようにRails 2.0ではREST対応のアプリケーションを簡単に作れるようになっていますが,URLに大きな制限を受けてしまいます。例えば,blogidが1のitemidが10というアイテムにアクセスする場合,URLは「blog/1/item/10」といった形式に固定化されてしまいます。ここを独自のURL設計にする方法が分からずに悩んでいたわけです。
現在のURL周りの処理はこんな感じです。例えば /blogname でブログにアクセスする場合
map.blog ':blogname', :controller => "blogs", :action => 'show'といった形で「blogs」をコントローラにしてshowというアクションを実行します。blognameからブログを見つける処理はshowの中に記述します。これに各種フォーマット対応をするために次のように追加します。
map.blog ':blogname.:format', :controller => "blogs", :action => 'show'アプリケーションのコントローラには
private
def setblog
if !params['blogname']
$NCGlobals['blog'] = Blog.find(CONF['DefaultBlog'])
else
$NCGlobals['blog'] = Blog.find_by_bname(params['blogname']) || Blog.find(CONF['DefaultBlog'])
end
end
def setcatid
if params['catid']
$NCGlobals['catid'] = params['catid'].to_i
end
end
def setskin(skinname)
if (skinname=='')
$NCGlobals['skin'] = $NCGlobals['blog'].skin
else
skin = Skin.find_by_sdname(skinname)
$NCGlobals['skin'] = (skin) ? skin :$NCGlobals['blog'].skin
end
end
public
def blog(skinname='')
setblog
setcatid
setskin(skinname)
$NCGlobals['skintype'] = 'index'
$NCGlobals['item'] = Array.new
$NCGlobals['controller'] = self
@contents = $NCGlobals['skin'].part($NCGlobals['skintype'])
@controller = NCParser::DefaultSkinIndex.new $NCGlobals['controller']
render :action=>'index.html.erb'
end
といった形でblognameから実際のブログオブジェクトを見つけることなどが記述されます。また,blogs_controllerのshowメソッドは
def show
respond_to do |format|
format.html { blog }
format.xml { blog('feeds/rss20') }
end
endといった形になります。このようにしておくだけで /lab.xml というURLでアクセスするとRSS2.0のフィードを生成してくれるようになります。