Ruby on Railsで電話帳アプリを作っています。
前回までで、電話帳リストの一覧画面と新規登録画面を作りました。
今回は、既に登録しているデータを編集する画面、及びその機能を作っていきます。
スポンサーリンク
編集画面のルーティングを確認する
まずはルーティングを確認しましょう。
コンソールにて、カレントディレクトリをアプリのルートにした状態で、
1 | $rails routes |
とコマンドを打つと、コンソールにルーティングの一覧が表示されます。
※$rake routesでも可
1 2 3 4 5 6 7 8 9 | Prefix Verb URI Pattern Controller#Action members GET /members(.:format) members#index POST /members(.:format) members#create new_member GET /members/new(.:format) members#new edit_member GET /members/:id/edit(.:format) members#edit member GET /members/:id(.:format) members#show PATCH /members/:id(.:format) members#update PUT /members/:id(.:format) members#update DELETE /members/:id(.:format) members#destroy |
5行目が編集画面に割り当てられているルーティングです。
urlは、
http://○○○○/members/:id/edit
となっています。
:idというところに、編集したいリソースのidが入ります。例えば、http://○○○○/members/1/editで、「ID番号1のmembersリソースを編集したい」という意味のリクエストになります。
これから、そのリクエストに応じた編集画面を表示できるように作っていきましょう。
ルーティングを確認すれば分かるように、/members/:id/editというリクエストに対する処理を定義するのが、membersコントローラーのeditアクションです。
スポンサーリンク
editアクションを定義する
membersコントローラー内にeditアクションを追加しましょう。
/app/controllers/members_controller.rbを開いて下さい。
以下のように、editアクションを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class MembersController < ApplicationController def index @members = Member.all end def new @member = Member.new end def create @member = Member.new(params.require(:member).permit(:name, :yomi, :phone)) @member.save redirect_to members_path end def edit end end |
前回作った新規登録画面なら空っぽの入力フォームでいいですが、編集画面には、当然、既に登録されているデータを表示する必要があります。
↑こんな感じです。
その為には、リクエストに含まれるIDを持つリソースのデータをモデルに用意させる必要があります。
例えば、ID番号1のリソースのデータをデータベースから引っ張ってくるには、以下のようにします。
1 2 3 | def edit @member = Member.find(1) end |
Memberクラスのクラスメソッドであるfindメソッドの引数にそのID番号を渡すだけです。これでID番号1のリソース情報を携えたMemberオブジェクトが出来上がり、それを@memberに代入しています。
このテンプレート変数@memberをビューから参照することで、そのデータを使って、編集画面を作るわけですが、当然ながらリクエストに含まれるIDに対して動的にデータを用意しなければなりません。
そのためにはリクエストからidを取得して、処理する必要があります。
とりあえず、この状態で、http://○○○○/members/1/editにアクセスしてみると、
テストサーバーを立ち上げるコマンド。
1 | $ rails s -b $IP -p $PORT |
まだeditのビューが未定義なので、このようブラウザの表示はエラーになるのですが、
Cloud9のコンソールを見てみると、このリクエストに付随して送られているパラメータを確認することができます。
1 2 3 4 5 6 | Started GET "/members/1/edit" for 10.240.0.33 at 2016-12-27 05:36:47 +0000 Cannot render console from 10.240.0.33! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255 Processing by MembersController#edit as HTML Parameters: {"id"=>"1"} nil Completed 406 Not Acceptable in 20ms (ActiveRecord: 0.0ms) |
↑このように、
/members/1/editへのリクエストには、
{“id”=>”1”}
というパラメータが付随しているのが分かります。
このパラメータを、コントローラーにて取得する為のメソッドが、前回も説明したparamsメソッドです。
paramsメソッドにより取得したidをfindメソッドの引数に渡すことで動的な処理が可能になります。
1 2 3 | def edit @member = Member.find(params[:id]) end |
paramsメソッドは送られてくるパラメータを、連想配列の形で取得します。
例えば、{“id”=>”1”}というパラメータが送られている場合、paramsメソッドによって以下のように連想配列を取得します。
1 2 3 | parameter = params p parameter #=> {"id"=>"1"} |
その連想配列のキーを指定することで得られるのが、その値です。
1 2 3 | parameter = params p parameter[:id] #=> "1" |
変数を用意せず、paramsメソッドによって取得した連想配列に対し、そのままキーを指定しているのが以下の記述です。同じことです。
1 2 | p params[:id] #=> "1" |
これで、
http://○○○○/members/:id/editというリクエストに対して、その:idによって動的にデータを用意する仕組みができました。
1 2 3 | def edit @member = Member.find(params[:id]) end |
@memberというテンプレート変数にそのデータを代入してあるので、あとは、そのデータを使って、編集画面を作ります。
編集画面のビューを作る
では編集画面のビューを作っていきましょう。
/app/views/membersディレクトリ内に、edit.html.erbというファイルを新規作成して下さい。
このedit.html.erbの中で修正画面のビューを定義するわけですが、修正画面のビューでは、登録した情報を修正する為のフォームを表示します。
このフォーム自体は、前回作った新規登録フォームと全く同じです。ただ、入力欄が空っぽなのか、あらかじめデータが表示されているかの違いです。
実は、こういう場合、新規登録画面のビュー(new.html.erb)をそのままを使いまわすことができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <%= form_for @member do |f| %> <p> <%= f.label(:name, "名前") %> <%= f.text_field :name %> </p> <p> <%= f.label :yomi, "ヨミガナ" %> <%= f.text_field :yomi %> </p> <p> <%= f.label :phone, "電話番号" %> <%= f.text_field :phone %> </p> <%= f.submit "登録" %> <% end %> <%= link_to "戻る", members_path %> |
このform部分(戻るリンク以外)をそのまま使いまわします。
と言っても単にコピペするだけでは芸がありません。というかコピペでしてしまうと、今後もし、フォームに関して何らかの変更があった際などに修正するのが面倒です。
なので、フォーム部分を1つの部品として作り、それをnew.html.erbからも、edit.html.erbからも読み込んで使うようにします。
まずは、そのフォーム部分を、部分テンプレートとして作りましょう。
/app/views/membersディレクトリに、_form.html.erbというファイルを新規作成して下さい。
この_form.html.erbに、new.html.erbのform部分を全てコピペします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <%= form_for @member do |f| %> <p> <%= f.label(:name, "名前") %> <%= f.text_field :name %> </p> <p> <%= f.label :yomi, "ヨミガナ" %> <%= f.text_field :yomi %> </p> <p> <%= f.label :phone, "電話番号" %> <%= f.text_field :phone %> </p> <%= f.submit "登録" %> <% end %> |
この部分テンプレート(_form.html.erb)をビューの中で読み込むには、renderメソッドを使います。
new.html.erbの中でrenderメソッドを使って、_form.html.erbを読み込みます。
※新規登録画面だと分かるように、「新規登録」というヘッダーを付けておきましょう。
1 2 3 4 5 | <h2>新規登録</h2> <%= render "form" %> <%= link_to "戻る", members_path %> |
edit.html.erbの中でも、_form.html.erbを読み込みます。
※こちらは「修正」というヘッダーを付けておきます。
1 2 3 4 5 | <h2>修正</h2> <%= render "form" %> <%= link_to "戻る", members_path %> |
これで、new.html.erbにも、edit.html.erbにも、_form.html.erbに書いてあるフォームが表示されることになります。
部分テンプレート用のerbファイルは、それと分かるようにファイル名の一文字目をアンダースコアにする必要があります。
○ _form.html.erb
× form.html.erb
そして、他のビューから部分テンプレートを読み込む際には、アンダースコアを省いたファイル名を指定します。
○ <%= render "form" %>
× <%= render "_form" %>
ただし、全く同じもの(_form.html.erb)を読み込んでいるのにも関わらず、new.html.erbでは空っぽの入力フォームが表示され、edit.html.erbではあらかじめ入力欄に然るべきデータが入った状態の入力フォームが表示されます。
なぜそんなことになるのか?
_form.html.erbをもう一度よく見て下さい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <%= form_for @member do |f| %> <p> <%= f.label(:name, "名前") %> <%= f.text_field :name %> </p> <p> <%= f.label :yomi, "ヨミガナ" %> <%= f.text_field :yomi %> </p> <p> <%= f.label :phone, "電話番号" %> <%= f.text_field :phone %> </p> <%= f.submit "登録" %> <% end %> |
この入力フォームには、form_forメソッドの引数に渡したモデルオブジェクト(Memberオブジェクト)が持つデータがそれぞれの入力欄に表示されるようになっています。
新規作成画面(newアクション)の場合、コントローラーにて空っぽのMemberオブジェクトを用意した上で、@memberに仕込んでいる為、それぞれの入力欄は空っぽになります。
1 2 3 | def new @member = Member.new end |
編集画面(editアクション)の場合は、コントローラーにて、指定されたIDのMemberオブジェクトを@memberに代入している為、それぞれの入力欄にはそのMemberオブジェクトが持つパラメータが入った状態で表示されます。
1 2 3 | def edit @member = Member.find(params[:id]) end |
ビューに関しては全く同じソースコードでも、コントローラーが用意する値(テンプレート変数)によって実際、表示されるHTMLは変わってくるわけです。
フォームの送信先
さて、新規登録画面も、編集画面も_form.html.erbを読み込むことで、入力フォームを表示することができました。
実は、それぞれの入力フォームには、入力欄のデータの初期状態だけではなく決定的に違うところがもう一箇所あります。それは登録ボタンを押した際のデータの送信先です。
送信先はformタグのaction属性で指定しますので、それぞれの吐き出されたソースコード(HTML)を確認してみましょう。
注)formタグのaction属性と、Railsのアクションは一切関係ありませんので混乱しないように。
1 2 3 4 5 6 | <form class="new_member" id="new_member" action="/members" accept-charset="UTF-8" method="post"> <input name="utf8" type="hidden" value="✓" /> 省略 </form> |
1 2 3 4 5 6 7 | <form class="edit_member" id="edit_member_1" action="/members/1" accept-charset="UTF-8" method="post"> <input name="utf8" type="hidden" value="✓" /> <input type="hidden" name="_method" value="patch" /> 省略 </form> |
ご覧のように新規登録画面のformタグのactionは、/membersとなっています。そしてmethodがpostになっています。
一方、編集画面のformタグのaction属性は、/members/1となっています。1というのは編集するリソースのIDを表しています。methodはpostになっていますが、3行目のinputタグで_methodというキーでpatchという値を設定しているのが分かると思います。これは、PATCHによるHTTPリクエストを行うことを指定しています。
本来なら、
1 | <form action="/members/1" method="patch"> |
とできればいいのですが、formタグのmethod属性で指定できるのはGETかPOSTのどちらのみとなっている為、PATCHによるHTTPリクエストを実現するにはこういった書き方になってしまうようです。
この辺を深く知りたい人は、Webを支える技術 -HTTP、URI、HTML、そしてRESTを読んでみてください。
要するに、新規登録画面からのデータ送信は、POSTメソッドによる/membersへのリクエストであり、編集画面からのデータ送信は、PATCHメソッドによる/members/:idへのリクエストということになります。
まとめるとこういうことです。↓
action(送信先) | メソッド | |
---|---|---|
新規登録画面から | /members | POST |
編集画面から | /members/:id | PATCH |
これをふまえてルーティングを確認してみましょう。
1 2 3 4 5 6 7 8 9 | Prefix Verb URI Pattern Controller#Action members GET /members(.:format) members#index POST /members(.:format) members#create new_member GET /members/new(.:format) members#new edit_member GET /members/:id/edit(.:format) members#edit member GET /members/:id(.:format) members#show PATCH /members/:id(.:format) members#update PUT /members/:id(.:format) members#update DELETE /members/:id(.:format) members#destroy |
3行目が新規作成画面のフォームから発信されるPOSTメソッドによる/membersへのリクエストに対する対応で、そのリクエストを受けたらmembersコントローラーのcreateアクションが呼ばれます。前回やりましたね。
7行目が編集画面のフォームから発信されるPATCHメソッドによる/members/:idへのリクエストに対する対応で、updateアクションが呼ばれるように設定されています。
※8行目のPUTも、members#updateになっていますが、あまり気にしなくていいです。
つまり、編集画面の登録ボタンを押すと、
/members/:idへとPATCHメソッドによるHTTPリクエストが送られ、そのリクエストに対する処理を定義するのがupdateアクションというわけです。
updateアクションを定義する
では、編集画面の登録ボタンを押した際の処理を実装しましょう。
先程ルーティングを確認した通り、登録ボタンから発せられたリクエストを受けて動くのが、updateアクションです。
そのリクエストを受け取った際の挙動を定義する為に、membersコントローラーにupdateアクションを追加しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class MembersController < ApplicationController def index @members = Member.all end def new @member = Member.new end def create @member = Member.new(params.require(:member).permit(:name, :yomi, :phone)) @member.save redirect_to members_path end def edit @member = Member.find(params[:id]) end def update end end |
編集画面の登録ボタンを押すと、その入力欄の情報(リクエスト)がサーバーへと送られますので、updateアクションでその情報を受け取って、データベースを書き換えます。
まず編集中のMemberオブジェクトを用意します。
1 2 3 | def update @member = Member.find(params[:id]) end |
あるリソースを修正するには、そのIDを持つモデル(Memberオブジェクト)が必要になります。
※空っぽのモデルを用意してしまうと新規のIDを持つモデルを用意(新規作成)することになり、修正にはなりません。
そして、修正フォームから送られてきたデータを使って、このモデル情報(membersテーブルの一行)を更新します。
テーブルを更新するにはモデルクラスのインスタンスメソッドであるupdateメソッドを使います。引数に
1 | {"カラム名" => "値", "カラム名" => "値"} |
といった連想配列を渡すことで、テーブルを更新します。
※updateアクションと、モデルインスタンスのupdateメソッドは全然別物です。
1 2 3 4 | def update @member = Member.find(params[:id]) @member.update(params.require(:member).permit(:name, :yomi, :phone)) end |
そして、最後に一覧画面へとリダイレクトします。
1 2 3 4 5 | def update @member = Member.find(params[:id]) @member.update(params.require(:member).permit(:name, :yomi, :phone)) redirect_to members_path end |
paramsメソッド、redirect_toメソッドについては、前回の解説にでてきたものなので、何をしてるのかよく分からない人は見直してみてください。
さて、これで編集処理も完成です。
重複しているコードを整理する
members_controller.rbを見てみると、ソースコードが重複しているところがあるのが分かると思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class MembersController < ApplicationController def index @members = Member.all end def new @member = Member.new end def create @member = Member.new(params.require(:member).permit(:name, :yomi, :phone)) @member.save redirect_to members_path end def edit @member = Member.find(params[:id]) end def update @member = Member.find(params[:id]) @member.update(params.require(:member).permit(:name, :yomi, :phone)) redirect_to members_path end end |
これをちょっと整理しておきましょう。
まずは送られてくるパラメータを取得する為の
1 | params.require(:member).permit(:name, :yomi, :phone) |
を、member_paramsというprivateメソッドとして定義して、それを各アクションにて使いまわすようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | class MembersController < ApplicationController def index @members = Member.all end def new @member = Member.new end def create @member = Member.new(member_params) @member.save redirect_to members_path end def edit @member = Member.find(params[:id]) end def update @member = Member.find(params[:id]) @member.update(member_params) redirect_to members_path end private def member_params params.require(:member).permit(:name, :yomi, :phone) end end |
さらに、
1 | Member.find(params[:id]) |
も重複しているので、こちらもfind_member_by_idというprivateメソッドを定義して使いまわしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | class MembersController < ApplicationController def index @members = Member.all end def new @member = Member.new end def create @member = Member.new(member_params) @member.save redirect_to members_path end def edit @member = find_member_by_id end def update @member = find_member_by_id @member.update(member_params) redirect_to members_path end private def member_params params.require(:member).permit(:name, :yomi, :phone) end def find_member_by_id Member.find(params[:id]) end end |
可読性、保守性が高くなるように、ソースコードの重複は可能な限り避けるようにしましょう。
編集画面へのリンクを作る
編集画面が出来上がったので、編集画面へのリンクを貼りましょう。
一覧画面の各memberの名前をアンカーテキストにして、そこから各々の編集画面へリンクするようにします。
/app/views/members/index.html.erbを開いて下さい。
1 2 3 4 5 6 | <% @members.each do |member|%> <p><%= member.name %></p> <p><%= member.yomi %></p> <p><%= member.phone %></p> <% end %> <%= link_to "新規登録", new_member_path %> |
現状では、普通に名前、ヨミガナ、電話番号を出力しているだけですが、以下のように、link_toメソッドを使って、名前をアンカーテキストとしたリンクを出力するようにします。
1 2 3 4 5 6 | <% @members.each do |member|%> <p><%= link_to member.name, edit_member_path(member.id) %></p> <p><%= member.yomi %></p> <p><%= member.phone %></p> <% end %> <%= link_to "新規登録", new_member_path %> |
link_toメソッドの第一引数にアンカーテキストを、第二引数にリンク先urlを渡すことで、aタグを出力しています。
1 | <%= link_to "text", "url" %> |
出力↓↓
1 | <a href="url">text</a> |
アンカーテキストはmember.nameになっているので、名前が出力されます。
リンク先は、edit_member_path(member.id)となっています。仮にmember.idが1ならば、
"/members/1/edit"
という文字列が出力されます。
link_toやredirect_toで遷移先を指定する際に使うmembers_pathやedit_member_pathなどは、定数や変数ではなくメソッドだったんですね。
引数にIDを渡すことで、そのIDが適当な場所に挿入された状態のurlが返ってきます。
これでそれぞれのリソースへの編集画面へのリンクも完成しました。
次は、リソースの削除機能を実装したいと思います。
いつもためになる記事をありがとうございます。
他サイトの記事でもなかなかうまくいきませんでしたが、このシリーズはわかりやすく順調に理解が進んでいます。
次の記事もお待ちしてます!!
by Shun 2017年1月10日 1:03 PM
>Shunさん
誰か読んでくれている人いるのかな。。と思いながらも一生懸命書いていましたが、反応をもらえて安心しました。しかも分かりやすいとのことで非常に嬉しいです。コメントありがとうございました。
by nobuo 2017年1月10日 1:28 PM
[…] リソースの編集画面を作って、編集処理を実装する | Ruby on Rails 始めました Ruby on Railsで電話帳アプリを作っています。 前回までで、電話帳リストの一覧画面と新規登録画面を作りま […]
by TECH EXPERT 19日目 | Once and Only 2019年8月1日 12:45 PM
[…] リソースの編集画面を作って、編集処理を実装する | Ruby on Rails 始めました Ruby on Railsで電話帳アプリを作っています。 前回までで、電話帳リストの一覧画面と新規登録画面を作りま […]
by Railsの落とし穴 | Once and Only 2019年8月8日 7:54 PM
こちらとても分かりやすかったです。
現在作成中の自作ポートフォリオの参考にさせて頂きます。
どうもありがとうございます。
by Reiko 2020年9月6日 1:43 PM