読者です 読者をやめる 読者になる 読者になる

techium

このブログは何かに追われないと頑張れない人たちが週一更新をノルマに技術情報を発信するブログです。もし何か調査して欲しい内容がありましたら、@kobashinG or @muchiki0226 までいただけますと気が向いたら調査するかもしれません。

Rails Tutorial 5 Updating, showing, and deleting users

Rails Tutorial 5 Updating, showing, and deleting users

前章で実装した認可モデルを使用して、ユーザー情報の更新ができるように実装を進めていく。

Edit form

トピックブランチを作成したらまずは編集用のフォーム画面を作成から始める。 - editアクションを Users コントローラに追加 - edit ビューの追加

と進める。

app/controllers/users_controller.rb

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

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

なるほど

  • ナビゲーションバーにユーザー設定へのリンクを追加
  • updateアクションの追加
    • マスアサインメント脆弱性防止に Strong Parameters を使う
    • 無効なデータを送信した時のエラー表示は、Userモデルのバリデーションとエラーメッセージのパーシャルでできている
  • ユーザー情報更新の統合テストを作成する
  • 編集失敗時のテストを作成する

と進んでいく。

$ rails g integration_test users_edit
Running via Spring preloader in process 1527829
      invoke  test_unit
      create    test/integration/users_edit_test.rb

で生成されるテストファイルにて、editビューに patch メソッドで無効なユーザーデータを送信し、再度 editビューが描画されることを確認する。

ここから本格的に TDD の実践となる。
受け入れテストを作成し、ある機能の実装が終わったかどうかの判断を、受け入れテストが通るかどうかで行う。

ユーザー情報の更新をテスト駆動で実装する。
test/integration/users_edit_test.rb

require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end
  .
  .
  .
  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
end
  • パスワードを変更する必要が無いときはパスワード入力なしで更新できるようにしてあげたほうが使い易い
  • パスワードの長さに対するバリデーションに引っかかる
  • パスワードのバリデーションにallow_nil: trueを追加する
  • has_secure_passwordがあるので空のパスワードで登録されてしまうことはない

と進んでいく。

Authorization

セキュリティモデルの実装を進めていく。

  • ログインしていないユーザーが保護されたページにアクセスしようとした場合はログインページにリダイレクト
    • Usersコントローラにてbeforeフィルターを使用する
  • ログインはしているが、許可されていないページにアクセスしようとした場合はルートURLにリダイレクト

として実装を進める。

すると、保護されたページに対するテストが通らなくなってしまうので、test/integration/users_edit_test.rb を修正してテスト前にログインしておくようにする。

これで既存のテストは全て GREEN になる。

が、このままでは before フィルターの実装を外してもテストは GREEN になってしまう。
セキュリティモデルの実装が壊れたら検出できるようテストを修正する。 - beforeフィルターはアクションごとに適用 - Usersコントローラのテストもアクションごとに書く - 正しい種類のHTTPリクエストを使ってeditアクションとupdateアクションをそれぞれ実行 - 以下を確認するテストを追加する。 - flashにメッセージが代入されたか? - ログイン画面にリダイレクトされたか? - ユーザーを複数用意する - ユーザーの情報が互いに編集できないことを確認 - 自身の情報以外を編集しようとしたユーザーはルートURLにリダイレクトする

上記を実装することで検出できるようになる。

Friendly forwarding

ログインしていないユーザーのリダイレクト先を、ユーザーが開こうとしていたページにしてあげることでより使いやすくする。

まずテストを以下のように書き換える。

  • 編集ページにアクセス
  • ログイン
  • 編集ページにリダイレクトされていることをチェック

次にこのテストが通るように以下のように実装していく。

  • リクエスト時点のページを store_location メソッドで記憶
    • session を使う
    • GET リクエストの場合のみ記憶する
    • beforeフィルター logged_in_user から呼び出す
  • 記憶した URL へのリダイレクトを redirect_back_or メソッドで実装
    • createアクションから呼び出す

これでテストが通るようになり、フレンドリーフォワーディングの実装が完了する。

Showing all users

indexアクションを追加して全てのユーザーを一覧表示する。

indexアクションはログイン済みユーザーにしか見せないようにする。
まずはこの動作のテストを作成し、テストが通るように実装を進める。

  • index アクションの追加
  • ユーザー情報のリスト表示
  • ヘッダーへの index へのリンク追加

と進めていく。

次に、Faker gem を追加して実際にいそうなユーザー名を作る。

Gemfile に以下を追記し、bundle install を実行する

gem 'faker',          '1.6.6'

サンプルユーザーを生成するRubyスクリプト (Railsタスク) を追加して実行する。

これでログインするとユーザー一覧画面にて 100 人分の情報が見れる。

全ユーザー情報を1ページに表示するのは辛いのでページネーションを実装する。
ページネーションの実装方法にも色々あるらしいが、ここで取り上げられているのは will_paginate

実装が済んだらページネーションの簡単なテストも実装しておく。
ページネーションのテストのために fixture を使用して、ユーザーを31人以上になるよう追加するコードも実装する。

ここでリファクタリングを挟み、テストがあるので安心してできるね、的なテストのありがたみを感じさせる展開。

Deleting users

最後のアクション destroy を実装する。

まずはこれを実行できる権限を持つ admin ユーザーを作成するところから。
boolean の admin 属性を User モデルに追加する。

  • マイグレーションの実行
  • default: false の付与

これで最初のユーザーをデフォルトで admin ユーザーになるようにしておく。

The destroy action

admin ユーザーだけが実行できる destroy アクションを実装していく。

  • admin ユーザーにだけ表示される delete リンクを作成する
  • destroy アクションを実装する
  • destroy アクションのアクセス制御を実装
  • ユーザー削除のテストを作成
    • ログインしていないユーザーであれば、ログイン画面にリダイレクト
    • 管理者以外ならホーム画面にリダイレクト

と進めていく。

最後にデプロイ。

またブランチを master に切り替えてマージし終わったところでテストがエラーに。
落ち着いて こちら を試して無事テスト通過。
(ちょいちょい発生して焦る。)

Heroku 本番環境の DB をリセットする。

$ heroku pg:reset DATABASE
 !    WARNING: Destructive Action

こんなワーニングが出るが、重大な変更になるからちゃんと app 名を確認してから進めろってな内容なので確認の意味も含めて app 名を打ち込めば良い。

 ▸    Heroku has temporarily disabled this feature, please try again shortly. See https://status.heroku.com for current Heroku platform status.

こんなのも出て「?」となったが、Heroku Status を見ると、

US API service update on Wednesday, April 26 at 21:00 UTC (17:00 EDT)

とありこれが原因だった模様。
上記時間帯を過ぎれば使用できるようになった。

そんなこんなで Heroku へのデプロイも完了。

最後に

Cloud9 を使用しているとこんなのが表示されるようになった。

Websockets are broken in Safari version 10.1.

Due to https://bugs.webkit.org/show_bug.cgi?id=170463, websockets do not work well on this browser version which may result in frequent disconnects from the service. Please use Chrome, ‘Safari Technology Preview’ or another browser until the Safari updates to version 10.2.

どうりでぶつぶつ切れまくってたわけだ。
Safari の 10.2 を待ちつつ Chrome に切り替えると快適快適。