totoコードリーディングで学ぶRuby on 生Rack 「第1回 totoで使われているライブラリの動作を知ろう」

Pocket

約350行程度のコードでブログエンジンが実装されているtoto。このtotoのコードリーディングを通して、Rubyの実践的な使われ方やRackアプリケーションの作成方法を学んでいきたいと思います。第1回目はtotoで使用されている各ライブラリの使われ方を展望します。

筆者の環境

OS : MacOSX 10.6.7 (snow leopard)

Ruby : 1.9.2p136

Get the toto

Usage:

totoで使われているライブラリ

yaml

Rubyに標準添付されている、YAMLを扱うためのライブラリです。(YAMLとは?

Usage:

totoで読み込まれるarticleファイルは、上部がYAMLフォーマット、下部がMarkdownで記述されます。上記の例では、ファイル名から日付を読み取り、YAML.loadで返されるHashオブジェクトとmergeしています。

例: totoで読み込まれるarticleファイル

date

Rubyに標準添付されている、日付を扱うライブラリ。Dateクラスを使用するためにrequireする必要があります。

Usage:

年月日が「/(スラッシュ)」で区切られている場合も「YYYY-MM-DD」の形となるように整形しています。例外が発生した場合はDate.todayが実行されるように書かれています。

Date.todayの返り値は以下のようになります:

時刻を扱うTimeクラスはrequireなしに使えますが、文字列をパースしてTimeクラスのオブジェクトにするメソッドがないため、Dateクラスを使っているのだと考えられます。

erb

Rubyに標準添付されているテンプレートエンジンです。totoではerbを使用してHTMLファイルが出力されます。

Usage: (test/templates/index.rhtml)

< % %>内にRubyのコードを埋め込むことができます。オブジェクト自体の値を使用する場合は< %= %>の中にコードを記述します。

キーポイント: 配列の範囲指定

上記のコードをよく見てみると、配列の範囲指定が..ではなく、...になっています。実際の動作を確認してみると、以下の通りになります。選択される範囲が異なります。動作が紛らわしいので、個人的には..で統一した書き方をしたいところです。

rack

Rackアプリケーションを記述するために読み込みます。今回のキモの部分ですね。標準添付されているライブラリではないので、gem install rackでインストールします。

サーバーとなるクラスにcallメソッドを実装するだけでWebアプリケーションを作成することができます。(Rackなんて知らん!という方にはこちらの記事が最高に分かりやすいです:5分でわかるRack

Usage: (toto.rb)

totoでもServerクラス内にcallメソッドを実装しています。envがリクエストです。内部構造についてはコードリーディングを進める中で追いかけます。

Usage: (test/toto_test.rb)

totoのファイル構成内にはconfig.ruがないのですが、テスト内でRack::MockRequestにてサーバをnewしています。rackupではrunメソッドにサーバのインスタンスを読ませますが、ここではRack::MockRequestに読ませることで、以下のようなテストを行えるようにしています。

http://toto/aboutといったURLがGETメソッドでリクエストされた際に、どんなレスポンスを返すかテストしているものです。@toto = Rack::MockRequest.new(Toto::Server.new(@config))とすることで、@totoをWebアプリケーションとして扱えるようになります。

digest

Rubyに標準添付されているメッセージダイジェストを作成するためのライブラリです。

Usage:

一つ前に紹介しましたServer#call内で使われています。上記のように@response(Rack::Responseクラスのオブジェクト)のハッシュに値を設定すると、HTTPレスポンスのヘッダに値が設定されます。以下がtotoアプリケーションのHTTPレスポンスの例です。

これは何のために使われているのかというと、ブラウザ側の判断の材料として使われています。ブラウザはHTTPレスポンスの内容をキャッシュしていますが、キャッシュしてある内容と、これからレスポンスされてくる内容が同じかどうかの判断にHTTPレスポンスのヘッダが使用されます。以下のHTTPリクエストヘッダは、上記のレスポンスが返ってきた後に、もう1度同じURLでリクエストを送った場合の例です。

If-None-Matchに、レスポンスヘッダのETagと同じ値が設定されています。「「c3cbc478…」というETagのキャッシュを持っていますよ」とサーバに伝えています。サーバ側が「リクエストしてきているブラウザはキャッシュ持っているみたいだから、レスポンスのボディを返す必要はないや」と判断すると、ブラウザに「304 Not Modified」が返り、ブラウザのキャッシュが利用されることになります。

……というのがいわゆるフツーの仕様なのですが、totoは中の処理に成功すると必ず200を返してしまいますので、せっかくETagを設定しているのに304を返しません(ChromeのDeveloper Tools等でモニタリングしてみて下さい)。修正の余地がありますね。

digestの説明をしてなくないですか?

メッセージダイジェストとはとどのつまり、与えられた値から1方向(元に戻せない)ハッシュを作成するということです。コード例ではresponse[:body]、つまりレスポンスの内容、つまりHTMLファイルの内容からSHA-1というハッシュ関数を使用して、ハッシュを作成していました。HTMLファイルの内容が変わると、ハッシュの値が変わることになります。つまりキャッシュの判断にも使えるということですね。

open-uri

Rubyに標準添付されている、http/ftpに簡単にアクセスするためのクラスです。

Usage:

totoにはgithubのリポジトリ内のReadmeにアクセスする機能があるのですが、その機能の実現のために使われているようです。openメソッドの引数にURLを指定するとStringIO型のオブジェクトが返ります。そのオブジェクトにreadメッセージを送ると、レスポンスされてきたHTMLファイルの内容を取得することができます。

ちゃんとTimeout::Error例外、OpenURI::HTTPError例外を処理しているあたりが、巷のコードサンプルを読むより勉強になる点ですねえ。

1点気になるのがURLを作成する際のプレースホルダの使い方です。

%演算子でプレースホルダに値を設定できるんだ。。

はじめて知りました。。

marukuとrdiscount

maruku、rdiscount共にMarkdownパーサなのですが、Windowsで動かしている場合はmarukuが選択されるようになっています。gem install marukuまたはgem install rdiscountでインストールして下さい。

marukuをrequireする際に、MarkdownをMarukuに置き換えているところが参考になります。

Usage:

「あれ、でもrdiscountのREADMEを見ると、RDiscount.newみたいな形の使用例が載っているし、rdiscountをrequireする部分ではMarkdown = RDiscountって書かなければならないのでは?」

rdiscount/lib/rdiscount.rb

rdiscount.rb内で、Markdownでも使えるように定義されていました。

builder

最後になりました。builderはXMLを作成するためのライブラリです。gem install builderでインストールして下さい。

Usage:

totoではRSSファイルを作成するために使われています。実際のフォーマットは「#{page}.builder」で定義されています。instance_evalは引数の文字列をRubyのコードとして評価・実行します。ここでは、「#{page}.builder」ファイルの内容をそのままrubyコードとして実行するという処理になっています。

index.builder:

「instance_evalの直前で作られているxmlオブジェクトはどこで使われているんだろう?」答えはinstance_evalで評価されている文字列の中です。

まとめ

ここまでtotoで使われているライブラリをざっと眺めてきました。特に凄いライブラリを使っている訳でもなく、標準ライブラリを上手に使っている印象で、参考にしやすいコードになっているように見受けられます。「敵を知るには使っているライブラリから攻めろ」と誰かが言っていたのですが、使っているライブラリを見れば大体どんな処理があるか分かるような気がしますね!

さて、次回はRackの処理の部分からディスパッチの部分まで眺めて行きたいと思います。