Categories
open all | close allTags
タグ | 国際化 | テスト | OpenID | 名称 | ドキュメント | モデル | rake | CSRF | デュアル・コア | RESTful | 認証 | スキンエンジン | Aptana | Migration | フォーム | パソコン | Flash | アクセス制御 | SubversionSearch
アクセス制御の枠組み
アクセス制御のためのデータ構造が大体決まりました。たぶんこれで行けるでしょう。
ちょっと分かりにくいので図にしてみました。
アクセスはロールごとに決めますが,ロールにはシステム・レベルのものとブログ・レベルのものがあります。システム・レベルのロールはmemberテーブル内に書きますが,ブログ・レベルのものはteamテーブルで指定します。本当は権限については独立したテーブルを用意し,中間テーブルを作ってマッチングさせるのが拡張性が高いのですが,実行速度を重視して,ロールのテーブル内のフィールドとして各権限を設定するようにしました。また,ブログレベルのロールとシステムレベルのロールを区別するためのissystemというフィールドを作っています。このほか,アイテム単位で制御方法を変えられるようにするためにアイテムとロールによるテーブルも作ります。
クエリーの効率が心配ですが,たぶんこれでやりたいことはできる枠組みではないかと思います。
:joinsに変えたら
SQLは正しく発行できているのを確認しました。でもオブジェクトとしてcountが取れていないみたい。どうしてかちゃんと調べようとしたところで昨晩はダウン。今週は仕事が忙しいのでさすがに疲れ気味です。
カテゴリとタグのリレーション設定
Foodynではタグとカテゴリは1テーブルで管理し,どのアイテムがどのカテゴリ,あるいはタグに属しているかを中間テーブルを使って記す方法を取っています。Ruby on Railsではこれはhabtn(has and belongs to many)あるいはhas_many throughという方法で取り扱います。ここでは中間テーブルも明示的にいじれるように後者の実装を採用します。中間テーブルのTagCategoryでは次のように取り扱います。
class TagCategory < ActiveRecord::Base
set_base_name 'tag_category'
belongs_to :categories, :class_name=>'Category', :foreign_key=>'category_id'
belongs_to :items, :class_name=>'Item', :foreign_key=>'item_id'
endItem側ではitemオブジェクトに対してitem.categoriesとすればカテゴリ一覧を,item.tagsとすればタグ一覧を表示するために条件を加えてやります。
has_many :categories, :through => :tag_category, :conditions=>"cleft is not null"
has_many :tags, :through => :tag_category, :source=>:categories, :conditions=>"caliasid is not null"
has_many :tag_category
2行目で:source=>:categoriesとしているのはTagCategoryの belongs_to:categoiesを参照しなさいという意味。
これで :conditionsのところの条件でカテゴリとタグを分けます。
マルチカテゴリのテンプレート指定
マルチカテゴリを標準機能として採用すると,Nucleusのテンプレート・モデルと合わない部分が出てきます。具体的には<%catname%>や<%catlink%>といったテンプレート変数は意味がなくなります。例えばdefault/indexのテンプレート中における<a href="<%categorylink%>" title="Category: <%category%>"><%category%></a>
という部分です。どうしても上位互換にするなら,これらのテンプレート変数が来たときは,検索してトップに来たカテゴリーを代表とするとかってことにする手もありますが,どうですかねえ(こうすればシングル・カテゴリーで使っている人の互換性は一応保たれます)。昔考えたときはitemテーブル内のicatをデフォルト・カテゴリという扱いにする,といった実装も考慮していたと思いますが,あまり前向きでないので,それは避けたいところ。
とりあえず,MultipleCategories使っている人はこの部分をそのままにしておくはずはないだろうから,MultipleCategoriesというテンプレート変数をそのまま使ってしまって,というのが当面の実装になりそうです。
で,肝心なことは,複数カテゴリが標準のときのテンプレートにおけるカテゴリー指定はどう書くのがいいか,ということ。<%categories%>とかってテンプレート変数を作って,それが呼ばれたときにテンプレート内の専用の項目を見に行くとかっていうのがNucleusの今の仕様に一番近い形ですが,セパレータの指定をどう書くかなど,ちょっと悩ましい部分があります。
あとは,これまで使用を封印してきたネスト付きパーサで,テンプレート内テンプレートをやらせてしまう,というのもあるけど,やっぱり今のモデルと極端に違うものを加えるのは良くないだろうなあ。
タグとカテゴリーのサポートについて
Foodyn CMSではツリー型のマルチカテゴリーとタグを標準でサポートします。機能的にはNucleusのMultipleCategoriesとTagEXと同等以上のものになるでしょう。詳しくは後日説明しますが,大部分は2年前にNucleusにこれらの機能を入れようとしたときのものをベースにしています(コードそのものはもう忘れたので別物ですが,考え方など)。
カテゴリー・テーブルを拡張してツリー型の管理およびタグの管理ができるようにします。ツリー管理ではNested Setと呼ばれる,SQL1回の呼び出しで子ツリーを全部取り出せる方法を取ります(説明はこちらの方が分かりやすいかも)。
ツリー構造はブログごとに持ちます。また,タグもブログごとに持ちます。これはタグからカテゴリーへの変換やその逆が楽にできるようにするためです。
タグの場合はブログ横断で検索したいケースもあると思うので,複数のタグが共通の「親タグ」(あるいは「別名タグ」)を参照するようにします。例えばブログ1に「Rails」というタグがあって,IDが10だとします。ブログ2にも「Rails」というタグがあってIDが20だとします。このときにaliasidというフィールドはどちらも10になります(別にどちらも20にしても構いません。共通であればいいだけなので)。このaliasidのフィールドを使うとブログを串刺しにしたタグ検索が可能です。
アイテムとタグ,カテゴリーを紐付けるために新しいテーブルを作ります。ここにはアイテムのIDとタグ・カテゴリーのIDがカテゴリーやタグの数だけ入ります。このテーブル上ではタグとカテゴリーの区別はなく,あくまでもカテゴリー・テーブルにおける違いだけになります。
RailsのActiveRecordにおいては,このテーブルを介してアイテムと,タグ・カテゴリーが多対多のリレーションを持つことになります。このとき「has and belongs to many(HABTM)」あるいは「has_many :through」という二つの方法があり,前者では中間テーブルはRailsが管理し,ActiveRecordで直接いじることはできません。ここでは自由度が高い後者の方法を使っています。
思ったより大変でない
前の記事で書いたTagEXデータのコンバートについてですが,どちらにしてもモデルで,そのタグがそのブログで新規かどうかのチェックなどをしないといけないので,コンバート自体についてはあまり深く考えなくてもそのメソッドを呼び出すだけでできそうです。裏方仕事は多少ありますが,それは当然のこと。
MultipleCategoriesのコンバート部分も基本的にはできたはず。両方できてからじゃないとチェックするのが面倒なのでまだ動かしていませんが。
URLによるプラグインへのアクセス
プラグインの機能として,外部から呼び出すこと(Nucleusで言えばActionのところ)が必要なわけですが,一応RESTfulを意識して実装しておきました。以前から気になっていたresource_hacksプラグインを使うことでmap.resourcesに対して名前付きのアクセスができるようにしています。将来はほかの部分もこれを使って書きなおすかもしれません。というわけでルート定義は次のような簡単なもの
map.resources :plugins, :member_path => '/plugin/:permalink'
plugins_controllerもいたってシンプル
class PluginsController < ApplicationController
before_filter :find_plugin
delegate :index, :show, :new, :edit, :create, :update, :destroy, :to=>"@plugin.main"
private
def find_plugin
if !(@plugin = Plugins.find_by_plugin_name(params[:permalink]))
return false
else
@plugin.main.params = params
end
end
enddelegateで委譲してしまっているので,ほとんどやることがありません。唯一パラメータを受け渡せるようにしておかないといけないので,FoodynPlugin::Mainクラスにparamsインスタンス変数を加えています。
attr_accessor :params
ちょっと注意しないといけないのはプラグインのメインの中ではidex, show, new, edit, create, update, destroyメソッドは外部から簡単に呼ばれてしまうことです。セキュリティホールになる恐れがあります。
プラグイン関係は,実装はまだまだやることがありますが,基本的な仕組みは全部できたので,マルチカテゴリーとタグのサポートを次に実装しようかと思います。
動かなかった理由がよく分かりませんが
今日,もう一回イベントのところを動かしてみたら何の問題もなく動いていました。この前動かなかったのはなぜ?
というわけでeventのところはNucleusよりももうちょっと簡単に書けます。プラグインではevent~~というメソッドを用意するだけ。イベント一覧を返すメソッドがなくてもメソッド名を調べて登録します。呼び出し側もイベント名でnotifyするだけ。
プラグイン関係はあとactionが実行できるようになれば,一応一通り仕組みはできたことになります。
イベントの処理に取り掛かる
プラグイン関連の処理でイベントの部分を書いています。アクティブなプラグインからイベント情報を収集してデータベースに保存するところは完成。プラグインを呼び出すところまではできているのですが,その後がなぜか動かない。デバッガが使えないと不便です。雰囲気的には変数名が違っているといった単純なものではないかと思っているのですが。美しくない解決法
いろいろ調べるうちに,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があるかどうかで判断しています。本来ここはクラスレベルで判定できるはずですが,上記の理由によって,わざわざ文字列に変換して比較しています。
美しくはありませんが,ともかくこれでスキン変数が動くようになりました。なんだかどっと疲れました。