« RailsでWikiクローンを作る | メイン | Wiki新設 »

2005年08月31日

RailsでWikiクローンを作る 6: ページ表示

LoadingModule
後回しになっていた show アクションを実装します。 show は、Wiki のページを表示するアクションです。 つまり wiki ソースをパースして、HTML に変換することになります。
この部分は Hiki のソースを利用させてもらい、以下の二つのファイルを作りました (ソースは後で公開します)。
parser_default.rb
Hikiの style/default/parser.rbをほぼそのまま流用。 Hiki::Parser_default というクラスを定義している。 HikiのWiki文法で書かれた文字列を構文解析してトークン列に変換する。
formatter_default.rb
Hikiの style/default/html_formatter.rbを元に、 minki 用に InterWikiName やプラグインなどの機能削って手直ししたもの。 Hiki::Formatter_default というクラスを定義している。 トークン列を HTML に変換する。
これらは Rails アプリの勉強という面では、あまり本質とは関係ないため、ここでは詳しい説明は省きます。 要は、Hikiのparserとhtml_formatterを流用して、Wiki文法(Hiki文法)のソースを HTML に変換する、ということだけ押さえておいてください。
問題は、これらのファイルをどこに配置すれば良いか、です。 lib あるいは vendor というディレクトリに置いておいて、どこかで require するという手もありますが、ここでは Rails の LoadingModule という機能を使います (LoadingModule について、詳しくは http://wota.jp/ac/?date=20050810#p01 等を参照してください)。
app/controllers/hiki というディレクトリを作って、そこに parser_default.rb と formatter_default.rb を入れておくと、Rails アプリ中で Hiki::Parser_default や Hiki::Formatter_default というクラスを使おうとすると、自動的にそれらのファイルを load してくれます。

そして、app/controllers/wiki_controller.rb の show メソッドを以下のように書き換えます。
  def show
    @page = Page.find_by_name params[:id]
    @title = (@page.title and !@page.title.empty?) ? @page.title : @page.name
    conf = Struct.new(:use_plugin, :use_wikiname).new(false, false)
    parser = Hiki::Parser_default.new conf
    tokens = parser.parse @page.content
    formatter = Hiki::Formatter_default.new tokens, self, conf
    @body = Hiki::Formatter_default.apply_tdiary_theme(formatter.to_s)
    render :text => @body, :layout => true
  end
@page = Page.find_by_name params[:id] で、params[:id]で示されるページ名を検索して、該当する行を @page に代入します (該当する行がなかった場合の処理は後ほど追加します)。 そして、@page.title に何か入っていればそれを、なければ @page.name を @title に代入します。
次の conf = ... の部分は、まだ管理画面を作っていないので Struct でダミーの conf オブジェクトを生成しています。 後程、管理画面を作って、conf には設定情報オブジェクトが入るようになる予定です。

parser = Hiki::Parser_default.new conf でHikiパーサオブジェクトを作り、tokens = parser.parse @page.contentで、@page.content に入っている Wiki ソースをトークン列に分解します。 そして、Hiki::Formatter_default でトークン列をHTML文字列に変換して @body に代入しています。

最後のrender :text => @body, :layout => trueは、@body の内容でビューをレンダリングするという意味で、HTMLタグも一切エスケープされずにそのまま出力されます。 その場合テンプレートは使われませんが、:layout => true と指定しているので layout は利用します。
動作確認
では、今まで実装した部分の動作確認をしてみます。 まずhttp://localhost:3000/wiki/new にアクセスしてページの新規作成画面を出します。 そして、何か適当なページ名(ここではSandBox)を入力してページを作成します。

SandBox 新規作成

編集画面に移るので、Hiki文法で何か適当に文章を記述します。

SandBox 編集

保存して正しくページが表示されることを確認します。

SandBox 表示

かなりWikiらしくなってきました。

flash
さて、show アクションに存在しないページ名をつけて呼び出したらどうなるでしょうか? 例えば http://localhost:3000/wiki/show/hogehoge にアクセスすると以下のようになってしまいます。

NoMethodError in Wiki#show

show アクションの中では、params[:id]で示されるページが存在していることを前提としているため、ページが存在しない場合は上記のようなエラーが発生してしまいます。 本当は、show アクションは次のように実装する必要があります。 指定のページが見つからなかった場合は、Hikiの動作にならい、「指定のページは存在しません」というメッセージを添えて新規作成画面を表示するようにしたいのですが、どうすれば良いでしょうか?
Railsにはこのような用途に使う flash という機構があります。 flashの実体はハッシュのようなもので、アクションの処理に関する各種データ、例えばメッセージなどの文字列を入れておくことができます。 flashに入れたデータはセッションという機構を使って次のリクエストだけに引き継がれ、その後は自動的に消されます。
その flash 機構を利用して show メソッドを以下のように書き換えます。
  def show
    @page = Page.find_by_name params[:id]
    if @page
      @title = (@page.title and !@page.title.empty?) ? @page.title : @page.name
      conf = Struct.new(:use_plugin, :use_wikiname).new(false, false)
      parser = Hiki::Parser_default.new conf
      tokens = parser.parse @page.content
      formatter = Hiki::Formatter_default.new tokens, self, conf
      @body = Hiki::Formatter_default.apply_tdiary_theme(formatter.to_s)
      render :text => @body, :layout => true
    else
      flash[:notice] = '指定のページは存在しません。ぜひ作成してください:-)'
      redirect_to :action => 'new', :id => params[:id]
    end
  end
前回の show に比べて、Page.find_by_name でページが見つからなかった場合の処理(else節)を加えています。 flash[:notice] の部分で、flash に「指定のページは存在しません」というメッセージを格納しています。 そして、new アクションへリダイレクトしていますが、flash のデータはそのアクションに引き継がれます。

一方、new のテンプレート app/views/wiki/new.rhtml は、flash にメッセージが格納されていれば、それを表示する必要があるので、以下のように修正します。
<% @title = '新規作成' %>
<div class="body">
<%if flash[:notice] %>
<p style="color: green"><%= flash[:notice] %></p>
<% end %>
<%= error_messages_for 'page' %> 
<%= start_form_tag :action => 'create' %>
  <%= text_field 'page', 'name' %>
  <%= submit_tag "Create" %>
<%= end_form_tag %>
</div>
追加したのは
<%if flash[:notice] %>
<p style="color: green"><%= flash[:notice] %></p>
<% end %>
の3行で、flash[:notice] に値が入っていればそれを表示するようにしています。

それでは動作確認です。http://localhost:3000/wiki/show/hogehoge (存在しないページ名)にアクセスすると、

新規作成+flash

このように new アクションにリダイレクトされて、「指定のページは存在しません。ぜひ作成してください:-)」というメッセージが表示されます。 そのままリロードするとflashのメッセージは消えますし、直接 new アクションを呼び出してもメッセージは表示されません。 flashの内容が保持されるのは、次のアクションまでというのがわかります。
しかし、このままでは入力フォームが空っぽでちょっと不親切です。 入力フォームに指定したページ名を入れるため、 app/controllers/wiki_controller.rb の new メソッドに一行追加して修正します。
  def new
    @page = Page.new
    @page.name = params[:id] if params[:id]
  end
ID が指定されていれば、それを@page.nameに代入しているだけです。 これで、http://localhost:3000/wiki/show/hogehoge にアクセスすると、

新規作成+flash

このように、あらかじめ入力フォームに指定したページ名が入るようになります。

投稿者 tam : 2005年08月31日 09:18

トラックバック

このエントリーのトラックバックURL:
http://tam.qmix.org/mt3/mt-tb.cgi/13

コメント

コメントしてください




保存しますか?