もくじ
スポンサーリンク
Scaffoldingとは
実はRailsには、リソースベースのルーティング及び、CRUD機能を備えたアプリケーションを一瞬で作ってしまう機能がついています。
その名もScaffolding。スキャフォールディングと読みます。
あくまで汎用的かつシンプルな機能しかないので、アプリのベースとして使ったり、あるいは管理用のシステムとして使ったりするようです。
とにかく簡単に作れるので、一度やってみましょう。
スポンサーリンク
Scaffoldingでアプリを作る
Cloud9のコンソールを開いて下さい。
workspaceディレクトリに移動します。
1 | $ cd ~/workspace |
例として、書籍についての情報の管理をするアプリを作ります。つまりリソースは書籍(book)です。
まずは、rails newコマンドで、Railsアプリのベースとなるディレクトリ構成を作ります。book-listという名前にしておきます。
1 | $ rails new book-list |
↑phone-bookと並んで出来ましたね。
book-listディレクトリが出来たら、そっちに移動します。
1 | $ cd book-list |
さあ、ここから瞬殺です。
scaffoldコマンドを使えば、コントローラーも、ルーターも、モデルも、ビューも何もかも一気に作ってしまうことが出来ます。
パラメータとして必要なのは、管理したいリソースの名前と、そのリソースが持つフィールドについての情報のみです。
1 | $ rails g scaffold リソース名 フィールド名:型 フィールド名:型 ... |
リソースはbooks(リソース名は複数形で指定する)。
各リソースが持つフィールドは、タイトル、作者、価格の3つとします。
フィールド | 種類 |
---|---|
title | string |
author | string |
price | integer |
なので、以下のコマンドを打ちます。
1 | $ rails g scaffold books title:string author:string price:integer |
たったこれだけで、bookリソースを管理するための、コントローラー、ルーター、モデル、ビュー、マイグレーションファイルといったファイル群がそれぞれ既に連携して機能する状態で出来上がります。
超早ワザです。
ただし、一つだけ手動でしなければならないことがあります。マイグレーションファイルの実行です。
とは言え、以下のコマンドを打つだけです。
1 | $ rake db:migrate |
これで完成です。
サーバーを立ち上げて、触ってみて下さい。
1 | $ rails s -b $IP -p $PORT |
素っ気ない画面ではありますが、本の情報を入力、管理できるシステムが出来上がっているのが分かると思います。
Scaffoldingで出来たファイルの中身
これまでの連載(初めてのRuby on Rails入門)で、電話帳アプリ(phone-book)を完成させる為に、コントローラーやビューをちまちま書いてきましたが、今、一瞬で作ったbook-listの方が、多くのソースコードが書かれています。全自動でです。
嬉しいやらアホらしいやら。
これらの自動的に作られた各ファイルを見れば、リソースベースの基本的な処理の流れがよく分かると思うので、ぜひ一通り目を通して見て下さい。
この連載で説明しきれていない部分について、以下で説明しておきます。
before_actionメソッド
booksコントローラー(/app/controllers/books_controller.rb)を開いて下さい。
1 2 3 4 5 6 7 8 9 10 11 12 | class BooksController < ApplicationController before_action :set_book, only: [:show, :edit, :update, :destroy] # GET /books # GET /books.json def index @books = Book.all end 以下省略 ・ ・ |
2行目のbefore_actionメソッドについて説明します。
このメソッドは、その名の通り、各アクションの直前に実行したい処理を指定するメソッドです。全てのアクションに共通のフィルターを付けるような感じです。
第一引数で、各アクションの直前に実行したいメソッドを指定します。ここではset_bookメソッドになっています。
※コロンの意味が分からない人はRailsでよく出てくるコロン(:)は、シンボルって言うやつらしいを読んでみてください。
set_bookメソッドというのは、このコントローラーで定義されているprivateメソッドの1つです。下の方に書かれています。
1 2 3 4 5 6 7 8 9 10 11 12 13 | ・ ・ 省略 private # Use callbacks to share common setup or constraints between actions. def set_book @book = Book.find(params[:id]) end 省略 ・ ・ |
↑リクエストパラメータに含まれるIDを持つモデルオブジェクトをテンプレート変数に格納する処理を行っています。
※この連載では同様の処理をfind_member_by_idという名前のメソッドで定義していました。
この処理が必要なアクションは4つ(show, edit, update, destroy)もあります。7つのうち4つのアクションでこのset_bookメソッドを呼ぶ必要があるわけです。
Railsではそういうコードの重複を嫌う文化があります。そこで活躍するのがbefore_actionメソッドです。
before_actionメソッドを使えば、何回も同じメソッドを書く必要はありません。
ただし、あくまでset_bookメソッドを実行するアクションはshow, edit, update, destroyの4つです。残りの3つでは実行しませんので、第二引数で実行するアクションを指定します。
1 | before_action :set_book, only: [:show, :edit, :update, :destroy] |
もし第二引数に何も渡さなければ、全てのアクションにおいてset_bookメソッドが実行されることになります。
ちなみに、よく似たフィルターメソッド的なやつで、実行するタイミングが違うメソッドもいくつか用意されています。
after_actionや、around_actionなど、いろいろありますので、気になる方はググって見て下さい。
ただ、こういったフィルターメソッドを使った書き方は、各アクションで行われる処理内容があちこちに分散することになるので、各アクションで何を実行しているのかが読みにくくなる印象です。慣れればそうでもないのかな?
個人的には多少コードが重複してしまっても、各アクションで実行されることは各アクション(メソッド)の定義部分を見れば分かるように書きたい派です。
respond_toメソッド
Scaffoldingによって自動生成されたbooksコントローラーを見て、一番何だこれ?と思うのは、respond_toメソッドだと思います。
createアクションはこんな風になってます。↓
1 2 3 4 5 6 7 8 9 10 11 12 13 | def create @book = Book.new(book_params) respond_to do |format| if @book.save format.html { redirect_to @book, notice: 'Book was successfully created.' } format.json { render :show, status: :created, location: @book } else format.html { render :new } format.json { render json: @book.errors, status: :unprocessable_entity } end end end |
respond_toメソッドは指定されたフォーマットに応じて、それぞれレスポンスを振り分ける為のメソッドです。
フォーマットっていうのは、ブラウザで普通にアクセスしている場合はもちろんhtmlです。返ってきたhtml形式のレスポンスをブラウザで表示しているわけですね。
もしjson形式でのレスポンスが欲しい場合は、
↑こんな風にリクエストを送ることもできます。jsonも普通にブラウザで見ることはできますが、普通はわざわざjson形式で見ようとは思わないので、API的な使い方をする為に用意してるんだと思います。
要するに、respond_toメソッドを使えば、こんな風に簡単にフォーマットごとのレスポンス処理を変えられるということです。
1 2 3 4 5 6 | respond_to do |format| if @book.save format.html { html形式でレスポンスを返す処理 } format.json { json形式でレスポンスを返す処理 } end end |
フォーマットごとにビューを変える
コントローラーでは上述のようにrespond_toメソッドによって、フォーマットごとに処理を分けられるわけですが、単純にフォーマットごとにビューを変えたい場合は、簡単です。
viewsディレクトリに同名のファイルを置いておくだけです。
/app/views/booksディレクトリ内に、index.json.jbuilderとshow.json.jbuilderというファイルがあるのが確認できると思います。
html形式のレスポンスを返す場合は、アクション名.html.erbがビューとして使われ、
json形式のレスポンスを返す場合は、アクション名.json.jbuilderがビューとして使われます。
.html.erbというのは、erbを使ってhtmlを出力する為のファイル、
.json.jbuilderとは、jBuilderを使ってjsonを出力する為のファイルだと言えます。
※erbというのはhtmlの中に<%= %>を使ってRubyスクリプトを挿入することによって動的にhtmlを生成する為のテンプレート。
※jBuilderとはjsonデータを生成する為のテンプレート。
変幻自在のモデルオブジェクト
上記でも出てきたbooksコントローラーのcreateアクションに、ちょっと??な箇所があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 | def create @book = Book.new(book_params) respond_to do |format| if @book.save format.html { redirect_to @book, notice: 'Book was successfully created.' } format.json { render :show, status: :created, location: @book } else format.html { render :new } format.json { render json: @book.errors, status: :unprocessable_entity } end end end |
6行目、新しいbookテーブルの書き込みに成功した後、html形式でのレスポンスを返すところです。
1 | redirect_to @book, notice: 'Book was successfully created.' |
見ての通り、redirect_toメソッドでリダイレクト先を設定し、flash[:notice]に新規作成に成功した旨を通知するメッセージを仕込んでいます。
しかしよく見ると、リダイレクト先に指定されているのは、@bookです。
@bookってのはBookモデルオブジェクトです。Bookオブジェクトにリダイレクトするってどういうこと・・??となると思います。
これは意味で考えると分かりやすいです。@bookにリダイレクトするってことは、要はその@bookについての情報を表示するページへリダイレクトするってことです。
つまり、ルーティングで言うところの、GETメソッドによるbooks/:id、コントローラーで言うところのshowアクションによって表示されるページです。
結果的に、以下のようにurlを指定するのと同じです。
1 | redirect_to "/books/#{@book.id}" |
要するに、redirect_toメソッドの引数にモデルオブジェクトを渡すと、それがパスを表す文字列(/books/1など)に変換されるわけです。
その際、内部的にはヘルパーメソッドであるbook_pathメソッドが一枚噛んでいるようです。
book_pathメソッドは、引数に渡された:idを使って/books/:idというパス(文字列)を返すメソッドです。
つまり、
1 | redirect_to @book |
は、内部的にはこういう風に処理されているようです。↓
1 | redirect_to book_path(@book) |
book_pathなどのヘルパーメソッドは、ルーターにおいてresourcesメソッドを使ってリソースフルなルーティングを生成した結果、使えるようになるものです。
なので、resources(resource)を使っていない場合は、モデルオブジェクトからパスを生成することは出来ません。
※undefined method `book_path’ というエラーが吐かれます。
そして、ちょっと混乱するかもしれませんが、もう一つ面白いのがあります。
1 | redirect_to book_path(@book.id) |
1 | redirect_to book_path(@book) |
↑これも全く同じなんです。book_pathの引数に@bookを渡しているのですが、この場合、@bookは、そのIDを表していることになります。
モデルオブジェクトをメソッドの引数に渡した場合、showページのpathとして処理されたり、そのオブジェクトのIDとして処理されたり、と非常に柔軟に振る舞うわけです。
Railsは直感的にコーディングできるように裏でいろいろしてくれているんだと思います。
コントローラーだけでなく、ビューでもこの仕組みが使われています。
index.html.erbを見てみましょう。
1 2 3 4 5 6 7 8 9 10 | <% @books.each do |book| %> <tr> <td><%= book.title %></td> <td><%= book.author %></td> <td><%= book.price %></td> <td><%= link_to 'Show', book %></td> <td><%= link_to 'Edit', edit_book_path(book) %></td> <td><%= link_to 'Destroy', book, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> <% end %> |
↑6行目のbook(モデルオブジェクト)は、そのshowページへのパスとして扱われ、7行目のbookはそのIDとして扱われています。
redirect_toメソッドだけでなく、link_toメソッドでも同様に、引数にモデルオブジェクトを渡すことができます。
モデルオブジェクトからパスを生成するのは、パス(url)を指定するメソッドに共通の仕様です。