紙一重の積み重ね

35歳のエンジニアがなれる最高の自分を目指して、学んだことをこつこつ情報発信するブログです。

【10章】Ruby on Railsチュートリアル演習まとめと解答例【10.1 ユーザーを更新する】

はじめに

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アクションが紐づいているため。

image.png

<% 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アクションを実装していないため、ユーザ―を更新するとエラーになる。 image.png

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

編集フォームから有効でないユーザー名やメールアドレス、パスワードを使って送信した場合、編集に失敗することを確認してみましょう。

すべてブランクの場合、バリデーションエラーになることを確認。 image.png

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.」というテキストを精査してみましょう。

画面上は以下の状態になる。

image

エラーの数が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

実際に編集が成功するかどうか、有効な情報を送信して確かめてみましょう。

パスワードが空でも更新できることを確認。 image

image

演習2

もしGravatarと紐付いていない適当なメールアドレス (foobar@example.comなど) に変更した場合、プロフィール画像はどのように表示されるでしょうか? 実際に編集フォームからメールアドレスを変更して、確認してみてましょう。

適当なメールアドレスに変更。

image

プロフィール画像が切り替わることを確認。

image

おわりに

9章に比べるとずいぶん楽に感じます。 railsはCRUD処理を書くのが本当に楽ですね。 これまでrailsコンソールでの動作確認以外では、1度もSQLを書いていません。 パーシャルでのリファクタリングも新鮮でした。 rails書いてて楽しいなあ。仕事で使いたくなってきたよほんとに。。。