はじめに
Ruby on Rails チュートリアル実例を使ってRailsを学ぼう 第4版](https://railstutorial.jp/)の 10章 10.1 ユーザーを更新するの演習まとめ&解答例です。
個人の解答例なので、誤りがあればご指摘ください。
動作環境
- cloud9
- ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]
- Rails 5.0.0.1
10.1.1 編集フォーム
本章での学び
Usersコントローラに、editアクションを追加する
params[:id]
で、ユーザーのidを取り出す。
def edit @user = User.find(params[:id]) end
editビューを実装する
editビューは、新規ユーザ登録のnewビューとほぼ同じ。
form_for(@user)
を使って実装する。
PATCHリクエストを送信する
form_for(@user)
を使うと、HTML生成時にrailsが自動的に、<input type="hidden" name="_method" value="patch">
を追加してくれる。
自動生成されたHTMLは以下の通り。
<form class="edit_user" id="edit_user_1" action="/users/1" accept-charset="UTF-8" method="post"> <input name="utf8" type="hidden" value="✓"> <input type="hidden" name="_method" value="patch"> ・・・略・・・ </form>
これは、ブラウザがPATCHリクエストを送信することができないため、POSTリクエストとhidden項目を使って、PATCHリクエストに偽造している。
演習1
先ほど触れたように、target=“_blank"で新しいページを開くときには、セキュリティ上の小さな問題があります。それは、リンク先のサイトがHTMLドキュメントのwindowオブジェクトを扱えてしまう、という点です。具体的には、フィッシング (Phising) サイトのような、悪意のあるコンテンツを導入させられてしまう可能性があります。Gravatarのような著名なサイトではこのような事態は起こらないと思いますが、念のため、このセキュリティ上のリスクも排除しておきましょう。対処方法は、リンク用のaタグのrel (relationship) 属性に、"noopener"と設定するだけです。早速、リスト 10.2で使ったGravatarの編集ページへのリンクにこの設定をしてみましょう。
noopener
をつけないリスクは、下記参照。
リンクのへの rel=noopener 付与による Tabnabbing 対策
<a href="http://gravatar.com/emails" target="_blank" rel="noopener">change</a>
演習2
リスト 10.5のパーシャルを使って、new.html.erbビュー (リスト 10.6) とedit.html.erbビュー (リスト 10.7) をリファクタリングしてみましょう (コードの重複を取り除いてみましょう)。ヒント: 3.4.3で使ったprovideメソッドを使うと、重複を取り除けます3。(関連するリスト 7.27の演習課題を既に解いている場合、この演習課題をうまく解けない可能性があります。うまく解けない場合は、既存のコードのどこに差異があるのか考えながらこの課題に取り組んでみましょう。例えば筆者であれば、リスト 10.5のテクニックをリスト 10.6に適用してみたり、リスト 10.7のテクニックをリスト 10.5に適用してみたりするでしょう。)
リファクタリングを実施する。 ※演習7.27を実施している状態です。
重複部分をformパーシャルとして切り出す。
<%= form_for(@user, url: yield(:url)) do |f| %> <%= render 'shared/error_messages', object:@user %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit yield(:buttun_text), class: "btn btn-primary" %> <% end %>
新規ユーザー登録画面
render 'form'
で、formパーシャルを呼び出す。
<% provide(:title, 'Sign up')%> <% provide(:buttun_text, 'Create my account') %> <% provide(:url, signup_path) %> <h1>Sign up</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= render 'form'%> </div> </div>
ユーザー更新画面
url:user_path
を使う理由は、PATCHリクエストと、usersコントローラのupdateアクションが紐づいているため。
<% provide(:title, "Edit user") %> <% provide(:buttun_text, "Save changes")%> <% provide(:url, user_path) %> <h1>Update your profile</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= render 'form' %> <div class="gravatar_edit"> <%= gravatar_for @user %> <a href="http://gravatar.com/emails" target="_blank">change</a> </div> </div> </div>
この時点では、まだupdateアクションを実装していないため、ユーザ―を更新するとエラーになる。
10.1.2 編集の失敗
本章での学び
【Controller】updateアクションの作成
Controller内でのみ使用できる、プライベートなStrong parametersのuser_params
を使って、ユーザー情報を更新する。
def update @user = User.find(params[:id]) if @user.update_attributes(user_params) # 更新に成功した場合を扱う。 else render 'edit' end end
演習1
編集フォームから有効でないユーザー名やメールアドレス、パスワードを使って送信した場合、編集に失敗することを確認してみましょう。
すべてブランクの場合、バリデーションエラーになることを確認。
10.1.3 編集失敗時のテスト
本章での学び
統合テストの生成
rails generate
コマンドで自動生成する。
yokoyan:~/workspace/sample_app (master) $ rails generate integration_test users_edit Running via Spring preloader in process 2388 Expected string default value for '--jbuilder'; got true (boolean) invoke test_unit create test/integration/users_edit_test.rb
テストコードの実装
以下のテストをコード化する。
- Fixtureでテストユーザmichealを作成する
- michealのユーザ情報を、GETリクエストで、users#editアクションに送信する
- editビューが描画されること
- 無効なユーザ情報を、PATCHリクエストで、users#updateアクションに送信する
- editビューが再描画されること
rails routes
でパス情報を確認
yokoyan:~/workspace/sample_app (master) $ rails routes Prefix Verb URI Pattern Controller#Action edit_user GET /users/:id/edit(.:format) users#edit user GET /users/:id(.:format) users#show PATCH /users/:id(.:format) users#update
テストコードの実装
def setup @user = users(:michael) end test "unsuccessful edit" do get edit_user_path(@user) assert_template 'users/edit' patch user_path(@user), params: { user: { name: "", email: "foo@invalid", password: "foo", password_confirmation: "bar" } } assert_template 'users/edit' end
テストがgreenになることを確認。
yokoyan:~/workspace/sample_app (master) $ rails test Running via Spring preloader in process 1976 Started with run options --seed 7556 29/29: [========================================================================================================================] 100% Time: 00:00:01, Time: 00:00:01 Finished in 1.51512s 29 tests, 76 assertions, 0 failures, 0 errors, 0 skips
演習1
リスト 10.9のテストに1行追加し、正しい数のエラーメッセージが表示されているかテストしてみてましょう。ヒント: 表 5.2で紹介したassert_selectを使ってalertクラスのdivタグを探しだし、「The form contains 4 errors.」というテキストを精査してみましょう。
画面上は以下の状態になる。
エラーの数が4件あるかを確認するために、以下のHTMLが出力されているかをチェックする。
<div class="alert alert-danger"> The form contains 4 errors </div>
テストコードに組み込む。
test "unsuccessful edit" do get edit_user_path(@user) assert_template 'users/edit' patch user_path(@user), params: { user: { name: "", email: "foo@invalid", password: "foo", password_confirmation: "bar" } } assert_template 'users/edit' assert_select "div.alert-danger", "The form contains 4 errors" end
テスト結果がgreenになることを確認。
yokoyan:~/workspace/sample_app (master) $ rails test Running via Spring preloader in process 3205 Started with run options --seed 22571 29/29: [========================================================================================================================] 100% Time: 00:00:01, Time: 00:00:01 Finished in 1.33817s 29 tests, 78 assertions, 0 failures, 0 errors, 0 skips
10.1.4 TDDで編集を成功させる
本章での学び
テストコードの実装
以下のテストをコード化する。
- Fixtureでテストユーザmichealを作成する
- michealのユーザ情報を、GETリクエストで、users#editアクションに送信する
- editビューが描画されること
- 有効な氏名、メールアドレスを入力する(パスワードは空=変更しない)
- 有効なユーザ情報を、PATCHリクエストで、users#updateアクションに送信する
- flashエラーメッセージが表示されないこと
- michealのプロフィールページにリダイレクトされること
- ユーザー情報を再度DBから読み込む
- 入力した名前と、DBの名前が一致すること
- 入力したメールアドレスと、DBのメールアドレスが一致すること
実装したテストコード。
test "successful edit" do get edit_user_path(@user) assert_template 'users/edit' name = "Foo Bar" email = "foo@bar.com" patch user_path(@user), params: { user: { name: name, email: email, password: "", password_confirmation: "" } } assert_not flash.empty? assert_redirected_to @user @user.reload assert_equal name, @user.name assert_equal email, @user.email end
【Controller】updateアクションの実装
flashメッセージの追加と、ユーザプロフィールページへのリダイレクト処理を追加する。
def update @user = User.find(params[:id]) if @user.update_attributes(user_params) # 更新に成功した場合を扱う。 flash[:success] = "Profile updated" redirect_to @user
【Model】パスワードのバリデーション修正
パスワードのバリデーションにallow_nil: true
を追加することで、
更新時のパスワードが空でも、更新できるようにする。
railsdoc.comによると、
trueならば、nilの検証はスキップ
とのこと。
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
テスト結果がgreenになることを確認。
yokoyan:~/workspace/sample_app (master) $ rails test Running via Spring preloader in process 4806 Started with run options --seed 30426 30/30: [======================================================================================================================================] 100% Time: 00:00:01, Time: 00:00:01 Finished in 1.33014s 30 tests, 83 assertions, 0 failures, 0 errors, 0 skips
演習1
実際に編集が成功するかどうか、有効な情報を送信して確かめてみましょう。
パスワードが空でも更新できることを確認。
演習2
もしGravatarと紐付いていない適当なメールアドレス (foobar@example.comなど) に変更した場合、プロフィール画像はどのように表示されるでしょうか? 実際に編集フォームからメールアドレスを変更して、確認してみてましょう。
適当なメールアドレスに変更。
プロフィール画像が切り替わることを確認。
おわりに
9章に比べるとずいぶん楽に感じます。 railsはCRUD処理を書くのが本当に楽ですね。 これまでrailsコンソールでの動作確認以外では、1度もSQLを書いていません。 パーシャルでのリファクタリングも新鮮でした。 rails書いてて楽しいなあ。仕事で使いたくなってきたよほんとに。。。