コントローラを作る
前回までに、ビューを作るかデフォルトを修正するだけで、Wavesは様々なことができることを示しました。今回は一歩進んで独自のモデルやコントローラを作ってみます。
まずはWordコントローラを作ってみましょう。前回、controllers/default.rbを書き換えて、nameフィールドではなく、idフィールドでレコードを探すように修正しましたが、これをWordモデル限定の動作にして、他のモデルはWavesのデフォルトである「nameフィールドの値でレコードを見つける」にしたいからです。
default.rbを元に戻す
まず、controllers/default.rbの動作を元に戻してください。
この変更により、Twアプリケーションは正常に動作しなくなります。そこで、以下の修正を行ってください。
Wordコントローラを作る
生成されたcontrollers/word.rbを上記の逆に修正します。これでWordモデルの検索動作だけがidフィールドに対して行われます。Twアプリケーションも無事動くようになるはずです。
WavesのREST動作
前回も簡単に述べましたが、Wavesがデフォルトで提供しているREST動作やnameフィードでレコードを検索する動作は、デフォルトルールとしてconfigrations/mapping.rbで指定されています。「include Waves::Mapping::PrettyUrls::RestRules」と「include Waves::Mapping::PrettyUrls::GetRules」です。前者がREST動作を規定して、後者がnameフィールドによる検索を規定しています。デフォルトのルールが不満なら、mapping.rbからルールを削除して、自前で記述することも可能です。
include Waves::Mapping::PrettyUrls::RestRule
RestRuleをincludeすると、以下の3つの処理が使えるようになります。
Twアプリケーションで'/words'というURLに対してフォームの内容をPOSTすると、新規にWordレコードが生成されるのが、RestRuleの機能です。
include Waves::Mapping::PrettyUrls::GetRules
GetRulesをincludeすると、以下の処理が使えるようになります。
Twアプリケーションで'/words'というURLを呼び出すと、Wordテーブルの全レコードを取得して、templates/word/list.mabが呼び出されるのがGetRulesの機能です。
ユーザと関連を追加する
Twアプリケーションは今のままではアクセスした人の発言がそのままリストされるだけなので、ユーザ毎に発言をまとめる機能を追加します。そのためにUserモデルを追加して、ユーザと発言の関連も追加します。
Userモデルを追加する
次に認証を追加することを考慮して、ユーザ名とパスワードを格納するテーブルを作ります。
schema/002_add_users.rbを以下のように修正します。
Usersテーブルはid、name、passwordフィールドを持ちます。nameフィールドはユニークでなければなりません。また、発言は発言者に属するので、Wordsテーブルにuser_idフィールドを追加します。「rake schema:migrate」でマイグレーションを実行しましょう。テーブルが生成されます。次にWordモデルとUserモデルを作り、両者の関連を設定します。
models/word.rbを以下のように修正します。
一人のユーザが複数の発言を所有するので「many_to_one :user, :class=>Tw::Models::User」が必要になります。Railsと異なり、クラスまで指定する必要があります。逆にUserモデル(models/user.rb)には、「one_to_many :words, :class=>Tw::Models::Word」を追加してください。これでUserモデルとWordモデルの間に関連が設定されました。
認証を追加する
認証機能を追加して、発言者が分かるようにします。Wavesの認証に関しては明示的に書かれたドキュメントが無く、お勧めのやり方がどれかはよく分からないので、ここではRackのBASIC認証を使ってみます。
設定ファイル(configrations/development.rb)の修正
RackはWavesでWebサーバの抽象化を担うライブラリです。configrations/development.rbを以下のように変更することで、簡単なBASIC認証を使うことができます。
「use Rack::Auth::Basic」以下のブロック内で、Userモデルの名前とパスワードの一致を確認しています。
スタティックファイルの処理
また「use Rack::Static, :urls => ['/css', '/javascript'], :root => 'public'」を追加しています。Rack::StaticはRackのミドルウェアのひとつで、指定したディレクトリ配下のファイルはWavesに渡されず、即座にクライアントにレスポンスとして返されます。スタイルシートのように内容の変化しない静的なファイルの指定に利用します。
さてこれでとりあえず認証画面が出るようになったはずです。ブラウザから「http://localhost:3000/words」と入力してみてください。IDとパスワードの要求画面になれば成功です。
ビューを修正する
ユーザと発言には一対多の関連を設定しました。次に発言をPOSTするとき、それがどのユーザのものであるか指定する必要があります。発言をPOSTするビュー(templates/word/list.mab)を修正します。
発言をPOSTするときにユーザidを設定する
まず「@user = Tw::Models::User[:name=>request.env['REMOTE_USER']]」でユーザを取得しています(ユーザのチェックをページ表示ごとに毎回行うのは効率が悪いので、Rack::Auth::Basicで認証を行ったときにセッションで格納するなどの手段をとるべきかもしれません)。そして「input :type=>:hidden, :name=>'word.user_id', :value=>@user.id」にあるようにhidden要素の名前を'word.user_id'、値を@user.idにすることで、wordレコードのuser_idフィールドに該当ユーザのidがセットされます。
ここで/wordsに対してPOSTすればレコードの生成になるのは前述したとおりです。
またsummaryテンプレートに「view :word, :summary, :word=>word, :user=>@user」と@userを渡すことで、各発言と一緒にユーザ名も表示できるようになりますので、summaryテンプレート(templates/word/summary.mab)の修正を行います。cssの組み込みを考えて、クラス名なども設定しましょう。
Markabyテンプレート
ここまでWavesのデフォルトのテンプレートライブラリであるMarkabyについて、あまり説明してこなかったので、ここで簡単に説明します。通常のテンプレートライブラリでは、特殊なテンプレート用の記法を導入しているものがほとんどです。例えばRailsのテンプレートエンジンであるerubyではhtmlのコードの中に「<%= %>」といった特殊な記法を導入し、その中でRubyの式を書きます。
Markabyはこうした独自の記法をまったく使わず、全てRubyのコードで記述します。Markabyの例をドキュメントから引用します。
Markabyでは、Rubyのメソッドして解釈されない名前がhtmlの要素として展開されます。例えばtableメソッドは<table>要素になります。パラメータとしてハッシュを渡すと、html要素のアトリビュートになります。ただし第一引数の文字列は要素にはさまれる文字列として展開されます。 「a 'test', :href=>'/testhost/index'」とすると<a href="/testhost/index">test</a>」となるわけです。ネストする要素はブロック内に記述します。
要素のクラスとIDの指定には特別な記法が用意されています。contentsクラスのdiv要素なら「div.contents」、IDがheaderのdiv要素なら「div.header!」と記述します。
Wavesの各コンポーネントはドキュメントが少ないのが弱点なのですが、その上Markabyのように機能を動的に生成するライブラリが多いので、Rdocを見ても記述がないものがたくさんあります。今のところ地道にサンプルコードやソースコードを当たるしかないようです。
summaryテンプレートを修正する
以下のように修正します。各要素に適当なクラスを設定して、cssでデザインを指定できるようにします。list.mabのviewメソッドで呼び出したときに@wordと@userを設定しているので、この中で利用しています。
あとは適当なcssを設定すれば見栄えを変えることができるようになりました。
public/css/base.cssを作り、簡単に設定しておきます。
まとめと次回の予定
ユーザの認証、自発言と他人の発言の区別、cssによるデザインの設定などができるようになりました。次回はユーザ間の関連、Twitterで言うfollow/followingのような友人関係を組み込みたいと思います。