Categories
open all | close allTags
rake | テスト | 名称 | Migration | CSRF | RESTful | タグ | JustPosted | 国際化 | モデル | フォーム | デュアル・コア | スキンエンジン | 認証 | Aptana | パソコン | ドキュメント | アクセス制御 | Flash | SubversionSearch
設計とメソッド名と内部構造
どうも頭がまだ手続き型言語から抜けていないようで,メソッド名を付けるときに,ついつい「get~~」みたいに動詞を入れてしまいがちです。ということに気付いたのは,あるブログに属するカテゴリー一覧を取得するとき。実はこれまでCategoryクラスの方でget_listというメソッドを作ってブログオブジェクトをパラメータに取得するようにしていました。こうしておくと,ブログが指定されないときの一覧やブログが複数あるときの一覧とかも同じメソッドで取れるというのがメリットなのですが,見直してみるとCategory.get_list ... と入るのはあまりきれいじゃないです。やはり blog.categories と書いた方がスマート。
これだと順序付けができないのかと思って,この記述を使っていなかったというのもあるのですが,APIを見たら has_many のリレーション指定の中で :order を使って指定できることが分かったので,今回はそちらに切り替えました。
ただ,しっくりこない面がないわけではありません。これだとBlogクラスの中でCategoryのテーブルの内部情報を使って :order のところを書くことになるため,Categoryの実装に完全に依存してしまいます。Categoryの内部構造が変わったらBlogクラスも変えなければいけないのでメンテナンス性が悪くなります。
どうするのがベストなんでしょう。
週末に
タグとカテゴリーは表示系まで終わらせるつもりだったのですが,意外とデータ移行に手間取り,表示系に行きつきませんでした。残念!タグとカテゴリーのサポートについて
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のコンバート部分も基本的にはできたはず。両方できてからじゃないとチェックするのが面倒なのでまだ動かしていませんが。
TagEXデータのコンバート
TagEXのタグってブログごとになっていないということを発見。カテゴリーとタグをひとつのテーブルで管理する場合,ブログごとに分けておかないと面倒なのでチェックしないといけないなあ。結構大変かも。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があるかどうかで判断しています。本来ここはクラスレベルで判定できるはずですが,上記の理由によって,わざわざ文字列に変換して比較しています。
美しくはありませんが,ともかくこれでスキン変数が動くようになりました。なんだかどっと疲れました。
わけが分からない
昨日の続きでまだうまくいきません。というか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に。理解できない。こまったなあ。