jasagiri @ ウィキ

CompactApp

最終更新:

jasagiri

- view
管理者のみ編集可
Getting Started - Compact Apps
Waves を gem でインストールしたいと仮定しましょう。

 $ gem install waves
ソースからも実行できます。

App Generation
次に、アプリケーションを作成したいディレクトリに移動してください。 waves generate を使ってアプリケーションを生成しましょう。
waves コマンドはおおくの splendored things です。(?)アプリケーションを生成し、サーバを走らせ、IRBコンソール内部の深いところに落とします。(?)
waves help または waves <command> --help を実行することでその機能のリストを確認できます。

Waves はアプリケーション構造について何の固定概念も持っていませんが、現在2つの生成手プレートを持っています。: classic と compact です。 compact アプリケーションは単に実行するのに十分なコードです。; 裏庭のある部屋を拡張する余地のある1つのファイルのプロジェクトです。 classic アプリケーションは Rails レイアウトと同様で、MVCインフラを提供するfaundation(基礎) とコード再読み込みマジックを使用します。 waves はデフォルトで classic テンプレートを使用しますが、このチュートリアルでは compact アプリケーションを必要とします。

 $ waves generate --name=spit_ball --template=compact 
 ** Waves 0.8.0 **
 ** Creating new Waves application ...
 ** Application created!

これは「spit_ball」というディレクトリとその中にたった1つのファイルを生成します。このチュートリアルと同じものをコード例のコミットリストで GitHub レポジトリから複製できます。

spit_ball ディレクトリからウェブサーバを開始しましょう。次のように:

 $ waves server
 ** Waves 0.8.0 **
 I, [2008-10-20 13:38:27 #3790]  INFO -- : Logger started.
 I, [2008-10-20 13:38:27 #3790]  INFO -- : Waves::Server starting ...
 I, [2008-10-25 11:24:31 #3790]  INFO -- : ruby-debug enabled
 I, [2008-10-20 13:38:27 #3790]  INFO -- : Mongrel started on 127.0.0.1:3000.


新生児の香りのように何もないアプリがlocalhostのポート3000で実行されて、 ブラウザで localhost:3000 を開くと悲しげな 404 Not Found を確認できるでしょう。私たちのダーリンはまだ何をしていいかわからないのです。

あなたのアプリケーションフォルダの1つのファイルは startup.rb です。 Waves runtimes (たとえば server や console)は開始するときこの名前のファイルを探します。 もちろん必要なときには --startup を使って異なったファイルを指定することもできます。スタートアップファイルは適切な foundation を必要としアプリケーションの名前空間を確立するでしょう。

 require 'foundations/compact'

 module Spitball
   include Waves::Foundations::Compact
 end




Foundations と Layers

Waves はレイヤアーキテクチャを使用しています。オプションで ORM サポートとテンプレートエンジンを「レイヤ」 モジュールをアプリケーションへmix-inで提供しています。さらに Waves はレイヤに重要な構造を決定させます。古典的なアプリケーションの (例えば)MVC インフラはレイヤによって提供されます。 foundation はレイヤモジュールでアプリケーションを実行するのに必要なすべての juju を設定したり混ぜたりします。(?) Foundations は他のレイアのスタックを混ぜて使い機能的です。

compact foundation はConfigurations と Resources のみを含む最小構成のアプリケーションです。 Configurations は Rails の環境と同じです。; compact アプリはデフォルトで development と production の設定を持っています。Waves runtimes (例えば server and console) は development 設定をデフォルトとしますが、?config オプションで別に使用している設定を指定できます。 後で設定のについて深く議論するつもりです。

Resources
Resources はウェブ要求のプロセスです。もちろん there’s some glue リソースへのリクエストを得てクライアントへレスポンスを返すための糊はありますが、強力な Resource はスパルタの戦士です。(?) 他のすべてのクラスは helots です。(?)

compact foundation はリソースの名前対応付けとリクエストプロセスのエントリポイントになります。 Map はデフォルトでどんなリクエストにもマッチしないので 404 Not Found です。 Spitball::Resources::Map を再度開いて変更してみましょう:

<waves-app>/startup.rb
 require 'foundations/compact'

 module Spitball
   include Waves::Foundations::Compact

   module Resources
     class Map
       on( :get ) { "Hello World!" }
     end
   end

 end

この呼び出しは次のようにマッピングされます:どんな GET リクエストでも、文字列“Hello World!”を返します。

結果を確認するには、サーバを再起動する必要があります( compact Foundation はコード再読み込みは提供されていません)。wavesサーバを再起動する場合はいつでも control-C を使用します。さて、ブラウザ画面を再読み込みすると、画面に“Hello World!”が確認できます。 localhost:3000 の後にどんなURLを入力してもこれが表示されるでしょう。なぜなら mapping 宣言はどんなパスにもマッチするからです。

You can restrict a mapping to specific paths by giving #on a URL matching argument, an array where the elements correspond to path components. E.g. [ "hello", "sailor" ] matches “/hello/sailor”

 on( :get, [ "hello" ] ) { "Hello World!" }

waves サーバを再起動して、 /hello へリクエストするだけで陽気な Hello を得ることができるでしょう。

URL にあっている配列では、シンボルがURLコンポーネントの値を取得します。アプリケーションに別の mapping 宣言を追加してください:

 on( :get, [ 'hello' ] ) { "Hello World!" }
 on( :get, [ 'hello', :name ] ) { "Hello, #{captured.name}!" }

waves サーバを再起動して、/hello/friend を開いてください。「Hello, friend!」と挨拶されるでしょう。キャプチャされているのは、それがどんな値だったとしてもURLから取得された openstructのようなオブジェクトです。 captured[:name] または captured['name'] を同じように使用できます。

宣言の順序は重要です:前に定義されているものより後で定義されたものが優先されます。これはRubyのメソッド宣言(後の宣言が前の宣言を上書きする)に拠り、予想可能な方法で Resources の継承を利用することを許容します。これらの宣言を考えてください。:

 on( :get, [ 'hello' ] ) { "Hello World!" }
 on( :get, [ 'hello', 'sailor' ] ) { "Ew!" }
 on( :get, [ 'hello', :name ] ) { "Hello, #{captured.name}!" }

これらのマッピングで「Ew!」を見ることはないでしょう。 最後の宣言が /hello/sailor へのリクエストを捕捉します。


Code Reloading and Resource Delegation
あなたはすべての変更の後サーバを再起動するのに飽きているとしましょう。 classic foundation は自動的にコードを再読み込みしますが、 compact apps へ再読み込みを付け加えるのは難しくありません。

startup.rb ファイルに次のように書いてください。:

 require 'foundations/compact'
 require 'autocode'

 module Spitball
   include Waves::Foundations::Compact

   module Resources
     include AutoCode
     auto_load true, :directories => '.'

     class Map
       on( true ) { to( :greeting ) }
     end
   end

   module Configurations
     class Development
       reloadable [ Resources ]
     end
   end

 end

いくつかのことがここで起こっています。

AutoCode はフレキシブルなコード再読み込みを提供します。 私たちは 現在のディレクトリでマッチしたファイルからSpitball::Resources をロードし、無くなった定数を引き出すために構成します。(?) 例えば Spitball::Resources::Greeting は ./greeting.rb からロードされるでしょう。 AutoCode は定数名のスネークケースを使ってファイルを探します。 (定数「BlogPost」はファイル名「blog_post」になります)。

再読み込み可能なリソースを指定するために Development 構成で再起動しました。 Any auto-loaded constants under Resources 配下のどんな自動ロードされた定数も各リクエストの度にファイルから再読み込みされるでしょう。
Resources::Map の #on 宣言は今やすべての HTTP リクエストにマッチし、Greeting リソースへ移譲します。 Greeting が startup.rb に定義されていなくても、現在のディレクトリのファイル名 greeting.rb から自動読み込みされるでしょう。

greeting.rb をアプリケーションディレクトリに作成してください。:

 module Spitball
   module Resources
     class Greeting
       include Waves::Resources::Mixin
       on( :get, [ 'hello' ] ) { "Hello World!" }
       on( :get, [ 'hello', :name ] ) { "Hello, #{captured.name}!" }
     end
   end
 end

Spitball::Resources::Greeting は前の繰り返しで、リソースとして正常に動作するのに Waves::Resources::Mixin をインクルードすること以外 Map リソースと同じです。

もう一度サーバを再起動して、すべてが動作していることを確認してください。 Greeting リソースへの変更は、その度にサーバを再起動すること無しにすぐに効果があります。

Continue to Part 2








Compact Applications, part 2


Spitball はファイルベースアプリケーションとして成長することを望みます。この願いを実現するために、私たちは Pastie と名付けられたリソースを作成して、/pasties で始まるパスへのすべてのリクエストを delegate する必要があります。まずは、 Map リソース(startup.rb 内)のリクエストマッチング定義をこのようにしてください。:


 on( true, [ 'hello' ] ) { to( :greeting ) }
 on( true, [ 'hello', true ] ) { to( :greeting ) }

 on( true, [ 'pasties' ] ) { to( :pastie ) }
 on( true, [ 'pasties', true ] ) { to( :pastie ) }

パスにマッチングしている配列では、対応する経路コンポーネントが存在する限り、true はどんな値でもマッチするでしょう。これが各リソースあたりどうして2つの定義が必要となるかの理由です; /hello は1つの経路コンポーネントしか持っていません。 このため [ 'hello', true ] はマッチしません。仲間が時々孤独になるため、当分 greeting bits は残しましょう。

次に、あなたのプロジェクトディレクトリに pastie.rb というファイル名でファイルを作成し、次のコードを追加してください。:

 require 'markaby'
 module Spitball
   module Resources
     class Pastie
       include Waves::Resources::Mixin

       # Request dispatching declarations
       on( :get, [ 'pasties' ] ) { list }

       # Resource methods
       def list
         pasties = Dir.entries( 'pasties' ).slice(2..-1)

         layout :title => "Spitball presents Pasties" do
           h1 "Have some pasties!"
           ul do
             pasties.each do |pastie|
               li { a pastie, :href => "/pasties/#{pastie}" }
             end
           end
         end

       end

       def layout( assigns = {}, &block )
         Markaby::Builder.new assigns do

           html do
             head { title @title }
             body do
               div.main!(&block)
             end
           end

         end
       end

     end
   end
 end

書いたら、 Pastie は今や GET リクエストを /pasties へハンドルします。 The return value of #list の戻り値はレスポンスボディとして使われるでしょう。また、 Markaby::Builder を使い、HTML をレンダリングする #layout ヘルパの紹介をしまいた。

Pastie#list はあなたのプロジェクトに pasties というディレクトリを持っていると仮定するので、ディレクトリやいくつかのダミーファイルを作成するのに、今がよい機会です。私たちは startup.rb を変更したので、もう一度 waves サーバを再起動する必要があります。 /pasties を訪れた時、あなたが作ったダミーファイルの一覧を見えるはずです。pastie リンクは実際にはまだ動作しておらず、次の修正をする必要があります。

まずは、request matcher に追加:

 # Request dispatching declarations
 on( :get, [ 'pasties' ] ) { list }
 on( :get, [ 'pasties', :name ]) { show( captured.name ) }

それから pastie.rb に show メソッドを追加:

 def show(name)
   pastie = "pasties/#{name}"
   response.status = 404 and return "404 Not Found" unless File.exist?( pastie )
   string = File.read( pastie )

   layout :title => "pastie #{name}" do
     h1 "Here's a hot pastie!"
     hr
     pre { text(string) }
     hr
   end
 end

/pasties への pastie リンクは今は動作していますが、ファイルが存在しない場合は #show で 404 を返し、無作為に pastie 名はそうしません(?)。このコードを提示します。 #response helper メソッドを使った HTTP response 属性でアクセスできます。 Waves には Not Found の状況をうまく扱うよりよい方法があり、これが以前の 404 エラーがはるかにきれいに見えていた理由です。以下のコードを response.status = ... 行の変わりに用いてください。:

 raise Waves::Dispatchers::NotFoundError unless File.exist?( pastie )

これは compact foundation ですべての NotFoundErrors を rescure する例外ハンドラを定義しているので動作します。この例外ハンドラは 404 のレスポンスステータスをセットして内蔵のエラーページをレンダリングします。

このアプリケーションは、現在リストで個別の pasties を表示できますが、まだ作成していない方法があります。少なくともフォームとそれを扱うアクションが必要です。次はよく使われている REST で、新しい pastie を作成するのに /pasties へ POST を使います。 An alternate setup, もしユーザに pastie の名前を選ばせるなら、/pasties/<name> への POST リクエストを受け入れるでしょう。これは項目を更新や新規作成する手段を提供するでしょう。このチュートリアルでは pasties は不変であると考えるつもりで、任意のファイル名に割り当てるつもりです。

request matchers に2つの定義を加えてください。; 1つはフォームを表示するためのもので、もうひとつは pastie を新規作成するためのものです。最初の定義は明確で新しいものは何も導入しません。2番目の定義は query helper を使用して、HTTP クエリへのアクセスを提供します。

 on( :get, [ 'pasties', 'form' ]) { form }
 on( :post, [ 'pasties' ] ) { create( query['code'] ) }

For the form, which will be accessible at /pasties/form create another view method in Pastie using the layout helper. 私たちはタイトルや他のどんなメタデータも保存しておらず、フォームは単一の textarea を必要とするだけです。お分かりのように上の request matcher のフィールド名は「code」です。

 def form
   layout :title => "Create a new pastie" do
     h1 "I want your pastie."
     form :method => :post, :action => "/pasties" do
       label "Code goes here" ; br
       textarea :name => :code, :id => "pastie_code", 
         :rows => 24, :cols => 80; br
       input :type => "submit", :value => "Save"
     end
   end
 end

pastie ファイルが作成された時、ファイル名を生成する必要があります。Using an MD5 ダイジェストを使用するのは、衝突する命名を避ける強固な方法です。pastie.rb の先頭に require 'digest/md5' を追加して、次のメソッドを加え、時間や乱数からダイジェストストリングを受け、最初の 80 文字を選びます。ダイジェストストリングで指定したファイルを作成した後で、 pastie text を含んで、helper メソッドへのリダイレクトを呼び出します。

 def create(text)
   digest = Digest::MD5.hexdigest("#{Time.now}#{rand(256)}#{text[0,79]}")
   pastie = "pasties/#{digest}"
   File.open( pastie, 'w' ) { |f| f.print text }
   redirect pastie
 end

最後に、list view にフォームへのリンクを追加してください。:

 def list
   pasties = Dir.entries( 'pasties' ).slice(2..-1)

   layout :title => "Spitball presents Pasties" do
     h1 { a "Give me a pastie!", :href => "/pasties/form" }
     h1 "Have some pasties!"
     ul do
       pasties.each do |pastie|
         li { a pastie, :href => "/pasties/#{pastie}" }
       end
     end
   end




Static assets
もし、見てくれを良くするのに CSS を使いたいと思うなら、(もちろん) views や layout helper にインラインで加えたり、実際のファイルを使えるように変更したりすることができます。後者を行う最も簡単な方法は、リソースの1つにリクエストマッチング定義を追加することで、実際のファイルから内容を得るのに File.read を使用するブロックかメソッドが支援されます。よりよい性能のために、URLへのリクエストを短くするミドルウェアを加えるために Rack の構成を変更することができます。

 module Configurations
   class Development
     reloadable [ Resources ]

     application do
       use ::Rack::ShowExceptions
       use ::Rack::Static, :urls => [ '/public/site.css' ], :root => './'
       run ::Waves::Dispatchers::Default.new
     end
   end
 end

アプリケーションに渡されたブロックは、あなたのアプリケーション用の Rack を構成します。この例は classic アプリケーションテンプレートから借りており、私たちが使うために簡素化されています。 run メソッドは Waves アプリケーションへのエントリーポイントを Rack に与え、その行を渡します。 The use method inserts pieces of Rack middleware into the processing chain. Here the Rack::Static middleware intercepts any requests /public/site.css and serves that file directly from the application root.

Next we modify the layout to link to that URL:

 def layout( assigns = {}, &block )
   Markaby::Builder.new assigns do
     html do
       head do
         title @title
         link :href => '/public/site.css', :rel => 'stylesheet', :type => 'text/css'
       end
       body do
         div.main!(&block)
       end
     end
   end
 end

あなたは public/site.css のようにどんなスタイルでも設定できます。私好みなのはこれです。:

 body { background: #9cf; }
 a, a:visited { color: #069; }
 pre { padding: 10px; background: #eee; }
 li { list-style: none;}
 hr { color: transparent; background: #900; }
 div#main { margin: 25px; padding: 25px; background: white; }


It’s alive!
今やリーズナブルで機能的なアプリケーションがあります。次のステップは Logical です。:

  • 無意味なファイル名の代わりに、各 pastie のための blurbs を表示するために list view を変更します。
  • 改ページや pasties の数を制限してリストページを表示します。おそらく最新のファイルだけを手に入れるために find - cmin を使います。(?)
  • 既存の pastieテキストのある新しい pastie を作る方法を提供します。
  • ファイルのファイル拡張子を使用して各 pasite の言語を追跡します。(?)
  • シンタックスを色付けします。
  • タイトルと他のメタデータを pastie に追加してください。 requiring a change from the simplistic file content == pastie text storage method

私たちは読者の理解とエクササイズのためにこれらを残しておきます。
記事メニュー
目安箱バナー