ぞえの技術めも

Ruby on Rails勉強中

【102日目】【1日20分のRailsチュートリアル】【第9章】ユーザーが自分の情報だけを編集できるように実装を追加する

Ruby on Railsチュートリアル(第3版)

今日は「9.2.2 正しいユーザーを要求する」から。

9.2.2 正しいユーザーを要求する

当然のことですが、ログインを要求するだけでは十分ではありません。ユーザーが自分の情報だけを編集できるようにする必要があります。

勝手に自分の情報を編集されたら困るもんね。

そこで本項では、セキュリティモデルが正しく実装されている確信を持つために、テスト駆動開発で進めていきます。

ほぅほぅ。

まずはユーザーの情報が互いに編集できないことを確認するために、サンプルユーザーをもう一人追加します。ユーザー用のfixtureファイルに2人目のユーザーを追加してみましょう (リスト9.20)。

2人目のユーザーを追加。

test/fixtures/users.yml

archer:
  name: Sterling Archer
  email: duchess@example.gov
  password_digest: <%= User.digest('password') %>

次に、 リスト8.50で定義したlog_in_asメソッドを使って、editアクションとupdateアクションをテストします (リスト9.21)。

setupでもう一つのユーザーとしてさっき追加ユーザーを設定して、
edit/updateアクションそれぞれでログインしているユーザーとは別のユーザーのプロフィールページを編集/更新をチェックするテストを追加。

test/controllers/users_controller_test.rb

  :
  def setup
    @user = users(:michael)
    @other_user = users(:archer)
  end
  :
  test "should redirect edit when logged in as wrong user" do
    log_in_as(@other_user)
    get :edit, id: @user
    assert flash.empty?
    assert_redirected_to root_url
  end

  test "should redirect update when logged in as wrong user" do
    log_in_as(@other_user)
    patch :update, id: @user, user: { name: @user.name, email: @user.email }
    assert flash.empty?
    assert_redirected_to root_url
  end
end

別のユーザーのプロフィールを編集しようとしたらリダイレクトさせたいので、correct_userというメソッドを作成し、beforeフィルターからこのメソッドを呼び出すようにします (リスト9.22)。

別のユーザーのプロフィールを編集しようとしたらルートURLにリダイレクトする処理を追加する。

app/controllers/users_controller.rb

  :
  before_action :correct_user,   only: [:edit, :update]
  :
    # 正しいユーザーかどうか確認
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless @user == current_user
    end
end

beforeフィルターのcorrect_userで@user変数を定義しているため、リスト9.22ではeditとupdateの各アクションから、@userへの代入文を削除している点にも注意してください。

@user変数の定義は二重にはいらないってことかな。。。
コメントアウトしとこう。

  def edit
    # @user = User.find(params[:id])
  end

  def update
    # @user = User.find(params[:id])
    :

ここまで実装できたらさっき追加したテストが通るはずなので、テスト実行。

$ bundle exec rake test
35 tests, 83 assertions, 0 failures, 0 errors, 0 skips

問題なし。

途中だけどキリがいいので今日はここまで。

今日の学習時間は【20分】

次は「9.2.2 正しいユーザーを要求する」のリファクタリングから。

【101日目】【1日20分のRailsチュートリアル】【第9章】ユーザーにログインを要求する処理のテストを作成する

Ruby on Railsチュートリアル(第3版)

今日は「9.2.1 ユーザーにログインを要求する」のテストを修正するところから。

9.2.1 ユーザーにログインを要求する

原因は、editアクションやupdateアクションでログインを要求するようになったため、ログインしていないユーザーだとこれらのテストが失敗するようになったためです

テストに失敗するようになってしまったのでテストを修正するところから。

このため、editアクションやupdateアクションをテストする前にログインしておく必要があります。

テストユーザーとしてログインする、log_in_asヘルパーを使うとのこと。

test/integration/users_edit_test.rb

  :
  test "unsuccessful edit" do
    log_in_as(@user)
  :
  test "successful edit" do
    log_in_as(@user)
  :

テスト実行してテストが通るようになったことを確認。

$ bundle exec rake test
31 tests, 75 assertions, 0 failures, 0 errors, 0 skips

これでテストスイートがパスするようになりましたが、実はbeforeフィルターの実装はまだ終わっておりません。

え、そうなん。

セキュリティモデルに関する実装を取り外してもテストがGREENになってしまうかどうか、実際にコメントアウトして確かめてみましょう (リスト9.16)。

この前追加したコードをコメントアウトしてみる。

app/controllers/users_controller.rb

class UsersController < ApplicationController
  # before_action :logged_in_user, only: [:edit, :update]
  :

テスト実行。

$ bundle exec rake test
31 tests, 75 assertions, 0 failures, 0 errors, 0 skips

あら、通りますね。

beforeフィルターをコメントアウトして巨大なセキュリティーホールが作られたら、テストスイートでそれを検出できるべきです。

ふむふむ。
テスト追加しましょう。

beforeフィルターは基本的にアクションごとに適用していくので、Usersコントローラのテストもアクションごとに書いていきます。

setupとedit/updateアクションを実行してフラッシュが表示されること、ログインページにリダイレクトすることを確認するテストを追加。

test/controllers/users_controller_test.rb

  :
  def setup
    @user = users(:michael)
  end
  :
  test "should redirect edit when not logged in" do
    get :edit, id: @user
    assert_not flash.empty?
    assert_redirected_to login_url
  end

  test "should redirect update when not logged in" do
    patch :update, id: @user, user: { name: @user.name, email: @user.email }
    assert_not flash.empty?
    assert_redirected_to login_url
  end
end

この時点では、(beforeフィルターが無効のままなので) テストスイートはREDになるはずです。beforeフィルターのコメントアウトを元に戻して、GREENになるかどうか確かめてみましょう (リスト9.18)。

コメントアウトしたままテスト実行してみると失敗する。

$ bundle exec rake test
33 tests, 79 assertions, 2 failures, 0 errors, 0 skips

beforeフィルターのコメントアウトを戻してテスト実行してみると成功した。

$ bundle exec rake test
33 tests, 79 assertions, 0 failures, 0 errors, 0 skips

これらのテストを実装したことによって、うっかり誰でも編集できてしまうバグがあっても、すぐに検知できるようになりました。

検知できる仕組みだいじ。

今日の学習時間は【20分】

次は「9.2.2 正しいユーザーを要求する」から。

【100日目】【1日20分のRailsチュートリアル】【第9章】ユーザーにログインを要求する処理を追加

遂に100日目!

Ruby on Railsチュートリアル(第3版)

今日は「9.2 認可」から。

9.2 認可

9.1のeditアクションとupdateアクションはすでに完全に動作していますが、セキュリティ上の大穴が1つ空いています。
どのユーザーでもあらゆるアクションにアクセスでき、ログインさえしていれば他のユーザーの情報を編集できてしまいます。

URL直打ちすれば他のユーザーの編集ページにアクセスできるってことかな。

9.2.1では、ログインしていないユーザーが保護されたページにアクセスしようとした際のケースについて対処していきます。
こういったケースはアプリケーションを使っていると普通に起こることなので、ログインページに転送して、そのときに分かりやすいメッセージも表示するようにしましょう。

よくありますね。確かに今はその機能ないや。

9.2.1 ユーザーにログインを要求する

図9.6のように転送させる仕組みを実装したいときは、Usersコントローラの中でbeforeフィルターを使います。

app/controllers/users_controller.rb

class UsersController < ApplicationController
  before_action :logged_in_user, only: [:edit, :update]
  :
  private
    :
    # beforeフィルター

    # ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end
end

デフォルトでは、beforeフィルターはコントローラ内のすべてのアクションに適用されるので、ここでは適切な:onlyオプションハッシュを渡すことによって:editと:updateアクションにのみこのフィルタが適用されるように制限をかけています。

フィルタかけられるのか。楽だなー。

beforeフィルターを使って実装した結果 (リスト9.12) は、一度ログアウトしてユーザー編集ページ (/users/1/edit) にアクセスしてみることで確認できます (図9.7)。

動作見てみよう。

サーバーを起動して

$ rails server -b $IP -p $PORT

アプリケーションからログアウトしていることを確認してユーザー編集ページにアクセス。

f:id:kt_zoe:20170125123745p:plain

ログインページとログインを促すメッセージが表示されました。

ちょっと気になってるんだけど、セッションの永続化してるはずなのに日をまたぐ(パソコン再起動挟む)とログアウト状態になってるのはいいのかな。。
もしくは毎回新しいブラウザでアプリケーションにアクセスしているからか。
なんでだろう~。

リスト9.12のキャプションに記したように、今の段階ではテストは失敗します。

テスト失敗するようになってしまった。
editアクションやupdateアクションでログインを要求するようにしたため、らしい。

$ bundle exec rake test
 :
 FAIL["test_successful_edit", UsersEditTest, 2017-01-24 19:32:59 +0000]
 test_successful_edit#UsersEditTest (1485286379.47s)
        expecting <"users/edit"> but rendering with <[]>
        test/integration/users_edit_test.rb:21:in `block in <class:UsersEditTest>'

 FAIL["test_unsuccessful_edit", UsersEditTest, 2017-01-24 19:32:59 +0000]
 test_unsuccessful_edit#UsersEditTest (1485286379.48s)
        expecting <"users/edit"> but rendering with <[]>
        test/integration/users_edit_test.rb:11:in `block in <class:UsersEditTest>'

31 tests, 69 assertions, 2 failures, 0 errors, 0 skips

途中だけど今日はここまで。

今日の学習時間は【12分】

次は「9.2.1 ユーザーにログインを要求する」のテストを修正するところから。

【99日目】【1日20分のRailsチュートリアル】【第9章】テスト駆動開発でプロフィール編集処理を実装する

Ruby on Railsチュートリアル(第3版)

今日は「9.1.4 TDDで編集を成功させる」から。

9.1.4 TDDで編集を成功させる

プロファイル画像の編集は、画像のアップロードをGravatarに任せてあるので、既に動作するようになっています。

Gravatarの方で画像の編集をする感じか。

今回はテスト駆動開発を使ってユーザーの編集機能を実装してみましょう。

TDDって「テスト駆動開発」のことか。そうか…覚えてなかったよ。。。

flashメッセージが空でないかどうかと、プロフィールページにリダイレクトされるかどうかをチェックします。
また、データベース内のユーザー情報が正しく変更されたかどうかも検証します。

データベース内のユーザー情報が正しく変更されたかについては、@user.reloadでデータベース内のユーザー情報を取得して情報を比較するそう。

test/integration/users_edit_test.rb

  test "successful edit" do
    get edit_user_path(@user)
    assert_template 'users/edit'
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), 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

テストにパスする必要のある、リスト9.8のupdateアクションは、リスト9.9に示したように、createアクション (リスト8.22) の最終的なフォームとほぼ同じです。

updateアクションにflashメッセージの表示とプロフィールページのリダイレクト処理を追加。

app/controllers/users_controller.rb

  def update
    @user = User.find(params[:id])
    if @user.update_attributes(user_params)
      flash[:success] = "Profile updated"
      redirect_to @user
    else
    :

パスワードのバリデーションに対して、空だったときの例外処理を加える必要があります。

パスワードのバリデーションに空だったときの例外処理(allow_nil: true)を追加。

app/models/user.rb

  validates :password, presence: true, length: { minimum: 6 }, allow_nil: true

すべてのテストを走らせてみて、成功したかどうか確かめてみてください。

テストは通りました。

$ bundle exec rake test
31 tests, 75 assertions, 0 failures, 0 errors, 0 skips

一応ユーザーの編集も試してみよう。

サーバーを起動して

$ rails server -b $IP -p $PORT

プロフィール編集ページにアクセス。

名前とか適当に変えてみてボタンクリック。

f:id:kt_zoe:20170123123826p:plain

フラッシュメッセージも表示されたしパスワード欄空でも問題なし。
ちゃんと動いてそう。

今日の学習時間は【25分】

次は「9.2 認可」から。

【98日目】【1日20分のRailsチュートリアル】【第9章】ユーザー情報の編集に失敗したときの処理とテストを作成

Ruby on Railsチュートリアル(第3版)

今日は「9.1.2 編集の失敗」から。

9.1.2 編集の失敗

本項では、7.3のユーザー登録に失敗したときと似た方法で、編集に失敗した場合について扱っていきます。

まずUsersコントローラーにupdateアクションを追加する。

app/controllers/users_controller.rb

  def update
    @user = User.find(params[:id])
    if @user.update_attributes(user_params)
      # 更新に成功したときの処理
    else
      render 'edit'
    end
  end

Userモデルのバリデーションとエラーメッセージのパーシャルが既にあるので (リスト9.2)、無効な情報を送信すると役立つエラーメッセージが表示されるようになっています (図9.3)。

サーバーを起動して

$ rails server -b $IP -p $PORT

パスワード空のまま「Save changes」ボタン押してみる。

f:id:kt_zoe:20170116125027p:plain

エラーメッセージが表示されることを確認。

9.1.3 編集失敗時のテスト

コラム3.3で説明したテストのガイドラインに従って、エラーを検知するための統合テストを書いていきましょう。

コマンドで統合テストを生成する。

$ rails generate integration_test users_edit
      invoke  test_unit
      create    test/integration/users_edit_test.rb

最初は編集失敗時の簡単なテストを追加します (リスト9.6)。

無効な情報を送信してみて、editビューが更新されるかを見るテスト。

test/integration/users_edit_test.rb

require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "unsuccessful edit" do
    get edit_user_path(@user)
    assert_template 'users/edit'
    patch user_path(@user), user: { name:  "",
                                    email: "foo@invalid",
                                    password:              "foo",
                                    password_confirmation: "bar" }
    assert_template 'users/edit'
  end
end

現時点ではテスト通ります。

$ bundle exec rake test
30 tests, 69 assertions, 0 failures, 0 errors, 0 skips

今日の学習時間は【20分】

次は「9.1.4 TDDで編集を成功させる」から。

TDDってなんだ。

【97日目】【1日20分のRailsチュートリアル】【第9章】ユーザー編集ページを作成する

Ruby on Railsチュートリアル(第3版)

今日は「9.1.1 編集フォーム」のeditアクションを実装するところから。

9.1.1 編集フォーム

Usersコントローラにeditアクションを追加して、それに対応するeditビューを実装する必要があります。

まずUsersコントローラにeditアクションを追加。

app/controllers/users_controller.rb

  def edit
    @user = User.find(params[:id])
  end

ユーザー編集ページに対応するビュー を、リスト9.2に示します (このファイルは手動で作成する必要があります)。

ユーザー編集ページのビューファイルを作成。

touch app/views/users/edit.html.erb

作成したファイルを下記内容で更新。

app/views/users/edit.html.erb

<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= 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 "Save changes", class: "btn btn-primary" %>
    <% end %>

    <div class="gravatar_edit">
      <%= gravatar_for @user %>
      <a href="http://gravatar.com/emails" target="_blank">change</a>
    </div>
  </div>
</div>

プロフィール画像の編集はGravatarのページに遷移させるのか。
アプリケーション内には持ってないもんね。

ビューが作成できたら動作確認。

サーバーを起動して

$ rails server -b $IP -p $PORT

https://rails-tutorial-kt-zoe.c9users.io/users/1/editにアクセス。

f:id:kt_zoe:20170113123629p:plain

ユーザー編集ページが表示できました。

だとすると、Railsはどうやって新規ユーザー用のPOSTリクエストとユーザー編集用のPATCHリクエストを区別するのでしょうか。
その答えは、Railsは、ユーザーが新規なのか、それともデータベースに存在する既存のユーザーであるかを、Active Recordのnew_record?論理値メソッドを使用して区別できるからです。

Railsは、form_for(@user)を使用してフォームを構成すると、@user.new_record?がtrueのときにはPOSTを、falseのときにはPATCHを使用します。

へ~。自分がコード書くときにわざわざフラグとか用意しなくていいってことか。

仕上げに、ナビゲーションバーにあるユーザー設定へのリンクを更新します。

Settingsの文字列リンクをユーザー編集ページへのリンクに更新。

app/views/layouts/_header.html.erb

:
<li><%= link_to "Settings", edit_user_path(current_user) %></li>
:

これでログイン後のSettingsから編集ページに遷移できるようになりました。

今日の学習時間は【24分】

次は「9.1.2 編集の失敗」から。

【96日目】【1日20分のRailsチュートリアル】【第9章】9章向けの作業ブランチ作る

Ruby on Railsチュートリアル(第3版)

今日は「第9章 ユーザーの更新・表示・削除」から。

第9章 ユーザーの更新・表示・削除

この章では、Usersリソース用のRESTアクション (表7.1) のうち、これまで未実装だったedit、update、index、destroyアクションを追加し、RESTアクションを完成させます。

createやshow以外のアクションを追加する感じかな。

9.1 ユーザーを更新する

POSTリクエストに応答するcreateの代わりに、PATCHリクエストに応答するupdateアクションを作成すればよいのです (コラム3.3)。

Ruby on RailsはPATCHリクエスト使えるんだっけ。
updateアクションもPOSTリクエストになるのかと思ってた。

では最初に、いつものようにupdating-usersトピックブランチを作成しましょう。

作業用のブランチ作成。

$ git checkout master
$ git checkout -b updating-users

9.1.1 編集フォーム

まずは編集フォームから始めます。モックアップ図9.1のとおりです。

あれ、モックアップ見るとプロフィール画像も編集できるようになってる。どう組むんだろう??

コラムとか読み直してたら時間かかったので今日はここまで。

今日の学習時間は【20分】

次は「9.1.1 編集フォーム」の実装していくところから。