26 Feb 2010

Cucumber入門(2) Cucumberの構成

前回の続き


Cucumberは巨大で多くのコンポーネントの集合体です。また構成もいくつか種類があり、試験対象のアプリケーションによって時に構成を変える必要があります。今回はCucumberの構成についてまとめ、選択肢について簡単に解説していきたいと思います。

まずはCucumberの依存関係を覗いてみましょう。アウトラインを把握するだけのためで、構成の選択肢とは関係ありません。

$ gem dependency cucumber --version 0.6.2 --remote
Gem cucumber-0.6.2
term-ansicolor (>= 1.0.4, runtime) #標準出力に色をつける
treetop (>= 1.4.2, runtime) #Ruby用の構文解析器生成系の一つ
polyglot (>= 0.2.9, runtime)
builder (>= 2.1.2, runtime)
diff-lcs (>= 1.1.2, runtime)
json_pure (>= 1.2.0, runtime)
nokogiri (>= 1.4.1, development) #HTML解析
prawn (= 0.6.3, development) #PDF出力
prawn-format (= 0.2.3, development)
rspec (>= 1.3.0, development)
syntax (>= 1.0.0, development) #Rubyコードのシンタクスハイライチィング
spork (>= 0.7.5, development) #テストサーバ


代表的な構成


単体でCucumberを動かすときは基本的に必要なコンポーネントはcucumberのみです。ただし、rubyのコードを試験したい場合はcucumberのみがセットアップされていれば動作しますが、pythonを試験するときはrubypythonが必要で、Javaを試験するときはjrubyからcucumberを動作させる必要があります。

参考 Python JRuby and Java

Web applicationを試験したい場合に選択肢が非常に多くなります。ここに代表的な構成を示しておきます。

Test Suite for TDD
+cucumber
|
Test Suite for Web App
+webrat
||
|Mode
|+selenium
|+mechanize
|+rails
|
+capybara
||
|Driver
|+selenium
|+celerity
|+culerity
|
+watir
|
browser
+firewatir
+safariwatir
+chromewatir
+celerity


ブラウザテスティングツールについてはruby-toolbox.com(ライブラリ比較サイト)がよくまとまっているので参考にされると良いと思います。簡単に解説します。

Webrat - Webアプリケーションの受け入れテストを簡単に綺麗に書くためのツール。
それそのものはブラウジングを行いません。seleniumやmechanizeが行います。JavaScript非対応。
Capybara - Webratと同等の機能を持つテスティングツール。すべてのブラウザに対応する事を目指している。
すべてのWebアプリケーションを試験できるわけではなく対応しているのはRack applicationのみ。
Xpath, Javascript, AJAX対応
Celerity - HtmlUnitのJRuby wrapper。Javaのheadless browser(UIなしブラウザ)です。JavaScript対応
Culerity - CucumberとCelerityを統合します。JRuby対応
Watir - 様々なブラウザに対応したテスティングツール。JavaScript非対応

その他の構成

Ruby on Railsを試験するときの構成オプション



ruby script/generate cucumber --webrat | --capybara | --rspec | --testunit | --spork

webrat, capybaraに関しては上記したとおりなので割愛します。rspec, testunit optionはstepsの実装をrspec, testunitで書くということではないかと思います。資料が見つからなかったので実装の仕方は分かりません。

Sporkオプションはscript/spec_serverに似ています。script/spec_serverはテスト用のDRubyサーバです。Railsの環境をロードしてテストコードがDrb越しにアクセスできるようにします。

Spork - テスト用のDRubyサーバ。サーバのコピーをテスト毎にフォークします。カーネルのフォークを利用するのでPOSIX準拠のOSでしか動作しません。SporkはDRbのオーバーヘッドがあるためシナリオの動作は遅くなります。代わりにシナリオを動作させるまでのRails framewokとrubygemsのロード時間を短縮します。つまり、シナリオの数が少ない方がSporkを利用する価値があります。複数になるとある時点でSporkを使っていない場合と速度が反転します。Cucumberのドキュメントによるとそれは"プロジェクトによるが10ー20シナリオ前後"であると記されています。

* Cucumber実行時に--drbオプション(次回以降に扱います)を付けることでfeaturesはSporkサーバ内で実行されます。Rubyインタープリタのプロセスにはロードされません。

.

13 Feb 2010

capistranoでフリーズ

新しい環境を構築中にcapistranoの設定ではまったのでメモ。

capistranoが止まる件はいくつか種類がある様ですが、私の場合はsubversionが対話式にパスワードを要求してきて処理が止まったのが原因。

- subversion+apache
- capistrano (2.5.14)
- rails (2.3.5)

という環境でsubversionから最新を引いて実行環境にアップロードしようとしたが失敗。
$ cap deploy:update -v
 ** transaction: start
Authentication realm:  administrators
Username: admin
Password for 'admin': 
Password: 
#=>ここでsvn用のpasswordの入力を求められる。
#入力すると以下が表示される

 ** [sanfrancisco :: out] Authentication realm:  administrators
 ** Username:

#=> Username: の一行したの行でフリーズしてしまう。入力を受け付けない
パスワードを一度入力してるのにまた入力を要求するところがよく分からない。

とりあえずrakeでトレースしながら実行すれば何か分かるかなと思ってやってみる
$ rake remote:setup --trace
rake aborted!
Don't know how to build task 'remote:setup'
あれ?remote:setupは廃止されたかな。。。

capistranoの導入ドキュメントにcap shellで一行ずつ実行する方法が載ってたのを思い出す。まずcapにオプション-d (--debug)を渡して、投げられてるコマンドを確認

$ cap deploy:update -dv 
:
Preparing to execute command: "svn checkout -q --username admin --password --no-auth-cache  -r1 
http://svn.sanfrancisco/yourfav /var/www/yourfav/releases/20100213222102 && (echo 1 > /var/www/yourfav/releases/20100213222102/REVISION)"
:
どうもsvnの--passwordオプションにパスワードそのものが定義されてないから要求されてそこで処理が止まってるんじゃないかな。ということで

$ cap shell
cap> svn checkout -q --username admin --password passadmin --no-auth-cache  -r1 
http://svn.sanfrancisco/yourfav /var/www/yourfav/releases/20100213213845 && (echo 1 > /var/www/yourfav/releases/20100213213845/REVISION)
[establishing connection(s) to sanfrancisco]
Password: 

cap> #次の行へ行った。てことは動いた?
パスワードを定義しないで実行すると、cap deploy:updateから実行した時と同じく止まりますが、今回は止まることなく次の行へ行きました。

そこでRAILS_HOME/config/deploy.rbに勘で以下のステップを追加したところうまく動作しました。
set :scm_password, "password_for_SCM"


誰かの役に立ちますように

10 Feb 2010

Processing on Ubuntu

手持ちのラップトップSamsung nc10のOSをwin XPからubuntuに入れ替えたのでProcessingの環境を再構築。

- processingのインストール
- よく使うライブラリのインストール
- JMyron

当方のOSはubuntu 9.10 karmic
$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=9.10
DISTRIB_CODENAME=karmic
DISTRIB_DESCRIPTION="Ubuntu 9.10"

processingのダウンロード、インストール


本家のダウンロードページに常に最新版のダウンロードリンクがあります。リンクアドレスをコピーしてください。
$ wget http://processing.org/download/processing-1.0.9.tgz
$ tar xvzf processing-1.0.9.tgz
$ sudo mv processing-1.0.9 /opt/processing-1.0.9
これだけです。

JREはprocessing直下のjavaフォルダに入ってるみたいなので別途インストール必要無いみたいです。

早速起動してみましょう
$ /opt/processing-1.0.9/processing

動作確認はFile->Examples以下のサンプルから。


JMyronのインストール


JMyronのインストールも本家から最新を取得。ここからはGUIで操作します。解答すると、HowtoInstall.txtというファイルがあるのでそれにしたがいます。
- Jmyronフォルダは先ほど配置したprocessingのホームディレクトリのしたのlibrariesディレクトリに移動
- Jmyron Examplesはprocessingのexamplesディレクトリに移動

librariesディレクトリは名前のとおりライブラリ群が配置されたディレクトリ。processingが参照パスが通ってます。先ほど動作確認を行ったとおりProcessingのメニューからexamplesディレクトリ以下のサンプルが参照可能です。私の場合メニューがExample>>JMyron Examlesとなるのが何だか気になってでディレクトリ名をJMyronに変更してます。

processingを再起動してFile->Examples->JMyron以下のサンプルを実行しようとするとJMyronをnewするステップで落ちます。soとそれにあわせたjarを配布しているページがこちらに有ります。ありがたいですね。日付リンクから最新版を取得します。

READMEにはJMyron.jarをprocessing/java/libにlibJMyron.soをprocessing/java/lib/i386に配置するように書いてありますが、両方共processing/libraries/JMyron/libraryに配置すれば動作します。 - パスが通っているみたいなので。



っと、ここまでやってprocessing.video.*が動かないことが判明。”Linux 向けの Processing には processing.video 自体が入ってない。”いやあほんとQuickTimeとか爆発してほしいですな。同感
ビデオの操作がしたい方はこちらを参考にしてください。Processing video on Linux

3 Feb 2010

Cucumber入門(1) 一番最初のCucumber

※. Javascript版のGoogle Searchに対応しました 21, Apl, 2012

明日Cucmberについてのmeetupがあるので予習します。 Cucumberとは
Cucmberはプレーンなテキストの機能記述を自動テストとして実行するツールです。Cucumberが理解する言語はGherkinというものです。 CucumberそれそのものはRubyで書かれていますが、Rubyやその他の言語(java、C#とpaythonを含みそれだけではない)で書かれたコードを”テスト”するために使われます。Cucumberは最小限のruby programmingを必要とし、Rubyは簡単です。なのでRubyでないコードを開発していても恐れないでください。 githubより
RailsにCucumberを導入した状態から始めようと思ったのですが、チュートリアルを読み進めていくうちにCucumberを構成するコンポーネントに選択肢がいくつかありCucumber自体規模が大きいことに気づいたので、まずは入門編ということでCucumberをrailsと組み合わせないで動作させてみることにしました。Cucumberの構成については後日また記事にしようと思います。 ちょうど良い記事があったので失礼して拝借させていただきます。 Getting started with Cucumber, RSpec, Webrat and multiruby


Cucumberでは以下のような感じでQAが書いたシナリオ試験や、仕様の振る舞いをGerhkinのフォーマットで自然言語で定義します。

簡単に自然言語で書かれたシナリオ試験をテストコードまでに落とすフローを示します。


1. Gerhkinのフォーマットで書かれたシナリオ - イメージをつかんでもらうための便宜上日本語にしました

主な特徴:  Google 検索
  自分のドキュメントがGoogleで検索できるようになているか確認したい。
 
  シナリオ: 
    条件  "https://www.google.com/" 
    場面  "engineerflies" を検索する
    そこで "Cucumber"と表示されている "/url?q=http://engineerflies.blogspot.com" というリンクがあるはず


2. シナリオの自然言語に正規表現でマッチするような、Webrat(首なしブラウザ) で実施するテストを用意してあげる
条件 /^"([^\"]*)" を開く$/ do |url|  
 visit url  
end  
  
場面 /^"([^\"]*)" を検索する$/ do |query|
  fill_in "q", :with => query 
  click_button "Google 検索"
end   
 
そこで /^"([^\"]*)"と表示されている"([^\"]*)" というリンクがあるはず$/ do |url, text|
  response_body.should have_selector("a[href^='#{ url }']") do |element|
    element.should contain(text)
  end
end



それでは今回はgoogleでこのブログを検索してヒットするまでをcucumberで検証してみたいと思います。今回のCucmberの構成は以下のとおり。Googleを使うのでWebratを使用します。

Cucmber + Webrat + Mechanize


* Webratの代わりにCapybaraという選択肢も考えましたが、CapybaraはgithubのdescriptionにあるようにRackアプリのテストを前提としているので今回は使用しません。Railsの外で使用する場合、SinatraやMerbで使用する場合は Capybara.app = MyRackApp のようにRackのアプリケーションを渡してあげる必要があります。

Cucmberのインストール

私の環境では次のバージョンをインストールしました
 #gemのインストール
sudo gem install cucumber -v 0.6.2
sudo gem install webrat -v 0.6.0
sudo gem install mechanize -v 1.0.0
sudo gem install rspec



下準備

- Cucumberに必要なディレクトリとファイルの作成
どこでもいいので以下のディレクトリ構成を作成します。
 $ mkdir -p cucumber/features #cucumberが実行するのに必要なテストスウィートを用意する場所
 $ cd cucumber/features
 $ touch search.feature #後で説明するfeatureを定義するファイル
 $ mkdir step_definitions #?
 $ mkdir support #環境設定ファイル
 $ vi support/env.rb
require 'webrat'

Webrat.configure do |config|
  config.mode = :mechanize # Mechanizeを使ってブラウジングする
end

World(Webrat::Methods)
World(Webrat::Matchers)
とりあえずその状態でcucumberを実行する。
 $ cd cucumber
 $ cucumber
   0 scenarios0 steps
   0m0.000s
当然ながらscenarioもstepも無い。scenarioとstepは以下を参照

Feature

まずfeatures/search.featureに以下を記述。
Feature: Search Google
  In order to make sure people can find my documentation
  I want to check it is listed on the first page in Google

  Scenario: Searching for suzukimilanpaak engineerflies
    Given I have opened "https://www.google.com/"
    When I search for "suzukimilanpaak engineerflies"
    Then I should see a link to "/url?q=http://engineerflies.blogspot.com" with text "Cucumber"
- Cucumberの便宜上の単位
  • Feature: 主要なもの、目玉。
  • Scenario: 人間が行う操作のまとまり
  • Step: 人間が行う操作や結果の最小単位
- 数の関係 一つの.featureファイルに複数のFeatureを書いたら Cucumber::Parser::SyntaxError になりました

単:Feature - 復:Scenario - 復:Step


- Stepの種類
  • Given~: ~がという状況が与えられている
  • When~: ~したとき
  • Then~: それで~なる(はず)


またcucumber実施
 $cucumber
Feature: Search Google
  In order to make sure people can find my documentation
  I want to check it is listed on the first page in Google

  Scenario: Searching for engineerflies                                                         # features/search.feature:5
    Given I have opened "https://www.google.com/"                                               # features/search.feature:6
    When I search for "suzukimilanpaak engineerflies"                                           # features/search.feature:7
    Then I should see a link to "/url?q=http://engineerflies.blogspot.com" with text "Cucumber" # features/search.feature:8

1 scenario (1 undefined)
3 steps (3 undefined)
0m0.007s

You can implement step definitions for undefined steps with these snippets:

Given /^I have opened "([^"]*)"$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end

When /^I search for "([^"]*)"$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end

Then /^I should see a link to "([^"]*)" with text "([^"]*)"$/ do |arg1, arg2|
  pending # express the regexp above with the code you wish you had
end
Scenarioとfeatureの各行末にfeatureの行番号が表示され、その後に結果が表示されます。
1 scenario (1 undefined)
3 steps (3 undefined)
0m0.002s 1つのscenarioが未定義、3つのstepが未定義。まだ、テストを記述していないので当然の結果ですね。

その後に"You can implement step definitions for undefined steps with these snippets:"のメッセージが表示されてます。

仰せのとおりに...

Steps

Stepsを記述していきます。

Given

features/search_steps.rbを用意します
 
Given /^I have opened "([^\"]*)"$/ do |url|  
 visit url  
end  
  
When /^I search for "([^\"]*)"$/ do |arg1| 
  pending
end

Then /^I should see a link to "([^\"]*)" with text "([^\"]*)"$/ do |arg1, arg2| 
    pending  
end

再度cucumberを実行
Feature: Search Google
  In order to make sure people can find my documentation
  I want to check it is listed on the first page in Google

  Scenario: Searching for suzukimilanpaak engineerflies                                         # features/search.feature:5
    Given I have opened "https://www.google.com/"                                               # features/step_definitions/search_steps.rb:1
    When I search for "suzukimilanpaak engineerflies"                                           # features/step_definitions/search_steps.rb:5
      TODO (Cucumber::Pending)
      ./features/step_definitions/search_steps.rb:6:in `/^I search for "([^\"]*)"$/'
      features/search.feature:7:in `When I search for "suzukimilanpaak engineerflies"'
    Then I should see a link to "/url?q=http://engineerflies.blogspot.com" with text "Cucumber" # features/step_definitions/search_steps.rb:15

1 scenario (1 pending)
3 steps (1 skipped, 1 pending, 1 passed)
0m1.039s
結果を見てみましょう。 1 passed - Givenだけが通りました。


結果の見方
1 passed - Givenのstepが通った
1 pending - Whenはpendingが指定されている
1 skipped - Whenがpendingのため、Thenはskipされた



When

次にWhen にフォーカスします。
When /^I search for "([^\"]*)"$/ do |query|
  #qという名前のHTML要素にqueryを入力
  fill_in "q", :with => query 
  click_button "Google 検索"  
end   
 
Feature: Search Google
  In order to make sure people can find my documentation
  I want to check it is listed on the first page in Google

  Scenario: Searching for suzukimilanpaak engineerflies                                         # features/search.feature:5
    Given I have opened "https://www.google.com/"                                               # features/step_definitions/search_steps.rb:1
Sorry, you need to install launchy to open pages: `gem install launchy`
    When I search for "suzukimilanpaak engineerflies"                                           # features/step_definitions/search_steps.rb:5
    Then I should see a link to "/url?q=http://engineerflies.blogspot.com" with text "Cucumber" # features/step_definitions/search_steps.rb:14
      TODO (Cucumber::Pending)
      ./features/step_definitions/search_steps.rb:15:in `/^I should see a link to "([^\"]*)" with text "([^\"]*)"$/'
      features/search.feature:8:in `Then I should see a link to "/url?q=http://engineerflies.blogspot.com" with text "Cucumber"'

1 scenario (1 pending)
3 steps (1 pending, 2 passed)
0m1.111s

Whenも通りましたね。うまく通らない場合はWhen の中でsave_and_open_pageでレスポンスの内容を作業ディレクトリに保存することができます。response_bodyにアクセスすることでGivenでvisit urlした結果、実際に返ってきたレスポンスを見ることができます。
When "..." do 
  save_and_open_page #=> ./webrat-1335366157.html

  # Above is equivalent to..
  File.open "webrat.html" do |f|
    f.puts response_body.inspect
  end
end
実際にwebratがどのようなリクエストをしているかは作業ディレクトリの./webrat.logにいかの様なけいしきで保存されます。
D, [2012-04-26T00:17:11.930393 #10569] DEBUG -- : REQUESTING PAGE: GET https://www.google.com/ with {} and HTTP headers {}
D, [2012-04-26T00:17:12.267081 #10569] DEBUG -- : REQUESTING PAGE: GET /search with {"btnG"=>"Google \346\244\234\347\264\242", "hl"=>"ja", "gbv"=>"1", "q"=>"suzukimilanpaak engineerflies", "ie"=>"Shift_JIS", "source"=>"hp"} and HTTP headers {"HTTP_REFERER"=>"https://www.google.com/"}


Then

 Then /^I should see a link to "([^\"]*)" with text "([^\"]*)"$/ do |url, text|
  #response bodyにはセレクター"a[href='#{ url }']"で指定した要素があるはず
  response_body.should have_selector("a[href='#{ url }']") do |element|
  #セレクターで取得された要素はtextを含んでいるはず
  element.should contain(text)
  end
 end
 
Feature: Search Google
  In order to make sure people can find my documentation
  I want to check it is listed on the first page in Google

  Scenario: Searching for suzukimilanpaak engineerflies                                         # features/search.feature:5
    Given I have opened "https://www.google.com/"                                               # features/step_definitions/search_steps.rb:1
Sorry, you need to install launchy to open pages: `gem install launchy`
    When I search for "suzukimilanpaak engineerflies"                                           # features/step_definitions/search_steps.rb:5
    Then I should see a link to "/url?q=http://engineerflies.blogspot.com" with text "Cucumber" # features/step_definitions/search_steps.rb:14

1 scenario (1 passed)
3 steps (3 passed)
0m1.292s
どうでしょう、3stepsとも通りましたでしょうか?


まとめ

以上のようにCucumberを使ったBDDでは、まずFeatureとScenarioを作成します。それから出来上がったScenarioをWebrat(首なしブラウザ)がブラウジングして結果をRspecの関数などでチェックします。このステップは自然言語であるGerhkinに正規表現でマッチするScenarioをどんどんプログラミングコードに落とすというようなことをすることになります。前者のステップまでをQA Scenario Writer、後者のステップからはQA Engineerが担当しRubyのコードをどんどん書くというふうに担当を分けることもできます。この時RubyをDSL(Domain Specific Language - ドメイン固有言語)と呼びます。実際のRubyでBDDを行っている現場では、簡易的なドメイン固有言語としてRubyのコードを最低限書けることというがQAの採用の条件になっていることがほとんどです。

Gerhkin編集専用のテキストエディタも出ていました。ずいぶんと一般化してきている印象 .

2 Feb 2010

rails ルートの操作

久しぶりにrailsの復習をしています。理解がおざなりだった部分とかも混ぜてちょっとずつちょっとずつ積み立てる予定。しばらくはこの状態が続きそう

今回はルートの操作のメモです。リクエストのルーティングがうまく通っているか確認したり、ルーティングを作成する方法を復習します。

サンプルコードを作ります。フィードを一覧、登録、編集するFeedsControllerを作ります。
rails -d mysql sample_routes ruby script/generate scaffold feeds


config/routes.rbで以下のルート定義が既に行われているものとします。
#関係あるのはこっちだけ
map.resources :feeds

map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'

scaffoldしたのでroutes.rbには既に自動で2行目のRestfulなマッピングが定義されているはずです。4、5行目はrailsコマンドでプロジェクトを作成したときに既に作成されているものです。

まずはscript/consoleを使ってRoutesオブジェクトを取得します。
$ ruby script/consoleLoading
development environment (Rails 2.3.5)
>> rs = ActionController::Routing::Routes


定義されたルートの確認



>>puts rs.routes
GET    /feeds(.:format)?                        {:controller=>"feeds", :action=>"index"}
POST   /feeds(.:format)?                        {:controller=>"feeds", :action=>"create"}
GET    /feeds/new(.:format)?                    {:controller=>"feeds", :action=>"new"}
GET    /feeds/:id/edit(.:format)?               {:controller=>"feeds", :action=>"edit"}
GET    /feeds/:id(.:format)?                    {:controller=>"feeds", :action=>"show"}
PUT    /feeds/:id(.:format)?                    {:controller=>"feeds", :action=>"update"}
DELETE /feeds/:id(.:format)?                    {:controller=>"feeds", :action=>"destroy"}::

出力される項目は左からHTTP Method、ルート定義、割り当てられるコントローラ/アクション。

またはrake taskからでもルート定義を表示できます
$rake routes

#grepが使える
$rake routes|grep feeds


URLから割り当てられるコントローラ、アクションを見つける



>> rs.recognize_path
"/feeds"=> {:controller=>"feeds", :action=>"index"}


混乱を避けるためmethodは指定しておいた方が良いです。
>> rs.recognize_path '/feeds/1'
=> {:controller=>"feeds", :action=>"1"}
>> rs.recognize_path '/feeds/1', :method=>:get
=> {:controller=>"feeds", :action=>"show", :id=>"1"}


また、そもそもコントローラが実装されていない場合はルートはマッチしません
>> rs.recognize_path '/foo'
ActionController::RoutingError:
No route matches "/foo" with {} from /opt/ruby-enterprise-1.8.7/lib/ruby/gems/1.8/gems/actionpack-2.3.5/lib/action_controller/routing/recognition_optimisation.rb:66:in 
`recognize_path' from (irb):24


コントローラ、アクションからURLを逆引きする


#コントローラだけ指定すると期待した動作をする。
>> rs.generate :controller => :feeds
=> "/feeds"#idを指定すると定義されていないURLが帰ってくる
>> rs.generate :controller => :feeds, :id => '123'
=> "/feeds?id=123"#recognize_pathでマッチしないルートなのでまあ無視しておこう。。。
>> rs.recognize_path '/feeds?id=123'
ActionController::RoutingError: No route matches "/feeds?id=123"
with {} from /opt/ruby-enterprise-1.8.7/lib/ruby/gems/1.8/gems/actionpack-2.3.5/lib/action_controller/routing/recognition_optimisation.rb:66:in 
`recognize_path' from (irb):20


作成前のコントローラのルートを作成する


#use_controllers! [”name_of_controller”]
>> ActionController::Routing.use_controllers! ['entries']
=> ["entries"]
#ルート定義をリロードしないとコントローラが有効にならない
>> load 'config/routes.rb'=> []
#確認
>> rs.recognize_path '/entries/show/1'
=> {:controller=>"entries", :action=>"show", :id=>"1"}
#routes.rbのmapの設定を行っていないのでこうなる。
>> rs.recognize_path '/entries/1'=> {:controller=>"entries", :action=>"1"}


http://api.rubyonrails.org/classes/ActionController/Routing/RouteSet/Mapper.html



ActionController::Routing::Routes.drawのブロック中の操作



準備
>> rs = ActionController::Routing::Routes
>> map = ActionController::Routing::RouteSet::Mapper.new rs


map.root
>> map.root :controller => "welcom", :action => "index"
=>
ANY    /                                        {:controller=>"welcome", :action=>"index"}


map.connect
>>   map.connect ':controller/ajax/:action'
=>
ANY    /:controller/ajax/:action/               {}


map.resources
>>   map.resources :feeds
=>
GET    /feeds(.:format)?                        {:controller=>"feeds", :action=>"index"}
POST   /feeds(.:format)?                        {:controller=>"feeds", :action=>"create"}
GET    /feeds/new(.:format)?                    {:controller=>"feeds", :action=>"new"}
GET    /feeds/:id/edit(.:format)?               {:controller=>"feeds", :action=>"edit"}
GET    /feeds/:id(.:format)?                    {:controller=>"feeds", :action=>"show"}
PUT    /feeds/:id(.:format)?                    {:controller=>"feeds", :action=>"update"}
DELETE /feeds/:id(.:format)?                    {:controller=>"feeds", :action=>"destroy"}


map.resource
>> map.resource :daily_sum
=> 
GET    /daily_sum/new(.:format)?                {:controller=>"daily_sums", :action=>"new"}
GET    /daily_sum/edit(.:format)?               {:controller=>"daily_sums", :action=>"edit"}
GET    /daily_sum(.:format)?                    {:controller=>"daily_sums", :action=>"show"}
PUT    /daily_sum(.:format)?                    {:controller=>"daily_sums", :action=>"update"}
DELETE /daily_sum(.:format)?                    {:controller=>"daily_sums", :action=>"destroy"}
POST   /daily_sum(.:format)?                    {:controller=>"daily_sums", :action=>"create"}

map.resourcesと比較した違い
- indexがない
- リソースが単一なので:idをURLに使用しない

resourceのオプション
- :member, :collection
map.resources :japan :member => {:county => :get}, :collection => {:progress_population => :get}
member => /nihon/:id/history, collection => /nihon/log

map.named_route
>> map.named_route 'error_occurred', '/:controller/internal_server_error', {:action=>"resque"}
=> 
ANY    /:controller/internal_server_error/      {:action=>"resque"}



map.namespace
>> map.namespace :admin do |admin|
>>  admin.resources :users
>>  admin.connect ':controller/:action/:id'
>> end
=>
GET    /admin/users(.:format)?                  {:controller=>"admin/users", :action=>"index"}
POST   /admin/users(.:format)?                  {:controller=>"admin/users", :action=>"create"}
GET    /admin/users/new(.:format)?              {:controller=>"admin/users", :action=>"new"}
GET    /admin/users/:id/edit(.:format)?         {:controller=>"admin/users", :action=>"edit"}
GET    /admin/users/:id(.:format)?              {:controller=>"admin/users", :action=>"show"}
PUT    /admin/users/:id(.:format)?              {:controller=>"admin/users", :action=>"update"}
DELETE /admin/users/:id(.:format)?              {:controller=>"admin/users", :action=>"destroy"}
ANY    /admin/:controller/:action/:id/          {}


map.method_missing(定義されていないメソッド)
>> map.login '/login', :controller => 'sessions', :action => 'new'
>> map.logout '/logout', :controller => 'sessions', :action => 'destroy'
=>
ANY    /login/                                  {:controller=>"sessions", :action=>"new"}
ANY    /logout/                                 {:controller=>"sessions", :action=>"destroy"}