Jewel-mmo開発日記

RubyでMMORPGを作る過程を記録する日記。 Yokohama.rb をよろしくお願いします。

BilboのMVC

http://mt.endeworks.jp/d-6/2008/03/db-1.html

BilboはDBを使わないけどMVCで実装している。

良い機会なのでBilboの超シンプルなMVCの実装を紹介。 このMVCを実現してるコア(bilbo.rb)は81行。

モデル

日記の記事はモデルとして管理される。具体的にはEntryクラスが記事を扱うモデルである。

entries = Entry.find('200803', :limit => 5)

こんな風にすると2008年3月の記事オブジェクトを5個取り出すことができる。

このEntryクラスの実装は30行。 この30行には、記事をHTMLに変換するコンパイラの登録と変換処理も含まれている。 このEntryクラスはBilboにとって最大の機構。30行なんて長すぎるのでコードは省略。

記事オブジェクトは次のようにしてHTML化できる。

entries.first.to_html

原稿のコンパイラは設定ファイル(bilborc)で指定する。 次のコードはHikiDocを登録する例。

require 'misc/hikidoc.rb'
Entry.add_compiler('.txt') {|entry|
  HikiDoc.to_html(entry.body, :level => 2)
}

拡張子「.txt」の原稿をHikiDocでHTML化する。

ビュー

ビューはいたってシンプル。4行で実装されている。

def render_view(name, b = binding)
  erb = Plugin.views[name] || (chdir(:views) { File.read("#{name}.html.erb") })
  ERB.new(erb).result(b) rescue "<h3>ViewError in #{h name}</h3>#{h $!}<pre>#{$@.join("\n")}</pre>"
end

HTMLテンプレートをERBにてレンダリング。

コントローラ

これも短いので、コードを全行貼ってしまったほうが話が早い。

class Controller
  def list
    @entries = Entry.find(params[:date] || '20',
                          :limit => (params[:limit] || config[:limit] || 5).to_i,
                          :page => (params[:page] || 0).to_i)
    render(:list)
  end

  def render(action)
    render_view(:layout) { render_view(action) }
  end
end

デフォルトのアクションとしてlistを定義してあって、あと基本描画にレイアウトファイル(layout.html.erb)にアクション用のテンプレートを埋め込む。

そしてこのコントローラを呼び出すのが以下の部分。 CGIから渡されたactionパラメータをもとにControllerクラスに定義されているメソッドを呼び出す。

def default_action
  :list
end

def action_name # support: ruby 1.8 and 1.9
  action = (params[:action] || params.index(nil) || 'list').to_sym
  Controller.instance_methods(false).map(&:to_sym).include?(action) ? action : default_action
end

def bilbo_context
  Plugin.load
  Controller.new.__send__(action_name)
end

プラグインの中でControllerクラスのインスタンスメソッドを定義してやれば、 それがアクションとしてすぐに呼び出せてしまう。 よってプラグインによる機能追加かがとっても容易。

permalink
category: Bilbo(15)
no tweet

コメントオン

Bilboでテスト運用中の本ブログにコメントプラグインを投入。 まだ実験段階なので、不都合があると消すことがあるかも。 スパムに対しても何の対策もしてないので、スパムが大量に来た場合も一括削除しちゃうかも。

スパム対策はスパムが来てから考える。

あと、RSSでコメントを配信するようにしてる。本文とまぜちゃうとスパムが来たときに問題がありそうなので、コメント用に専用のRSSを用意してみた。

http://dgames.jp/dan/?rss_comments

必要な機能をさくっとプラグインで簡単に追加できるというのが夢だったのだけど、いまのところうまくいっている。 今回コメントのRSS配信用に追加したコードは以下。

class Controller
  def rss_comments
    generate_rss(Comment.find('20*'), :title => "Comments - #{config[:title]}")
  end
end

class Comment
  def rss_description(n = 128)
    body[0, n]
  end

  def rss_tile
    name
  end

  def date
    Date.parse(@filename.to_s[/\A\d{8}/])
  end
end

Plugin.add_hook(:header) {
  %Q!<link rel="alternate" type="application/rss+xml" title="RSS" href="?rss_comments">!
}
permalink
category: Bilbo(15)
no tweet

記事タイトルをページタイトルに付加する

permalinkアクションで記事タイトルをグローバル変数に保存し、フック処理から呼び出す。

$permalink_title = nil
class Controller
  def permalink
    @entry = Entry.find(params[:date], :limit => 1, :complete_label => true).first
    $permalink_title = @entry.title
    render(:entry)
  end
end

Plugin.add_hook(:before_header) {
  if $permalink_title
    "<head><title>#{$permalink_title} - #{config[:title]}</title></head>"
  end
}

うーん、グローバル変数を使わずに、アクションメソッド内の変数をフック処理に渡せないものか……。

あ、これでいいのか。

class Controller
  def permalink
    @entry = Entry.find(params[:date], :limit => 1, :complete_label => true).first
    Plugin.add_hook(:before_header) {
      "<head><title>#{@entry.title} - #{config[:title]}</title></head>"
    }
    render(:entry)
  end
end
permalink
category: Bilbo(15)
no tweet

後方互換と前方互換

BilboはtDiaryスタイルのURLに対する後方互換性をサポートする。

2008年1月23日の1つ目のエントリのURLは上のようになる。 BilboはtDiaryスタイルのURLも受け付けるけど、正式なパーマリンクはtDiaryとは異なる。 Bilboは同日に複数のエントリが合った場合、それぞれのエントリを個別に管理する。 だからコメントも日付単位じゃなくて個別のエントリに対して付けることができる。

で、当然なんだけどtDiaryはBilboに対する前方互換性がない。 本サイトでBilboのテストをしているわだが、すでにBilboスタイルのパーマリンクに対して外のサイトがリンクしていて、 tDiaryに戻すと問題が発生することに気がついた。 まあたぶん戻さずに済みそうだけど。

permalink
category: Bilbo(15)
no tweet

Bilbo試験中

試験的に本日記をBilboで運用してます。

コメント機能はアツイので切ってあります。少ししたら実験的にコメント機能を有効にします。

tDiaryの記事を全部インポートしたときの動作速度が心配だった(最適化はやってない)のだけど、特に問題なさそうだなあ。

permalink
category: Bilbo(15)
no tweet