Hatena::Grouprails-recipebook

Rails RecipeBookはてな版

書籍化を目標にRailsについて書いております。申し訳ありませんが禁無断転載でよろしくお願いします。

[記事一覧]

2008-05-25

[重要] Railsレシピブックが出版されます 00:28  [重要] Railsレシピブックが出版されます - Rails RecipeBookはてな版 を含むブックマーク はてなブックマーク -  [重要] Railsレシピブックが出版されます - Rails RecipeBookはてな版  [重要] Railsレシピブックが出版されます - Rails RecipeBookはてな版 のブックマークコメント

いままでこの日記で一部の原稿を公開・オープンレビューしていただきましたRailsレシピブックが、ついに書籍として出版できる運びとなりました。

いろいろな指摘をくださった方々、この日記をブクマしてくださった方々、記事を読んで興味をもってくださった方々にお礼を申し上げます。ありがとうございました。

Railsレシピブック 183の技

Railsレシピブック 183の技

出版に当たって、公開中の記事はいったん削除させていただきたいと思います。ただし、多くの方々に目を通していただいた一部の記事は、そのまま残しておきたいと思います。(ブックマーク数が10users以上であるものを残すという基準で選んでいます。)

この他の記事については、削除させていただきます。

また、残しているこれらの記事に関しても記述が古い場合やサンプルコードが間違っている場合等があります。説明やサンプルコードを見直したレシピがこの「Ralsレシピブック」に載っていますので、ぜひご覧ください。

2008年5月26日

Railsレシピブック著者

トラックバック - http://rails-recipebook.g.hatena.ne.jp/rrbk/20080525

2007-12-10

[][] RMagickで画像を扱う 10:54  RMagickで画像を扱う - Rails RecipeBookはてな版 を含むブックマーク はてなブックマーク -  RMagickで画像を扱う - Rails RecipeBookはてな版  RMagickで画像を扱う - Rails RecipeBookはてな版 のブックマークコメント

お知らせ

皆様から様々なご指摘をいただき、無事に書籍を出版することができました。

ありがとうございます。

Railsレシピブック 183の技

Railsレシピブック 183の技

このエントリの内容には、古いものや技術的な誤りが含まれている

可能性があります。説明やサンプルコードを見直したレシピがこの

「Ralsレシピブック」に載っていますので、ぜひご覧ください。

RMagickで画像を扱う

Webアプリケーションで必要となることの多い機能として画像のハンドリングが上げられます。本レシピでは、Rubyでよく利用される画像ライブラリであるRMagickを使って、画像のフォーマットを判別する方法と画像サイズを変更する方法、画像フォーマットの変換方法を紹介します。

RMagickとは

RMagickは高機能な画像処理ライブラリ/コマンド集であるImageMagickRubyから利用するためのライブラリです。ImageMagickでは画像の変換や特殊効果の適用をはじめ、画像に対して様々な処理を行うことができます。RMagickを使うと、Rubyスクリプト内からこのImageMagickを利用することができ、様々な画像処理をアプリケーションに組み込むことができます。詳細は下記のサイトを参照してください(英語)。

http://www.imagemagick.org/script/index.php

http://rubyforge.org/projects/rmagick

RMagickのインストール

RMagickをインストールするには、もととなるImageMagickをインストールし、それに利用してRMagickをコンパイルする、といった方法が一般的です。また、Windows環境ではコンパイル済みのバイナリも用意されています。

Windowsdeでバイナリパッケージをインストールする
  1. http://rubyforge.org/projects/rmagick より、rmagick-win32をダウンロードし、zipファイルを展開する
  2. ImageMagick-6.x.y-z-windows-dll.exeをダブルクリックし、ImageMagickのライブラリをインストールする(x.y.-zはバージョン番号)
  3. gemコマンドを利用して、zipアーカイブに含まれるRMagick-win32-1.x.y-mswin32.gemをインストールする(x.y.はバージョン番号)
 > gem install RMagick-win32-1.15.9-mswin32.gem
その他のシステムでインストールする

Mac OS XLinuxではaptやMacPortsといったパッケージ管理システムでImageMagickをインストールし、gemコマンドを利用してRMagickをインストールするのが簡単でしょう。例えばMac OS Xの場合は次のようになります。

  1. sudo port install imagemagick +darwin_8 +gs +mpeg +no_x11 +wmf
  2. sudo gem install rmagick

Mac OS Xの場合、事前にDevlopperToolsをインストールしておくことと、MacPortsでのインストール時に+gsを指定する必要があります。その他のOSでもほぼ同様の手順でインストールできるでしょう。

RMagickで画像を処理する

RMagickで画像を扱うには、まずプログラム内でgem 'rmagick'を宣言した後、画像を読み込んで各種操作を行います。画像を読み込むにはMagick::Image.from_blob(content)メソッドを利用します。

Magick::Image.from_blob()メソッドは、引数としてバイナリ文字列を受け取り、Magick::Imageインスタンスの配列を返します*1。通常はshift()して先頭の要素を取得すればよいでしょう。

require 'rubygems'
require 'RMagick' # RMagickのライブラリを読み込む。大文字小文字に注意

content = File.read("/path/to/picture.jpg")

img = Magick::Image.from_blob(content).shift

実際のRailsアプリケーションでは、アップロードされたデータを読み込んだりDBから取得したblobカラムを読み込むことが多いでしょう。

# <input name='image' type='file' />でアップロードされたデータを読み込む
img = Magick::Image.from_blob(params[:image]).shift
# entriesテーブルのimageカラムに格納されたデータを読み込む
entry = Entry.find(params[:id])
img = Magick::Image.from_blob(entry.image).shift

実際の画像への操作はこのMagick::Imageオブジェクトに対して行います。

画像のフォーマットを判別する

画像のフォーマットを判別するにはMagick::Image#format()メソッドを呼び出します。format()メソッドはそのが像のファイルタイプを文字列で返します。

img = Magick::Image.from_blob( File.read("/path/to/photo.jpg") ).shift
img.format => "JPEG"

img2 = Magick::Image.from_blob( File.read("/path/to/picture.png") ).shift
img2.format => "PNG"

画像ファイルでないものを読み込んだ場合、Magick::ImageMagickErrorやRuntimeErrorといった例外が発生します。これを利用してアップロードされたファイルが本当に画像であるかどうかを判別することもできます。

class Entry < ActiveRecord::Base
  def validates
    begin
      image = Magick::Image.from_blob(self.content)

      unelss %w(JPEG GIF PNG).member?(image)
        self.errors.add(:image, "format should be JPEG, GIF or PNG.")
      end
    rescue Magick::ImageMagickError, RuntimeError => ex
      self.errors.add(:image, "should be an image file.")
    end
  end
end

この例では、内容を画像として読み込む際に例外が発生すると、その内容が画像でないと判断し、エラーメッセージを追加しています。さらに画像が読み込めた場合にはそのフォーマットをチェックし、JPEG, GIF, PNGのどれかでない場合にも別のエラーを追加しています。

画像サイズを取得する

画像の縦横サイズを取得するには、それぞれMagick::Image#rows()、Magick::Image#columns()メソッドを呼び出します。

# 画像の高さを取得する
img.rows

# 画像の幅を取得する
img.columns
画像サイズを変更する

画像サイズを変更するには、Magick::Image#resize(new_width, new_height, filter=LanczosFilter, support=1.0)メソッドを指定します。第一引数にリサイズ後の幅、第二引数に高さを指定します。第三引数以降ではサイズ変更アルゴリズムを指定できますが、通常は省略できます。

img = Magick::Image.from_blob( File.read("/path/to/photo.jpg") ).shift

# 横400px, 縦600pxの画像オブジェクトを取得する
new_img = img.resize(400, 600) 

# resize()は新しいMagick::Imageオブジェクトを返す。
# 当然そのオブジェクトはto_blobなどで書き出せる
File.open("new_400x600.jpg","wb"){|f| f << new_img.to_blob }

また、第一引数のみを指定すると倍率を指定してサイズ変更することができます。

# 元の画像の80%にリサイズする
new_img = img.resize(0.8) 
処理した画像を書き出す、画像のフォーマットを変換する

読み込んだ画像に対し、Image::Magick.to_blobメソッドを呼び出すと、そのデータをバイナリ文字列として取得できます。この時、事前にフォーマットを指定すると画像フォーマットを変換することができます。

img = Magick::Image.from_blob( File.read("/path/to/photo.jpg") ).shift
img.format() => "JPEG"

# JPEG画像をPNGに変換する
img.format = "PNG"

# 元データをPNGに変換し、"foo.png"に書き出す
File.open("foo.png","wb"){|f| f << img.to_blob }

指定できるフォーマットには"JPEG"や"PNG"、"GIF"などがあります。詳細はMagick.formatsで定義されています。

Railsアプリケーションから使用する場合はto_blob()メソッドで得るバイナリ文字列をコントローラのsend_data()メソッドで送出する、といった使い方が多くなるでしょう。

クラス

  • Magick::Image

関連項目

*1:アニメーションGIFなど、複数の画像が一ファイルに格納されている場合を考慮しているためです

2007-10-26

[][] 複雑なルーティングを定義する 14:14  複雑なルーティングを定義する - Rails RecipeBookはてな版 を含むブックマーク はてなブックマーク -  複雑なルーティングを定義する - Rails RecipeBookはてな版  複雑なルーティングを定義する - Rails RecipeBookはてな版 のブックマークコメント

お知らせ

皆様から様々なご指摘をいただき、無事に書籍を出版することができました。ありがとうございます。

Railsレシピブック 183の技

Railsレシピブック 183の技

このエントリの内容には、古いものや技術的な誤りが含まれている可能性があります。説明やサンプルコードを見直したレシピがこの「Ralsレシピブック」に載っていますので、ぜひご覧ください。

複雑なルーティングを定義する

ActiveResourceに対応したルーティングを定義する」レシピではActiveResourceに対応した基本的なルーティングを定義する方法を説明しました。それに加えmap.resource()やmap.resourcesでオプションを設定することで、より複雑なルーティングも定義することができるようになります。

基本的なCRUDアクション以外へのルーティングを定義する

map.resouce()メソッドやmap.resources()メソドでは、自動的にindex, show, create, update, deleteの各アクションへのルーティングが定義されます。

さらにそれ以外のアクション(例えば人気エントリの一覧を返すEntriesController#hot()アクション)を定義したい場合には、リソース定義時にオプションを指定します。オプションを指定することで、各アクションへの名前付きrouteとルーティングが定義されます。

オプションオプション名をキー、アクション定義を値としたHash形式で指定します。アクション定義は、アクション名の配列、もしくはアクション名をキー、受け付けるHTTPメソッドを値としたHashで指定します。

受け付けるHTTPメソッドは、:get、:post、:put、:deleteと、HTTPメソッドを特定しない:anyをそれぞれ指定可能です。

文章で書くと複雑に聞こえますが、具体的には、以下のような指定が可能です。

  map.resources(:entries,
                :collection=>{:hot=>:get, :updated=>:get}, # hot_entriesとupdated_entriesを定義
                :member=>[:detail, :history], # detail_entryとhistory_entryを定義
                :new=>{:preview => :post} )   # preview_new_entryを定義、POSTのみ受け付ける

オプションについて、簡単に説明します。

collectionオプション

【アクション名】_【リソース名の複数形】という名前付きrouteと指定したアクションへのルーティング定義を生成します。collectionオプションでは、リソースのうちのある集合の名前を付ける、と考えると分かりやすいでしょう。

上記のように、{:hot=>:get, :updated=>:get}を指定した場合には、以下のような定義が生成されます。

          updated_entries GET    /entries/updated         {:action=>"updated", :controller=>"entries"}
formatted_updated_entries GET    /entries/updated.:format {:action=>"updated", :controller=>"entries"}
              hot_entries GET    /entries/hot             {:action=>"hot", :controller=>"entries"}
    formatted_hot_entries GET    /entries/hot.:format     {:action=>"hot", :controller=>"entries"}

それぞれの名前付きrouteとformat指定の名前付きrouteが生成されていることが分かります。

名前付きrouteを利用する場合は次のようになります。

hot_entries_path(:limit=>10)
# => /entries/hot?limit=10

formatted_updated_entries_url(:xml, :since=>"2007-10-20")
# => "http://www.example.com/entries/updated.xml?since=2007-10-20"
memberオプション

【アクション名】_【リソース名の単数形】という名前付きrouteと指定したアクションへのルーティング定義を生成します。memberオプションでは、特定のリソースについてのプロパティなどを設定する場合に使用します。

collectionと違い、IDを指定できるのが大きな違いです。

上記のように、[:detail, :history]を指定した場合には、以下のような定義が生成されます。

           detail_entry        /entries/:id/detail              {:action=>"detail", :controller=>"entries"}
 formatted_detail_entry        /entries/:id/detail.:format      {:action=>"detail", :controller=>"entries"}
          history_entry        /entries/:id/history             {:action=>"history", :controller=>"entries"}
formatted_history_entry        /entries/:id/history.:format     {:action=>"history", :controller=>"entries"}

HTTPメソッドは指定していため、どのHTTPメソッドであってもこのアクションへルーティングされます。

名前付きrouteを利用する場合はIDが必要です。

detail_entry_url(1)
# => "http://www.example.com/entries/1/detail"

formatted_history_entry_path(1, :json)
# => "/entries/1/history.json"
newオプション

【アクション名】_new_【リソース名の単数形】という名前付きrouteと、指定したアクションへのルーティング定義を生成します。memberオプションと似ていますが、こちらはまだ生成されていないリソース(IDが存在しないリソース)に関するアクションを定義しています。

上記のように、{:preview=>:post}を指定した場合には以下のような定義が生成されます。

          preview_new_entry POST   /entries/new/preview             {:action=>"preview", :controller=>"entries"}
formatted_preview_new_entry POST   /entries/new/preview.:format     {:action=>"preview", :controller=>"entries"}

名前付きrouteを利用する場合にはIDは不要です。実際には以下のようになります。

preview_new_entry_url(:expire_token=>Time.now.to_i)
# => "http://www.example.com/entries/new/preview?expire_token=1192856492"

ネストしたリソースへのルーティングを定義する

リソース間に関連がある場合などには、ネストしたURLを生成したくなります。たとえば、Blog has_many Entriesの場合には、/blogs/:blog_id/entries/:entry_idというURLが生成できると嬉しいのではないでしょうか。

そのような場合には、リソースの定義時にブロックを渡します。ネスト内のリソースはブロック引数に対して定義していきます。

map.resources :blogs do |blog|
  blog.resource :entries
end

すると、以下のようなルーティング定義が生成されます。なお、この定義では非常に多くのルーティングが出力されるため、フォーマット指定部分を省いています。実際にはformatted_【名前付きroute】という形で、フォーマット付きのルーティング定義も生成されることに注意してください。

$ rake routes | grep -v :format
(in /Users/moro/dev.build/myblog-2_0)
           blogs GET    /blogs                           {:action=>"index", :controller=>"blogs"}
                 POST   /blogs                           {:action=>"create", :controller=>"blogs"}
        new_blog GET    /blogs/new                       {:action=>"new", :controller=>"blogs"}
       edit_blog GET    /blogs/:id/edit                  {:action=>"edit", :controller=>"blogs"}
            blog GET    /blogs/:id                       {:action=>"show", :controller=>"blogs"}
                 PUT    /blogs/:id                       {:action=>"update", :controller=>"blogs"}
                 DELETE /blogs/:id                       {:action=>"destroy", :controller=>"blogs"}
    blog_entries GET    /blogs/:blog_id/entries          {:action=>"index", :controller=>"entries"}
                 POST   /blogs/:blog_id/entries          {:action=>"create", :controller=>"entries"}
  new_blog_entry GET    /blogs/:blog_id/entries/new      {:action=>"new", :controller=>"entries"}
 edit_blog_entry GET    /blogs/:blog_id/entries/:id/edit {:action=>"edit", :controller=>"entries"}
      blog_entry GET    /blogs/:blog_id/entries/:id      {:action=>"show", :controller=>"entries"}
                 PUT    /blogs/:blog_id/entries/:id      {:action=>"update", :controller=>"entries"}
                 DELETE /blogs/:blog_id/entries/:id      {:action=>"destroy", :controller=>"entries"}

ネストの外側のリソースであるblogsに関しては、「ActiveResourceに対応したルーティングを定義する」で説明した基本となるルーティングが定義されています。

ネストの内側のentriesについても、同様に基本となるCRUDのためのルーティングが定義されています。それに加え、URL中の/blogs/:blog_idが、パラメータとして取り扱われます。このように、ネストをしたルーティングの特徴は二点あります。

  • ネストの内側のリソースへのコントローラが呼び出される(本項前半のオプション指定とはここが異なります)。
  • 親リソース(ネストの外側のリソース)へのIDがパラメータとして取得できる

たとえば、/blogs/10/entries/12 というリクエストは{:blog_id=>10, :id=>12}というパラメータに変換され、EntriesControllerに渡されます。

名前付きrouteを利用する場合には、親リソースのIDも指定する必要があります。

blog_entries_url(10)
# => "http://www.example.com/blogs/10/entries"

blog_entry_url(10, 12)
# => "http://www.example.com/blogs/10/entries/12"

formatted_new_blog_entry_path(10, :json)
# => "/blogs/10/entries/new.json"

当然、ネストしたリソース定義と、オプション指定は組み合わせることも可能です。

map.resources :blogs do |blog|
  # generate named_route "preview_new_blog_entry"
  blog.resources :entries, :new => {:preview => :post}
end
preview_new_blog_entry_url(10)
# => "http://www.example.com/blogs/10/entries/new/preview"
ネストしたリソースをオプションで指定する

Rails 2.0以降では、ネストしたリソースをオプションで定義できるようになっています。

ネストしたリソースを定義するためのオプションには:has_manyと:has_oneがあり、それぞれブロック中でmap.resources、map.resourceを呼び出すのと同じ働きをします。

ブロック中での定義 オプションでの定義
map.resource has_one
map.resources has_many

つまり、

map.resources :blog do |blog|
  blog.resources(:entries)
  blog.resource(:custom_design)
end

は、

map.resources(:blog, :has_many=>:entries, :has_one=>:custom_design)

と同じルーティング定義を生成します。

その他のオプション

map.resource()メソッドやmap.resources()メソッドには、他にもいくつかのオプションを設定できます。

controller

そのリソースへのリクエストを受け付けるコントローラを指定します。

# ex) entries   GET  /entries   {:action=>"index", :controller=>"my_great_entry"}
map.resources(:entries, :controller=>"my_great_entry")
singular

リソースの単数形を指定します。map.resources()メソッドのみ指定可能で、単数形の名前を使う名前付きrouteを生成する際に使用されます。

# ex) dwarf   GET  /dwarow/   {:action=>"index", :controller=>"dwarows"}
map.resources(:dwarows, :singular=>"dwarf")
path_prefix

ルーティング定義中のURL部のプレフィックスを指定します。

# ex) entries GET /my_great/entries  {:action=>"index", :controller=>"my_great_entries"}
map.resources(:entries, :path_prefix=>"my_great")
name_prefix

名前付きrouteのプレフィックスとなります。

# ex) my_great_entries  GET  /entries/:id   {:action=>"show", :controller=>"entries"}
map.resources(:entries, :name_prefix=>"my_great")
conditions

通常のルーティング定義の:conditionsオプションと同じ働きをします。

requirements

通常のルーティング定義の:requirementsオプションと同じ働きをします。

クラス

関連項目

  • (受け側のコントローラ)

2007-10-19

[][] ActiveResourceに対応したルーティングを定義する 19:27  ActiveResourceに対応したルーティングを定義する - Rails RecipeBookはてな版 を含むブックマーク はてなブックマーク -  ActiveResourceに対応したルーティングを定義する - Rails RecipeBookはてな版  ActiveResourceに対応したルーティングを定義する - Rails RecipeBookはてな版 のブックマークコメント

お知らせ

皆様から様々なご指摘をいただき、無事に書籍を出版することができました。ありがとうございます。

Railsレシピブック 183の技

Railsレシピブック 183の技

このエントリの内容には、古いものや技術的な誤りが含まれている可能性があります。説明やサンプルコードを見直したレシピがこの「Ralsレシピブック」に載っていますので、ぜひご覧ください。

ActiveResourceに対応したルーティングを定義する

ActiveResouceでは、特定のURLにGET、POST、PUT、DELETEの各HTTPメソッドを用いてアクセスしてきます。API提供側となるコントローラでは、それらのHTTPメソッドとアクションを対応付けなければなりません。

このようなリクエストのHTTPメソッドとアクションの対応付けを行うために、ルーティングマップにresource、resourcesというメソッドが用意されています。config/routes.rbの中でこれらのメソッドを使うと、HTTPメソッドとコントローラのアクションの対応付けを簡単に行うことができるようになります。

複数のリソースに対するルーティングを定義する(map.resources)

最も基本となるルーティング定義メソッドは、複数のリソースに対するルーティングを定義するmap.resources(*entities, &block)メソッドです。このメソッドは複数、つまりIDを用いてリソース同士が識別される場合のルーティングを定義します。

ルーティング定義メソッドは、config/routes.rbの中でActionController::Routing::Routes.draw()メソッドのブロック引数、mapに対して呼び出します。

ActionController::Routing::Routes.draw do |map|
  map.resources :entries
  ...
end

Rails 2.0以降の場合、rake routesを実行すると定義されたルーティングの一覧を確認できます。1.x系の場合、この方法での一覧はできませんが定義自体は行われていますので安心してください。

 $ rake routes
             entries GET    /entries                    {:controller=>"entries", :action=>"index"}
   formatted_entries GET    /entries.:format            {:controller=>"entries", :action=>"index"}
                     POST   /entries                    {:controller=>"entries", :action=>"create"}
                     POST   /entries.:format            {:controller=>"entries", :action=>"create"}
           new_entry GET    /entries/new                {:controller=>"entries", :action=>"new"}
 formatted_new_entry GET    /entries/new.:format        {:controller=>"entries", :action=>"new"}
          edit_entry GET    /entries/:id/edit           {:controller=>"entries", :action=>"edit"}
formatted_edit_entry GET    /entries/:id/edit.:format   {:controller=>"entries", :action=>"edit"}
               entry GET    /entries/:id                {:controller=>"entries", :action=>"show"}
     formatted_entry GET    /entries/:id.:format        {:controller=>"entries", :action=>"show"}
                     PUT    /entries/:id                {:controller=>"entries", :action=>"update"}
                     PUT    /entries/:id.:format        {:controller=>"entries", :action=>"update"}
                     DELETE /entries/:id                {:controller=>"entries", :action=>"destroy"}
                     DELETE /entries/:id.:format        {:controller=>"entries", :action=>"destroy"}

たった一行定義するだけで大量のルーティングが生成されますが、これをゆっくり見ていきましょう。

formatと名前付きroute

まず、これらの各アクションには:formatを指定することができるようになっています。そのため生成されるパターンが単純に二倍となります。:formatには"xml"や"json"といったパラメータを指定できます。

また、一覧の左端に表示されている「entries」や「formatted_entries」は名前付きroutesと呼ばれています。コントローラやビューの中でURLを生成する際に、シンプルに記述することができます。

名前付きrouteは【名前】_pathや【名前】_urlという形式で使用します。

【単数形の名前】_path(params ={})

entries_path 
# => /entries

new_entry_url
# => http://example.com/entries/new

また、単数系の名前付きrouteは、第一引数にIDを指定します。複数形の名前付きrouteはリソースのコレクションへのルーティングをあらわし、単数系はIDで区別される単一のリソースへのルーティングである、と考えるとわかりやすいでしょう。

【複数形の名前】_path(id, params ={})

entry_path(1)
# => /entries/1

entry_path(1, :param_key=>"param_value", :one=>1)
# => /entries/1?param_key=param_value&one=1

formatted_【名前】という形式では引数にフォーマットを指定することでルーティング中の:formatオプションを指定できます。

formatted_entries_path(:xml)
# => /entries.xml

formatted_entry_path(1, :xml)
# => /entries.xml

formatted_entry_path(1, :xml, :param_key=>"param_value", :one=>1)
# => /entries/1.xml?param_key=param_value&one=1
リソースをCRUDするためのルーティング

フォーマットを除いたうち、特に重要なルーティングは以下のようなものがあります。

 entries GET    /entries      {:controller=>"entries", :action=>"index"}
         POST   /entries      {:controller=>"entries", :action=>"create"}
 entry   GET    /entries/:id  {:controller=>"entries", :action=>"show"}
         PUT    /entries/:id  {:controller=>"entries", :action=>"update"}
         DELETE /entries/:id  {:controller=>"entries", :action=>"destroy"}

一覧取得/検索(indexアクション)、一件取得(show)、作成(create)、更新(update)、削除(destroy)のためのアクションへのルーティングが生成されていることが確認できます。「ActiveResourceに対応したコントローラを作成するための規約を知る」レシピで説明している基本的なアクション群へのルーティングです。

Webブラウザ経由のアクセスに対応するためのルーティング

通常のWebアプリケーションでは、Webブラウザからフォームを使って情報を入力します。そのため、そのフォームを表示するためのアクションも必要となります。

  new_entry GET /entries/new              {:controller=>"entries", :action=>"new"}
 edit_entry GET /entries/:id/edit         {:controller=>"entries", :action=>"edit"}

新規作成フォームを表示するnewアクションと、更新のためのフォームを表示するeditアクションへのルーティングが定義されます。そのため、「map.resourcesでRESTful APIのためのルーティングを定義したあとでブラウザ用のルーティングを定義する」といった作業の重複は省かれています。

単一のリソースに対するルーティングを定義する(map.resouce)

単一のリソースに対するルーティングを定義するには、map.resource(*entities,&block)メソッドを使用します。ここでいう単一のリソースとはURL中のIDを含まないもののことを指します。これらのリソースは、システム全体で一意なリソースや、URL以外の識別方法(セッションやリクエスト中のuser_idなど)で識別されるものを指します。

ルーティング定義メソッドresource()をActionController::Routing::Routing.draw()メソッドのブロック引数に対して呼び出すと一意なりソースのためのルーティングが定義されます。

ActionController::Routing::Routes.draw do |map|
  map.resource :account
  ...
end

このように設定すると、以下のルーティング定義が生成されます。

 $ rake routes
                       POST   /account              {:controller=>"accounts", :action=>"create"}
                       POST   /account.:format      {:controller=>"accounts", :action=>"create"}
           new_account GET    /account/new          {:controller=>"accounts", :action=>"new"}
 formatted_new_account GET    /account/new.:format  {:controller=>"accounts", :action=>"new"}
          edit_account GET    /account/edit         {:controller=>"accounts", :action=>"edit"}
formatted_edit_account GET    /account/edit.:format {:controller=>"accounts", :action=>"edit"}
               account GET    /account              {:controller=>"accounts", :action=>"show"}
     formatted_account GET    /account.:format      {:controller=>"accounts", :action=>"show"}
                       PUT    /account              {:controller=>"accounts", :action=>"update"}
                       PUT    /account.:format      {:controller=>"accounts", :action=>"update"}
                       DELETE /account              {:controller=>"accounts", :action=>"destroy"}
                       DELETE /account.:format      {:controller=>"accounts", :action=>"destroy"}

複数のリソースを定義する場合と同様、それぞれ見ていきましょう。

formatと名前付きroute

単一のリソースのために生成される名前付きrouteは当然ながら単数系の名称になっています。また、こちらの場合はIDを指定することはできません。

【単数系の名前】_path(params={})と、【単数系の名前】_url(params={})がそれぞれ生成されます。

account_url
# => http://www.example.com/account

new_account_path(:login_type=>"open_id")
# => /account/new&login_type=open_id

フォーマットに関しては複数リソースの場合と同様です。

formatted_account_path(:xml)
# => /account.xml

formatted_edit_account_url(:xml)
# => http://www.example.com/account_edit.xml
リソースをCRUDするためのルーティング

リソースをCRUDするためのルーティングを見るとIDを指定する必要がない(指定できない)ことに気がつきます。

account GET    /account  {:controller=>"accounts", :action=>"show"}
        POST   /account  {:controller=>"accounts", :action=>"create"}
        PUT    /account  {:controller=>"accounts", :action=>"update"}
        DELETE /account  {:controller=>"accounts", :action=>"destroy"}

そのため、返却するリソースはシステムグローバルなリソースや、URL以外の情報から特定可能なものとなります。

たとえば、AccountsController#show()アクションでログインユーザのアカウント情報を返したい、という場合には以下のようになるでしょう。

class AccountsController < ApplicationController
  def show
    @account = User.find( session[:user_id] ).account
    respond_to do |format|
      ...
    end
  end
end

この例ではリソースを特定するための情報をparams[:id]ではなく、session[:user_id]から取得しています。

単一リソースへのルーティング定義では、このようにURL中のID以外の情報を用いて対象となるリソースを同定します。

Webブラウザ経由のアクセスに対応するためのルーティング

Webブラウザ経由でリソースを追加・更新するためのルーティングも定義されます。これも、更新(edit_account)の場合にもIDが含まれていないこと以外は、複数リソースへのアクセスと同じように考えられます。

 new_account GET    /account/new          {:controller=>"accounts", :action=>"new"}
edit_account GET    /account/edit         {:controller=>"accounts", :action=>"edit"}

クラス

関連項目

2007-10-01

[] assert_selectでHTMLを検証する 01:00  assert_selectでHTMLを検証する - Rails RecipeBookはてな版 を含むブックマーク はてなブックマーク -  assert_selectでHTMLを検証する - Rails RecipeBookはてな版  assert_selectでHTMLを検証する - Rails RecipeBookはてな版 のブックマークコメント

お知らせ

皆様から様々なご指摘をいただき、無事に書籍を出版することができました。ありがとうございます。

Railsレシピブック 183の技

Railsレシピブック 183の技

このエントリの内容には、古いものや技術的な誤りが含まれている可能性があります。説明やサンプルコードを見直したレシピがこの「Ralsレシピブック」に載っていますので、ぜひご覧ください。

assert_selectでHTMLを検証する

アプリケーションのビュー、つまり処理結果の画面をどのように検証するか、という問題はアプリケーション開発者にとって変わることのない課題でした。この問題は言語やフレームワークに限らず生じるもので、Railsでの開発を行う場合にも例外ではありません。

この課題に立ち向かうため、正規表現でHTMLを解析しその結果を検証する、というアプローチや、Javascriptを用いてブラウザを自動操作し検証する、というアプローチなどさまざまな優れた手法が生み出されてきましたし、今後もさらに発展していくでしょう。

そんな中、Rails 1.2以降ではCSSセレクタ形式でHTMLから要素をとりだし、それを検証するassert_selectというテストメソッドが追加されました。本項では、assert_selectの使用法やできることを紹介します。

assert_select とは

assert_selectはファンクショナルテストなどでレスポンスのHTMLを検証するためのメソッドです。

検証したい対象のエレメントをCSS2セレクタ形式で指定することができます。

assert_select の API

assert_selectは下記の二種類のAPIが存在します。

  • assert_select(selector, equality?, message?)
  • assert_select(element, selector, equality?, message?)
assert_select(selector, equality?, message?)

assert_selectで基本となる検証メソッドです。第一引数にはCSSセレクタを記述します。第二引数はそのセレクタの示すエレメントがどのような状態であるかを検証します。

true
指定されたセレクタのエレメントが、少なくとも一つ以上あることを検証します。第二引数省略時のデフォルトの検証です。
 # formエレメントが一つ以上存在することを検証します
 assert_select "form"
 assert_select "form", true
 # 指定したCSSセレクタ、つまり <body>の中に<div class="header">、
 # さらにその中に<ul class="menu">というタグが存在することを検証します。
 assert_select "body div.header ul.menu"
false
指定されたセレクタのエレメントが、一つも存在しないことを検証します。
 # formエレメントが存在しないことを検証します
 assert_select "form", false, "This page must contain no forms"
String/Regexp
指定されたセレクタのエレメントが含んでいる文字列が、Stringに一致するまたはRegexpにマッチすることを検証します。
 # titleエレメントに"Welcome"という文字列であることを検証します。
 assert_select "title", "Welcome" #=> <title>Welcome</title>
 assert_select "title", /Welcome/ #=> <title>Welcome to my app</title> or <title>Welcome</title> or ..

 # ? の部分が第二引数にマッチする要素が存在することを検証します
 # この例では<ol>の直下に/item-\d/にマッチするIDを持った<li>があることを検証します。
 assert_select "ol>li#?", /item-\d+/
 <ol>
   <li id="item_19" class="line_item">line item </li>
   <li id="item_20" class="line_item">line item </li>
   <li id="item_21" class="line_item">line item </li>
   <li id="item_22" class="line_item">line item </li>
 </ol>
Integer
指定されたセレクタのエレメントが、指定された個数存在することを検証します。
 # formエレメント内に4つのinputエレメントが存在することを検証します
 assert_select "form input", 4
Range
指定されたセレクタのエレメントの個数が、指定された範囲内であることを検証します。
 # idが"items"のulの中に、liエレメントが 1〜5個存在することを検証します。
 assert_select "ul#items li", (1..5)

さらに、第二引数をHashで渡すと、文字列や個数を検証する、ということを明示的に指定できます。一度の検証で個数と文字列の双方を検証する場合や、検証したい興味の内容を明示的に示す際に有効です。使用できるHashのキーは下記のものになります。

:text
エレメントが含んでいる文字列に対する検証であることを明示します。文字列または正規表現を指定します。
:html
エレメントが含んでいるHTMLに対する検証であることを明示します。文字列または正規表現を指定します。
:count
エレメントの個数に対する検証であることを明示します。Integerを指定します。
:minimum
エレメントの個数の最小値に対する検証であることを明示します。Integerを指定します。
:maximum
エレメントの個数の最大値に対する検証であることを明示します。Integerを指定します。

例えば、以下のように記述できます。

 # title エレメントの文字列が"Welcome"であることに加え、ひとつのみであることも検証しています。
 assert_select "title", {:count=>1, :text=>"Welcome"}
ネストした検証とassert_select(element, selector, equality?, message?)

assert_selectにはブロックを渡すこともできます。その場合は、指定されたセレクタにマッチしたエレメントの配列がブロック引数として渡されます。

このエレメントに対して検証を行いたい場合はassert_select(element, selector, equality?, message?)を用いて検証を行ないます。実例をもとに説明します。

 # ol > li にマッチした li 要素の配列がブロックに入っている
 assert_select "ol>li" do |elements|
   elements.each do |element|
     # each でそれぞれの li 要素に対して検証を行う。この場合は assert_select(element, selector, true) の省略形
	 assert_select element, "li"
   end
 end

さらに、ブロックの仮引数を省略すると、マッチしたエレメントの配列それぞれに対するassert_selectを記述できます。すなわち、以下のような省略記法を使うこともできます。

 # "ol>li"にマッチした要素は、それぞれ"li"エレメントであることを検証します。
 assert_select "ol>li" do
   assert_select "li"
 end

 # <form>の中にある<input>がかならず[name]属性を持っていることを検証します。
 assert_select "form input" do
   assert_select "[name=?]", /.+/  # Not empty
 end

 assert_select "form[action="hoge/create"] input" do
   assert_select "[name=item[title]]"
   assert_select "[name=item[started_at]]"
   assert_select "[name=item[ended_at]]"
 end

また、この他にも類似の検証メソッドとしてHTMLメールに対してその要素を検証するassert_select_email、XML内にエンコードされたHTML部分を検証するassert_select_encoded、RJSでinsertまたはupdateするHTMLを検証するassert_select_rjsも存在しますが、本項では基本となるassert_selectのみを紹介します。

クラス

関連項目

  • テストデータを用意する
  • ファンクショナルテストで使用できるテストメソッド
  • インテグレーションテストで使用できるテストメソッド