スクラッチでRails3に挑戦 Part 3(レコードを編集,更新してみる)

さて,前回の続きです.

Railsのログを見てみると,Webにアクセスする度に,つぎのようなメッセージが出てくる...

Started GET "/assets/application.css?body=1" for 127.0.0.1 at 2011-11-09 14:40:37 +0900
Served asset /application.css - 304 Not Modified (0ms)

Started GET "/assets/jquery_ujs.js?body=1" for 127.0.0.1 at 2011-11-09 14:40:37 +0900
Served asset /jquery_ujs.js - 304 Not Modified (0ms)

何かcacheの問題らしいのですが,このasset関係のログはうるさいので,とりあえずだまってもらうために,「config/emvironments/development.rb」を修正します.

[config/environments/development.rb]

   :
   # Expands the lines which load the assets
   config.assets.debug = false
   :

※修正しません(2013/03/11訂正)
→修正してしまうと,CSS(SCSS)ファイルの更新が反映されない場合があります.

さて,今回の本題です.

学生データを編集する edit アクションを定義したいと思います.URLで「http://〜/students/1234567890/edit」と指定したときに,idが「1234567890」の学生のレコードを編集するという画面を作ります.

まずは,ルーティング情報(「config/routes.rb」)の書き換えです.「edit」アクションにからむ部分を追加です.

[config/routes.rb]

Sample::Application.routes.draw do
  match 'students', :to  => 'students#index'
  match 'students/:id', :to => 'students#show'
  match 'students/:id/edit', :to  => 'students#edit'
  :
end

次に,ビュー(「app/views/students/edit.html.erb」)の追加です.とりあえず,適当にファイルを作っておきます.

[app/views/students/edit.html.erb]

<h1>学生情報の編集</h1>

<%= @hoge %>

最後に,コントローラ(「app/controllers/students_controller.rb)の修正です.

[app/controllers/students_controller.rb]

class StudentsController < ApplicationController
  def index
    @students = Student.all
  end

  def show
    @student = Student.find(params[:id])
  end

  def edit
    @hoge = "#{params[:id]}の編集"
  end
end

これで一応,URLで「http://〜/students/1234567890/edit」でアクセスでき,editビューテンプレートの結果が表示されると思います.

HTMLのフォームであるinputタグ等を使用するにはFormHelperあるいはFormTagHelperを使用します.

では,ここでFormHelperクラスの「text_field」と,FormTagHelperクラスの「text_field_tag」の例を見てみます.

[app/views/students/edit.html.erb]

<h1>学生情報の編集</h1>

<%= @hoge %><br />

text_filedの例: <%= text_field :student, :name, :value => "No name!" %><br />
text_field_tagの例: <%= text_field_tag :hogehoge, "No name!" %>

結果はこうなります.

どちらも同じようなテキスト入力フォームを表示させ,中にはデフォルトの文字列である「No Name!」が入っています.見かけは同じですが,大きく違います.それは,ソースを表示してみると分かります.

[app/views/students/edit.html.erbのソース(一部)]

  :
text_filedの例: <input id="student_name" name="student[name]" size="30" type="text" value="No name!" /><br />
text_field_tagの例: <input id="hogehoge" name="hogehoge" type="text" value="No name!" />
  :

フォームのデータを次のアクションに送信する時,上記text_fieldの例の場合は,params[:student][:name] に「No name!」が,text_field_tagの例の場合は,params[:hogehoge] に「No name!」がそれぞれ渡されます.整理すると

text_field
モデルに基づいたテキストフィールドを作成し,paramsパラメータを二次元ハッシュで構成しデータを格納する.
text_field_tag
モデルに関係のない独立したテキストフィールドを作成し,paramsパラメータを一次元ハッシュで構成しデータを格納する.

と言えます...

基本的に,text_fieldは,form_forの中,text_field_tagは,form_tagの中でそれぞれ使用されます.

例として,次のように使用されます.

<%= form_for @student do |f| %>
  <%= f.text_field :name, :value => "No name!" %>
<% end %>

とにかく,モデルの値を編集するような場合は,FormHelperクラス(text_field,form_forなど)を利用したほうが無難でしょう.ということで,form_forを使用して,@studentオブジェクトの中身をそれぞれtext_fieldを使って表示してみましょう.

[app/views/students/edit.html.erb]

<h1>学生情報の編集</h1>

<ul>
  <%= form_for @student do |f| %>
    <li>ID: <%= f.text_field :id %></li>
    <li>氏名: <%= f.text_field :name %></li>
    <li>E-mail: <%= f.text_field :email %></li>
  <% end %>
</ul>

あれれ...エラーが出ましたよ

undefined method `student_path' for #<#<Class:0x00000102be6df8>:0x00000102b551f0>

「student_path」が定義されていないって...

確かに,ルートパスには,「students_path」はあるけれど,「student_path」は無いです.

$ rake routes
students  /students(.:format)          {:action=>"index", :controller=>"students"}
          /students/:id(.:format)      {:controller=>"students", :action=>"show"}
          /students/:id/edit(.:format) {:controller=>"students", :action=>"edit"}

結局,簡潔記載ではなく,丁寧にurlパラメータを付けたら上手くいきました.

<%= form_for @student, url => students_path do |f| %>

ソースを見てみると,上記formタグの部分は

<form accept-charset="UTF-8" action="/students" class="edit_student" id="edit_student_1234567890" method="post">

となっています.つまり,「action=”/students”」のところが,「:url => students_path」から来たものというのが分かります.確かに「action=”/student”」ではエラーになるはず...

でも,schaffoldを使用したときは,「<%= form_for @student do |f| %>」という記述で良かったはず...単にオブジェクト名「@student」と書いたら,「student_path」がactionのURLになってしまうんだ.

じゃあ,「student_path」を用意すれば良いのね,ということになりますよね.

[config/routes.rb]

Sample::Application.routes.draw do
  match 'students', :to => 'students#index'
  match 'students/:id', :to => 'students#show', :as => 'student'
  match 'students/:id/edit', :to => 'students#edit'
  :
end

「:as => ‘student’」というオプションを付けていますが,これは,URL「http://〜/students/1234567890」というパスを,別名,student_path で指定する,ということになります.「rake routes」で確認.

$ rake routes
students  /students(.:format)          {:action=>"index", :controller=>"students"}
student   /students/:id(.:format)      {:controller=>"students", :action=>"show"}
          /students/:id/edit(.:format) {:controller=>"students", :action=>"edit"}

これで,urlパラメータを付加しなくても大丈夫となりました.

<%= form_for @student do |f| %>

ソースを確認してみます,

<form accept-charset="UTF-8" action="/students/1234567890" class="edit_student" id="edit_student_1234567890" method="post">

変化したところは,「action=”/students/1234567890″」のところです.

一人の学生情報を修正して,いざ更新となったときどのページにアクセスしにいくかをこの「action」に記述することになろうかと思います.従って,

<%= form_for @student :url => ‘students’ do |f| %>の時
「action=”/students”」ということで,学生一覧表示のページ(index)へ
<%= form_for @student :url => ‘student’ do |f| %>(この場合 urlパラメータは省略可能)の時
「action=”/students/1234567890″」ということで,編集対象となった一人の学生情報表示のページ(show)へ

更新ボタン(submitボタン)を追加してみます.

[app/views/students/edit.html.erb]

<h1>学生情報の編集</h1>

<%= form_for @student do |f| %>
<ul>
    <li>ID: <%= f.text_field :id %></li>
    <li>氏名: <%= f.text_field :name %></li>
    <li>E-mail: <%= f.text_field :email %></li>
</ul>
<%= f.submit '更新' %>
<% end %>

表示はこんなの.

ということで更新してみます.

すると各入力データは

  • params[:student][:id] = ‘1234567890’
  • params[:student][:name] = Taro カゴシマ
  • params[:student][:email] = taro@kago.jp

と,paramsパラメータの二次元ハッシュとして格納されているのが分かります.

あと,params[:_method]が「put」になっています.いわゆるHTMLのFORMタグのmethodは「post」になっていますが,Rails内部で,「_method」は「put」ということで設定されています.これは,フォームのソースを見れば分かります.

<input name="_method" type="hidden" value="put" />

これはいわゆるRESTfulな関係から言えば,「put」が「update」に対応ということになるのでしょう.でも,現在は結果として更新されていません.普通に学生情報表示のページが出ています.

それでは,データを更新させるために「update」アクションを定義しましょう.

[app/controllers/students_controller.rb]

# -*- coding: utf-8 -*-
class StudentsController < ApplicationController
  def index
    @students = Student.all
  end

  def show
    @student = Student.find(params[:id])
  end

  def edit
    @student = Student.find(params[:id])
  end

  def update
    @student = Student.find(params[:id])
    @student.update_attributes(params[:student])
    redirect_to @student
  end

end

まずは,受け取ったパラメータ params[:id]より変更対象のレコードオブジェクトを特定します.その後,「update_attributes」メソッドで更新するのですが,引数には,params[:student] を指定します.これは二次元ハッシュで,id,name,emailのフィールド用データを含んでいるんでしたね.

そして,ルーティング情報にも「update」アクションの項目を追加しておきます.(※順番注意!)

[config/routes.rb]

Sample::Application.routes.draw do
  match 'students', :to  => 'students#index'
  match 'students/:id', :to => 'students#show', :as => 'student'
  match 'students/:id', :to => 'students#update'
  match 'students/:id/edit', :to  => 'students#edit'
  :
end

これで,実行してみますと,更新はされず元の学生情報を表示してしまいます.そこで,routes.rbの順番を書き換えます.

[config/routes.rb]

Sample::Application.routes.draw do
  match 'students', :to  => 'students#index'
  match 'students/:id', :to => 'students#update'
  match 'students/:id', :to => 'students#show', :as => 'student'
  match 'students/:id/edit', :to  => 'students#edit'
  :
end

すると,Webブラウザの画面にはSafariの場合ですが,「多くのリダイレクトが発生しています」ということでエラー画面が出てしまいます.

ログを見ますと,一応UPDATEしてCOMMITしているみたいです.しかし,COMMIT→リダイレクト,が連続して発生しているようです.

[Railsのログ]

Started PUT "/students/1234567890" for 127.0.0.1 at 2011-11-10 13:37:00 +0900
  Processing by StudentsController#update as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"RuWgfdKyuie3E4lJmFrOL4peA9IG6rYgr+fNtBNMbuA=",
 "student"=>{"id"=>"1234567890", "name"=>"Taro カゴシマ", "email"=>"taro@kago.jp"}, "commit"=>"更新",
 "id"=>"1234567890"}
  Student Load (0.3ms)  SELECT `students`.* FROM `students` WHERE `students`.`id` = '1234567890' LIMIT 1
   (0.1ms)  BEGIN
WARNING: Can't mass-assign protected attributes: id
   (0.3ms)  UPDATE `students` SET `name` = 'Taro カゴシマ' WHERE `students`.`id` = '1234567890'
   (0.1ms)  COMMIT
Redirected to http://localhost:3000/students/1234567890
Completed 302 Found in 55ms
 :

要は,更新してまた,URL「http://〜/students/1234567890」にリダイレクトするときに,またupdateアクションを実行してしまうということなのです.これは,routes.rb の記述順番,即ちルーティング情報の参照順番によるものです.

updateアクション,showアクション,両方が「/students/:id」がパスとなっているので,現在は先のupdateの方が優先になってしまうのです.では,どうやって振り分けたら良いのでしょうか...

その鍵は,HTTPメソッドです.showアクションの場合は「get」,updateアクションの場合は「put」でそれぞれリクエストが来たときに振り分けられるようにすればいいのです.

[config/routes.rb]

Sample::Application.routes.draw do
  match 'students', :to  => 'students#index'
  match 'students/:id', :to => 'students#show', :as => 'student', :via => :get
  match 'students/:id', :to => 'students#update', :via => :put
  match 'students/:id/edit', :to  => 'students#edit'
   :

「:via」パラメータで「:get」や「:put」を指定すれば良さそうです.

$ rake routes
students     /students(.:format)          {:action=>"index", :controller=>"students"}
 student GET /students/:id(.:format)      {:controller=>"students", :action=>"show"}
         PUT /students/:id(.:format)      {:controller=>"students", :action=>"update"}
             /students/:id/edit(.:format) {:controller=>"students", :action=>"edit"}

再度実行を確認してみます.

問題なくできました!

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中