2.0のscaffoldから書き直してみる

Rails2.0.2環境にして試行錯誤中。前回までにRails1.2.6の既存プロジェクトを2.0.2環境で稼働出来るようにはなった。しかし、プロジェクトを新規作成してから始めようとすると、また勝手が違っていることに気付く。ここは基本に返って、scaffoldから同じプロジェクトtest_slip202を作り直してみることに。

作業環境

  • MacBook OSX10.5 Leopard
  • RadRails0.7.2
    • 現在は配布を終了したのかダウンロード出来ない状態になっている。これからRadRailsを使いたい方は、最新のaptana使うしかなさそう...。(以前、導入に試行錯誤して、結局諦めて0.7.2を使い続けることにした。)
    • 最近は、シンプルにターミナルと自分好みのエディタで十分な気がしてきた。(動作が鈍い&バージョンアップ対応の遅れがちな統合開発環境よりも)
  • sqlite3.4.0
    • MacBook OSX10.5 Leopard環境には、sqlite3.4.0が最初からインストール済みの状態。

プロジェクト作成〜サーバー起動

  • Rails2.0.2環境版、1アクションで複数のモデルを同時に保存プロジェクトtest_slip202を新規作成した。
$ rails test_slip202
$ cd test_slip202
$ script/server
 => Booting Mongrel (use 'script/server webrick' to force WEBrick)
 => Rails application starting on http://0.0.0.0:3000
 => Call with -d to detach
 => Ctrl-C to shutdown server
 ** Starting Mongrel listening at 0.0.0.0:3000
 ** Starting Rails with development environment...
 ** Rails loaded.
 ** Loading any Rails specific GemPlugins
 ** Signals ready.  TERM => stop.  USR2 => restart.  INT => stop (no restart).
 ** Rails signals registered.  HUP => reload (without restart).  It might not work well.
 ** Mongrel 1.1.3 available at 0.0.0.0:3000
 ** Use CTRL-C to stop.
Welcome aboard
You’re riding the Rails!
About your application’s environment

Ruby version	        1.8.6 (universal-darwin9.0)
RubyGems version	1.0.1
Rails version	        2.0.2
Active Record version	2.0.2
Action Pack version	2.0.2
Active Resource version	2.0.2
Action Mailer version	2.0.2
Active Support version	2.0.2
Application root	/Users/zari/railsapp/test_slip202
Environment	        development
Database adapter	sqlite3
  • デフォルトのデーターベースはsqlite3に変更になったようだ。

scaffoldを実行する

  • 以前のscaffoldと違って、モデル名に続けて「フィールド名:データ形式」の書式でオプション指定しておく必要がある。
$ script/generate scaffold slip number:integer executed_on:string total_yen:integer
  • scaffoldのオプション指定の手間は増えるが、下記の通りマイグレーションファイルが自動生成されるので、結果として効率はさらに上がった。

scaffoldで何が起こったか?

  • Rails2.0以前の動作
    • appフォルダ以下にindex、show、new、create、edit、update、deleteアクションに必要なモデル(M)、ビュー(V)、コントローラー(C)のファイルを生成する。
    • testフォルダ以下に上記に関連するtestファイルを生成する。
    • 上記webアクセスでの見栄えを設定するスタイルシートを生成する。(public/stylesheets/scaffold.css
  • 上記に加えて、Rails2.0から追加された動作
    1. モデル名に続けた「フィルド名:データ形式」のオプション指定に基づくマイグレーションファイルを生成する。(db/migrate/001_create_slips.rb)
    2. config/routes.rbファイルに「map.resources :slips」を追記する。

Rails2.0から追加された動作1、2、についてもう少し詳しく見てみた。

1.マイグレーションファイルの追加
# マイグレーション: app/db/migrate/001_create_slips.rb
class CreateSlips < ActiveRecord::Migration
  def self.up
    create_table :slips do |t|
      t.integer :number
      t.string :executed_on
      t.integer :total_yen

      t.timestamps
    end
  end

  def self.down
    drop_table :slips
  end
end
  • マイグレーションファイルの書式もちょっと変更されている。
    • t.データ形式 フィールド名という書き方になっている。(以前は逆でt.フィールド名 データ形式だった。)
    • この変更で何が嬉しくなるのか?...それは、以下のようにフィールド名をカンマで区切って複数指定することが可能になること。(この場合フィールド名の順番は違ってしまうが...。)
# マイグレーション: app/db/migrate/001_create_slips.rb
class CreateSlips < ActiveRecord::Migration
  def self.up
    create_table :slips do |t|
      t.integer :number, :total_yen
      t.string :executed_on
...(中略)...
    • t.timestampsとうい書き方は何?...それは、以下と同等らしい。
# マイグレーション: app/db/migrate/001_create_slips.rb
class CreateSlips < ActiveRecord::Migration
  def self.up
    create_table :slips do |t|
      t.datetime :created_at, :updated_at
      # または...
      t.datetime :created_on, :updated_on
...(中略)...
2.routesファイルへの追記
  • scaffold後、config/routes.rbにはオレンジ色の部分が追記された。
ActionController::Routing::Routes.draw do |map|
  map.resources :slips
...(中略)...
  map.connect ':controller/:action/:id'
  map.connect ':controller/:action/:id.:format'
end

マイグレーションの実行

  • マイグレーションを実行した。ちなみに、databese.yamlはデフォルトのままでOK。(sqliteにはユーザー名もパスワードも無いので、余分な設定が必要ないところがいい。)
$ rake db:migrate
 (in /Users/bebe/railsapp/test_slip202)
 == 1 CreateSlips: migrating ===================================================
 -- create_table(:slips)
    -> 0.0041s
 == 1 CreateSlips: migrated (0.0043s) ==========================================

アクセスしてみる

  • scaffoldが生成してくれたページにアクセスしてみる。
http://0.0.0.0:3000/slips
  • いつものscaffoldと同じ画面が現れた。操作も同じだ。

URLの変化

  • ところが、アクションに対するURLが変化していた。
アクション Rails2.0のURL Rails2.0のメソッド 以前のURL 以前のメソッド
index http://0.0.0.0:3000/slips GET http://0.0.0.0:3000/slips GET
new http://0.0.0.0:3000/slips/new GET http://0.0.0.0:3000/slips/new GET
create http://0.0.0.0:3000/slips POST http://0.0.0.0:3000/slips/create POST
show http://0.0.0.0:3000/slips/1 GET http://0.0.0.0:3000/slips/show/1 GET
edit http://0.0.0.0:3000/slips/1/edit GET http://0.0.0.0:3000/slips/edit/1 GET
update http://0.0.0.0:3000/slips/1 PUT http://0.0.0.0:3000/slips/update/1 POST
destroy http://0.0.0.0:3000/slips/1 DELETE http://0.0.0.0:3000/slips/destroy/1 POST
  • URLだけ見ると、以前のURLの方が分かり易いと思った。
  • Rails2.0からは、リクエストメソッドGET、POSTに加え、PUT、DELETEも利用するようになった。URLとGET、POST、PUT、DELETEメソッドを組み合わせて、コントローラーのアクションを呼び出す。(以前のURLとGET、POSTで処理する方法も引き続き利用できる。)

生成されたコード

コントローラー
  • indexアクションを例に見てみると、respond_to do〜endブロックが追記されている。
class SlipsController < ApplicationController
  # GET /slips
  # GET /slips.xml
  def index
    @slips = Slip.find(:all)

    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @slips }
    end
  end
...(中略)...
  • 上記のように、基本的にすべてのアクションにrespond_to do〜endブロックが追記された*1
  • respond_to do〜endブロックでは、要求されるレスポンスに応じた描画方法で処理し、その結果を返してくれる。
  • この場合は、htmlかxmlで結果が返ってくる。(拡張子.xmlが付加されたhttp://0.0.0.0:3000/slips.xmlだとxmlで描画される。)
  • もし、xmlなんか興味ない、htmlだけで十分、という場合は以下のようにしても良いのではないだろうか。
# コントローラー: app/controllers/slips_controller.rb
class SlipsController < ApplicationController
  def index
    @slips = Slip.find(:all)
  end

  def show
    @slip = Slip.find(params[:id])
  end

  def new
    @slip = Slip.new
  end

  def edit
    @slip = Slip.find(params[:id])
  end

  def create
    @slip = Slip.new(params[:slip])

    if @slip.save
      flash[:notice] = 'Slip was successfully created.'
      redirect_to(@slip)
    else
      render :action => "new"
    end
  end

  def update
    @slip = Slip.find(params[:id])

    if @slip.update_attributes(params[:slip])
      flash[:notice] = 'Slip was successfully updated.'
      redirect_to(@slip)
    else
      render :action => "edit"
    end
  end

  def destroy
    @slip = Slip.find(params[:id])
    @slip.destroy
    redirect_to(slips_url)
  end
end
  • このようにして見ると、コントローラーでの以前との違いはredirect_toの書き方だけのようだ。
redirect_to(@slip) redirect_to :action=>'show', :id=>@slip と同等
redirect_to(slips_url) redirect_to :action=>'index' と同等
  • 上記のシンプルな書き方を可能にしているのが、routesファイルに追記されたmap.resources :slipsの1行。試しにコメントアウトしてみると、オブジェクトそのもの、またはsllips_urlのような書式の部分は全部エラーになる。(ビューファイルも含めて)
ビュー
  • 拡張子.rhtmlは.html.erbに変更された。(.rhtmlも引き続き利用可能)
  • コントローラー、アクション、idを指定するあらゆる箇所で、上記コントローラーと同じ記法が利用されている。その部分だけ抜粋してみた。
<%= link_to 'Show', slip %>
<%= link_to 'Edit', edit_slip_path(slip) %>
<%= link_to 'Destroy', slip, :confirm => 'Are you sure?', :method => :delete %>
<%= link_to 'New slip', new_slip_path %>
<% form_for(@slip) do |f| %>
<%= link_to 'Back', slips_path %>
  • form_for(@slip) do |f|ブロックの中では、f.submit、f.label、f.error_messsage_on、f.error_messagesのような書き方も追加された。

map.resources :slipsは何をしているのか?

  • rails2.0ではrake routesで、現在のルーティング定義を確認できる。実行してみると...
$ rake routes
(in /Users/zari/railsapp/test_slip202)
...(ここから)...
              slips GET    /slips                           {:controller=>"slips", :action=>"index"}
    formatted_slips GET    /slips.:format                   {:controller=>"slips", :action=>"index"}
                    POST   /slips                           {:controller=>"slips", :action=>"create"}
                    POST   /slips.:format                   {:controller=>"slips", :action=>"create"}
           new_slip GET    /slips/new                       {:controller=>"slips", :action=>"new"}
 formatted_new_slip GET    /slips/new.:format               {:controller=>"slips", :action=>"new"}
          edit_slip GET    /slips/:id/edit                  {:controller=>"slips", :action=>"edit"}
formatted_edit_slip GET    /slips/:id/edit.:format          {:controller=>"slips", :action=>"edit"}
               slip GET    /slips/:id                       {:controller=>"slips", :action=>"show"}
     formatted_slip GET    /slips/:id.:format               {:controller=>"slips", :action=>"show"}
                    PUT    /slips/:id                       {:controller=>"slips", :action=>"update"}
                    PUT    /slips/:id.:format               {:controller=>"slips", :action=>"update"}
                    DELETE /slips/:id                       {:controller=>"slips", :action=>"destroy"}
                    DELETE /slips/:id.:format               {:controller=>"slips", :action=>"destroy"}
...(ここまで)...
                           /:controller/:action/:id         
                           /:controller/:action/:id.:format 
  • 結果を見ると、map.resources :slipsによって、...(ここから)......(ここまで)...の範囲が定義されているようだ。
  • respond_toに対応するため、「.:format」を付加したものが必ずペアで存在している。(これがslips.xmlのような書式を可能にしていると思う。)
  • 書式は以下のようになっている。(以下「.:format」を除いた一覧)
名前付きルート HTTPメソッド URLフォーマット コントローラー、アクション
slips GET /slips {:controller=>"slips", :action=>"index"}
POST /slips {:controller=>"slips", :action=>"create"}
new_slip GET /slips/new {:controller=>"slips", :action=>"new"}
edit_slip GET /slips/:id/edit {:controller=>"slips", :action=>"edit"}
slip GET /slips/:id {:controller=>"slips", :action=>"show"}
PUT /slips/:id {:controller=>"slips", :action=>"update"}
DELETE /slips/:id {:controller=>"slips", :action=>"destroy"}
  • 上記定義によって、素晴らしくシンプルなurl指定を可能にしているようだ。


map.resources :slipsが提供する仕組みは、今までダラダラ書いていたurl指定を素晴らしいシンプルさで表現できるようになった。しかし、その代わりに、その仕組みは見えにくくなったと感じている。(たぶん、自分がルート定義に慣れていないからだろう...。)ルート定義については、今までほとんど活用していなかった...。Rails2.0を便利に使うためには、もう少し勉強が必要だ。

*1:newアクションにはあり、editアクションには無いのが気になるが...。なぜ?