Waves チュートリアル パート3

http://www.rubywaves.com/tutorial-3
パート1の翻訳はこちら
パート2の翻訳はこちら

        • -

先に進む前に,今までやってきたことの簡単なおさらいです。

  • waves コマンドで新規プロジェクトを作った
  • データベース設定ファイル configurations/default.rb を自分の環境用に修正した
  • rake schema:migration と rake schema:migrate を使って,最初のスキーマを作成した
  • waves コンソールを使い,Waves が正しくデータベースからモデルを作ったかを確認して,テスト用のレコードを追加した
  • レイアウトと再利用ビューを使い,ブログエントリの一覧,表示,編集テンプレートを作った
  • レコードの追加,更新を行うのに,REST スタイルのインターフェースを用いた.同じ URL が GET か POST かによって異なったマッピングがなされる.

ビューのコードを書くだけでこれら全てのことができました。テンプレートにアクセスするとき,データがなんらかの方法でデータベースから取り出されて,インスタンス変数に詰め込まれたのは明らかです。

モデルにコメントを追加することで,もう少し複雑なことをしてみます。こうするには,モデルを明示的に定義しなければならず,Waves のデフォルトをどのようにオーバライドするかが明らかになります。

コメント用に新しいマイグレーションを追加します。

~/blog $ rake schema:migration name=add_comments

生成されたマイグレーションファイルを編集します(scheme/migrations ディレクトリにあって,002_add_comments.rb のような名前になっていることを思い出してください)。編集が終われば,こんな風になっているはずです。

class AddComments < Sequel::Migration

  def up
    create_table :comments do
      primary_key :id
      foreign_key :entry_id, :table => :entries
      text :name
      text :email
      text :content
      timestamp :created_on
    end
  end

  def down
    drop_table :comments
  end

end

timestamp を追加したことに注意してください。これは Sequel によって自動的に設定されます。

マイグレーションを実行します。

~/blog $ rake schema:migrate

次に Entry モデルを,コメントを保持するように修正します。ここが面白いところです。なぜなら Entry モデルなんて作っていませんから。でも問題ありません。Waves では,何かを明示的に定義すれば,それはデフォルトをオーバライドするようになっています。なので,単純に entry.rb を models ディレクトリに作って修正します。一番簡単なのはジェネレータタスクを使うことです。

~/blog $ rake generate:model name=entry

では entry.rb を開き,編集してください。コメントの関連を追加する必要があります。こんな感じです。

module Blog
  module Models
    class Entry < Sequel::Model     
      before_save do
        set(:updated_on => Time.now) if columns.include? :updated_on
      end
      one_to_many :comments, :from => Blog::Models::Comment, :key => :entry_id
    end
  end
end

before_save 部は,ジェネレータタスクが自動的に作り,これは default.rb のデフォルト実装に基づいています。comments への一対多関連を,Sequel の one_to_many マクロを使って追加しました。これは Rails の has_many と同じですが,ちょっとだけ分かりやすい名前になってます。

次に同じことを Comments に行い,コメントが Entry を参照するようにします。まず,さきほどと同じように,モデルを生成します。

~/blog $ rake generate:model name=comment

次に関連を追加します。こんな感じです。

module Blog
  module Models
    class Comment < Sequel::Model(:comments)      
      before_save do
        set(:updated_on => Time.now) if columns.include? :updated_on
      end
      one_to_one :entry, :from => Blog::Models::Entry
    end
  end
end

では entry/show テンプレートにコメントの一覧表示と追加機能を付け加えましょう。こんな風になるはずです。

layout :default, :title => @entry.title do
  a 'Show All Entries', :href => '/entries'
  h1 @entry.title
  textile @entry.content
  h1 'Comments'
  view :comment, :add, :entry => @entry
  view :comment, :list, :comments => @entry.comments
end

ここでやってることはコメント関連のビューをフォームに埋め込んでいるだけです。したがって次にやるべきことは,そのビューを作ることになります。コメント追加用のビューから始めましょう。templates/comment に add.mab を作ります。

form :action => "/comments", :method => 'POST' do
  input :type => :hidden, :name => 'comment.entry_id', :value => @entry.id
  label 'Name'; br
  input :type => :text, :name => 'comment.name'; br
  label 'Email'; br
  input :type => :text, :name => 'comment.email'; br
  label 'Comment'; br
  textarea :name => 'comment.content', :rows => 10, :cols => 80; br
  input :type => :submit, :value => 'Save'
end

簡単ですね。REST スタイルの決まり事を使い,新しいオブジェクトを POST メソッドで /comments に追加します。hidden フィールドに entry_id を持つので,どのエントリにコメントを追加すればいいのか分かります。コメントはエントリ画面でのみ編集されるので,レイアウトは不要です。

次にコメント一覧です(templates/comment/list.mab)。

@comments.map{ |c| c }.sort_by( &:created_on ).each do |comment|
  p 'Posted on ' << comment.created_on.strftime('%b %d, %Y') << ' by ' <<
    ( ( comment.name.nil? or comment.name.empty? ) ? 
      'anonymous coward' : comment.name )
  textile comment.content
end

(Sequel の MySQL アダプタの制限のため,不格好な map が必要になります)
これでそれぞれのエントリーに,追加用フォーム,コメント一覧ができました。

ブログのエントリにコメントを追加してみましょう。/entries にアクセスして,'My First Blog Entry' をクリック,コメントを追加します。

http://www.rubywaves.com/images/adding-a-comment?size=medium

"Save" をクリックすると…おっと。404 ですね(200 で空白ページの場合もあるようです)。何が起きたのでしょうか。問題はデフォルトの URL マッピングRails でいうルート)だと,指定のリソースを追加したあとにエディタを読み込もうとするためです。今回はエディタを定義していませんし,単に追加したコメントを表示したいだけです。なので,新しいマッピングルールを追加することにします。

これはとても分かりやすいので,Wavesマッピングルールを見るのに向いています。configurations ディレクトリの mapping.rb を開いてください。中を見てみましょう。

module Blog
  module Configurations
    module Mapping
      extend Waves::Mapping
      # your custom rules go here
      include Waves::Mapping::PrettyUrls::RestRules
      include Waves::Mapping::PrettyUrls::GetRules
    end
  end
end

Waves の他の部分と同じように,まずアプリケーションモジュール(このケースでは Blog)をオープンしています。マッピングは設定の一部なので Configurations モジュールもオープンしています。最後に自前の Mapping モジュールを定義しています。これが Waves がリクエストを処理するときに参照する部分です。

マッピングモジュールは通常 Waves::Mapping を extend して,マッピングメソッドを使えるようにして,よく使われるパターンをまとめた定義済みのルール集を include します。自分用のパターンを定義することもできます。デフォルトでは,Waves は追加,更新,削除ルールの REST スタイルのキレイな URL を提供します。

今回のケースでは,コメント用に自前のルールが必要です。どうすれば新しいルールを追加できるのでしょう? やり方はいくつかありますが,ここでは path メソッドを使います。path メソッドは URL とマッチする正規表現,制約のハッシュ,そして1つのブロックを引数にとります。新しいコメントを追加するために,デフォルトをオーバーライドしましょう。"your custom rules go here" というコメントのすぐ下に追加します。

path %r{^/comments/?$}, :method => :post do
  use( :comment ); comment = controller { create }
  redirect( "/entry/#{comment.entry.name}" )
end

このルールは,comment をモデル,ビュー,コントローラとして使い,コントローラの create メソッドを呼び出し,comment の entry にリダイレクトするいう意味です。

ブラウザの「戻る」ボタンを押して,もう一度コメントを追加してみてください。コメントが2つ追加されているはずです。これは最初に "save" をクリックしたときにもコメントが保存されたのに,正しい URL にリダイレクトされてなかったからです。

チュートリアルの次のパートでは,スタイルシートと少しの JavaScript を追加して,Blog アプリを少し見栄えよくします。準備が出来たらパート4へ。