«Prev || 1 | 2 | 3 | 4 || Next»

力づくで解決

あまり,ここでばかり時間を費やしたくないので,コメントフォームを作ったときと同様,CSRF対策用のタグを手動で埋め込むことでとりあえず先に進むことにしました。

ただ,さすがにフォームを作ったり,保存したりといったところは楽です。Railsのリソース・ルーティングにしたがってやっておくだけでほとんどはRailsが面倒見てくれる感じ。慣れたら,管理画面作るのはすいすいできそうです。というわけで,すごくいい加減(例えばデフォルトカテゴリはカテゴリIDを直接入れないといけない)ですが,ブログ設定をFoodynの管理画面で編集できるようになりました。

余談ですが「企業で使えるオープンソースCMS一挙12種類解説」という記事,なかなか参考になります。ステージ管理とか時限発行とか,考えていなかったなあと。時限発行は未来の日付で投稿された記事に対してpingなどを送る機能と同じような形で実現できそうな気がします。有効期限が切れた記事をドラフト扱いにするのか,遠い未来の投稿扱いにするのか,別のフラグを立てるのかといったところがちょっと悩ましいですが。

ステージ管理はどうやったらできるだろう。

05 Jul, 2008 | Foodyn ( 管理画面 , つぶやき ) | | Andy | Leave comment - 0 -

初めての管理画面(笑)

とりあえず前言撤回で,RESTfulで用意されているもので大体なんとかできそうな感じがしてきました。deleteのユーザー・インタフェースなど,気になるところもありますが,まずは標準機能で実装していきたいと思います。

で,一応今の管理画面。(管理画面

URLは全部ではないですが,ちゃんとしたものを生成しています。

ここではメモとして,map.namespace付きのprefix付き,ネスティッド・リソースという“テンコ盛り”状態でのルーティングについて書いておきます。

map.namespace(:admin) do |admin|
  admin.root :controller =>'admin'
  admin.connect ':memberid', :controller =>'Admin'
  admin.resources :blogs, :path_prefix=>'admin/:memberid' do |blog|
    blog.resources :items
    blog.resources :comments
    blog.resources :categories
  end
  admin.resources :plugins, :path_prefix=>'memberid'
end

というのがルーティング部分(まだ完全ではありません。必要なとこしか作ってないので)。ここでルートのコントローラを指定することで,アドミン用のコントローラはadminディレクトリの下から選ばれるようになります。アドミンへのアクセスは全部認証付きであり,認証されたユーザー名がmemberidになります。したがって'admin/:memberid'が管理画面のルートです。

その下のresourcesの部分が今日の骨子。ブログ周りのリソース定義をしています。RESTfulなルーティングを使うためにはresourcesが必要です。ここでちょっと注意が要るのがpath_prefixのところ,ここは本来':memberid'だけでいいはずなのですが,それだとURLを生成したときにadminが抜けてしまうバグ?があります。そこでわざわざこれを入れています。

以下でネストされたリソースを加えています。

URLを生成するときはリソースを指定する必要があるのですが,この場合,例えばアイテムの一覧を表示したかったら「admin_blog_items_url(@memberid, blog)」といったリソースをurl_forやlink_toなどのメソッドに渡します。ここで@memberidがユーザー名ですが,これをパラメータに入れるのも一つの肝。プレフィクスを指定しているからなのでしょうけれども,そのあたりの説明はドキュメントになく,試行錯誤で見つけました。なお,admin_blog_items_urlというメソッド名は「rake routes」でルート定義の一覧を出すと分かります。例えば今の部分は
admin_blog_items GET /admin/:memberid/blogs/:blog_id/items {:controller=>"admin/items", :action=>"index"}
といった形で記述されています。これで,今度はAdmin::ItemsControllerのindexメソッドを用意すればいいことが分かります。

これで後はスピードアップするかな?

23 Jun, 2008 | Foodyn ( 管理画面 ) , Rails | | Andy | Leave comment - 0 -

権限を調べる部分の実装

まず,Memberのオブジェクトからはhas_rightというメソッドで権限を持っているかどうかを調べます。
  # context is either a Blog, or an Item
  def has_right rightname, context
    #search role first.
    context.has_right rightname, self
  end

rightnameが権限の名前,例えば"browse"などになります。contextはコメントにもあるように,どのコンテキストで権限を判断するかと言うこと。ブログあるいはアイテムがcontextです。で,これは実際にはコンテキストに問い合わせて調べます。つまり,BlogクラスとItemクラスにはどちらもhas_rightというメソッドがあってそれが呼び出されます。これが昨日書いたダック・タイピング,すなわち同じメソッドを持っていたら同じものとして使える,ということになります。

Blogクラスのこの部分の実装はこう。
  def role_of member
    if !member
      return self.guest_role
    else
      t = Team.find(:first, :conditions=>["tblog=? and tmember=?", self.id, member.id])
      if t then
        return t.role
      else
        return self.user_role
      end
    end
  end
  
  def has_right rightname, member
    role = self.role_of member
    return role.attributes[rightname]
  end

下のhas_rightがメインで,ロールに対してフィールド名で取得するようにしています。実はこれだと関係ないフィールドまで取れてしまいますが,そこはガチガチにしなくてもいいだろうと判断しています。上のrole_ofでは,ログインしていない場合(memberがnil),ログインしているけどチームに入っていない場合,チームに入っている場合に分けてRoleを返します。前二つの場合はBlogのテーブル内のフィールド情報から取り出します。guest_roleやuser_roleはクラスの上の方でbelongs_toとして定義しています。

一方Itemクラスは
 def role_of member
   role = self.blog.role_of member
   itemrole = ItemRole.find(:first, :conditions=>["item_id=? and role_id=?",self.id, role.id])
   if itemrole then
     return itemrole
   else
     return role
   end
 end
 
  def has_right rightname, member
    role = self.role_of member
    return role.attributes[rightname]
  end 

has_rightの部分は実は同じですが,ここでもダック・タイピングもどきのことをしています。アイテムで独自の権限を決める場合はその情報はItemRoleというクラスに入っていますが,その場合もRoleクラスと同じようにattributes[rightname]で結果を取り出しています。role_ofの方は先ほどのBlogのrole_ofでRoleを取り出し,ItemRoleが存在すればそっち,しなければ取ってきたRoleを返します。

21 Jun, 2008 | Foodyn ( 実装メモ , 管理画面 ) | | Andy | Leave comment - 0 -

アクセス制御の枠組み

アクセス制御のためのデータ構造が大体決まりました。たぶんこれで行けるでしょう。
アクセス管理
ちょっと分かりにくいので図にしてみました。
アクセスはロールごとに決めますが,ロールにはシステム・レベルのものとブログ・レベルのものがあります。システム・レベルのロールはmemberテーブル内に書きますが,ブログ・レベルのものはteamテーブルで指定します。本当は権限については独立したテーブルを用意し,中間テーブルを作ってマッチングさせるのが拡張性が高いのですが,実行速度を重視して,ロールのテーブル内のフィールドとして各権限を設定するようにしました。また,ブログレベルのロールとシステムレベルのロールを区別するためのissystemというフィールドを作っています。このほか,アイテム単位で制御方法を変えられるようにするためにアイテムとロールによるテーブルも作ります。
クエリーの効率が心配ですが,たぶんこれでやりたいことはできる枠組みではないかと思います。

05 Jun, 2008 | Foodyn ( 実装メモ , 仕様 , つぶやき ) | | Andy | Leave comment - 0 -

:joinsに変えたら

SQLは正しく発行できているのを確認しました。でもオブジェクトとしてcountが取れていないみたい。
どうしてかちゃんと調べようとしたところで昨晩はダウン。今週は仕事が忙しいのでさすがに疲れ気味です。

29 May, 2008 | Foodyn ( 実装メモ ) | | Andy | Leave comment - 0 -

カテゴリとタグのリレーション設定

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'
end


Item側では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のところの条件でカテゴリとタグを分けます。

20 May, 2008 | Foodyn ( 実装メモ ) | | Andy | Leave comment - 0 -

マルチカテゴリのテンプレート指定

マルチカテゴリを標準機能として採用すると,Nucleusのテンプレート・モデルと合わない部分が出てきます。具体的には<%catname%>や<%catlink%>といったテンプレート変数は意味がなくなります。例えばdefault/indexのテンプレート中における
<a href="<%categorylink%>" title="Category: <%category%>"><%category%></a>

という部分です。どうしても上位互換にするなら,これらのテンプレート変数が来たときは,検索してトップに来たカテゴリーを代表とするとかってことにする手もありますが,どうですかねえ(こうすればシングル・カテゴリーで使っている人の互換性は一応保たれます)。昔考えたときはitemテーブル内のicatをデフォルト・カテゴリという扱いにする,といった実装も考慮していたと思いますが,あまり前向きでないので,それは避けたいところ。

とりあえず,MultipleCategories使っている人はこの部分をそのままにしておくはずはないだろうから,MultipleCategoriesというテンプレート変数をそのまま使ってしまって,というのが当面の実装になりそうです。

で,肝心なことは,複数カテゴリが標準のときのテンプレートにおけるカテゴリー指定はどう書くのがいいか,ということ。<%categories%>とかってテンプレート変数を作って,それが呼ばれたときにテンプレート内の専用の項目を見に行くとかっていうのがNucleusの今の仕様に一番近い形ですが,セパレータの指定をどう書くかなど,ちょっと悩ましい部分があります。

あとは,これまで使用を封印してきたネスト付きパーサで,テンプレート内テンプレートをやらせてしまう,というのもあるけど,やっぱり今のモデルと極端に違うものを加えるのは良くないだろうなあ。

19 May, 2008 | Foodyn ( 実装メモ ) | | Andy | Leave comment - 0 -

タグとカテゴリーのサポートについて

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で直接いじることはできません。ここでは自由度が高い後者の方法を使っています。

07 May, 2008 | Foodyn ( 実装メモ ) | | Andy | Leave comment - 0 -

思ったより大変でない

前の記事で書いたTagEXデータのコンバートについてですが,どちらにしてもモデルで,そのタグがそのブログで新規かどうかのチェックなどをしないといけないので,コンバート自体についてはあまり深く考えなくてもそのメソッドを呼び出すだけでできそうです。

裏方仕事は多少ありますが,それは当然のこと。

MultipleCategoriesのコンバート部分も基本的にはできたはず。両方できてからじゃないとチェックするのが面倒なのでまだ動かしていませんが。

06 May, 2008 | Foodyn ( 実装メモ ) | / | Andy | Leave comment - 0 -

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
end

delegateで委譲してしまっているので,ほとんどやることがありません。唯一パラメータを受け渡せるようにしておかないといけないので,FoodynPlugin::Mainクラスにparamsインスタンス変数を加えています。
attr_accessor :params

ちょっと注意しないといけないのはプラグインのメインの中ではidex, show, new, edit, create, update, destroyメソッドは外部から簡単に呼ばれてしまうことです。セキュリティホールになる恐れがあります。

プラグイン関係は,実装はまだまだやることがありますが,基本的な仕組みは全部できたので,マルチカテゴリーとタグのサポートを次に実装しようかと思います。

03 May, 2008 | Foodyn ( プラグイン ) | | Andy | Leave comment - 0 -
«Prev || 1 | 2 | 3 | 4 || Next»

© 2007 yoursite.com | Designed by DesignsByDarren
Ported to Nucleus CMS: Suvoroff