(ギリ)20代の地方公務員がRailsチュートリアルに取り組みます【第10章】


前提

・Railsチュートリアルは第4版
・今回の学習は3周目(9章以降は2周目)
・著者はProgate一通りやったぐらいの初学者

基本方針

・読んだら分かることは端折る。
・意味がわからない用語は調べてまとめる(記事最下段・用語集)。
・理解できない内容を掘り下げる。
・演習はすべて取り組む。
・コードコピペは極力しない。

 
 認証システム開発・第5段回目、ついに2ケタ・第10章に突入です。RESTアクションを完成させていきましょう。
 
本日のBGMはこちら。
PLASTIC GIRL IN CLOSET "TOY"
もう10年前のアルバムになるのか…時が経つのは早いですね。そら私の20代終わっちゃうわ。

 

【10.1.1 編集フォーム メモと演習】

・リンクのaタグ内に target="_blank"を入れると、リンクを別タブで開くようになる。(セキュリティ対策は演習で)
・Active Recordのnew_recordメソッドで新規ユーザーか既存ユーザーか、Rails内部で判断している。

1. 先ほど触れたように、target="_blank"で新しいページを開くときには、セキュリティ上の小さな問題があります。それは、リンク先のサイトがHTMLドキュメントのwindowオブジェクトを扱えてしまう、という点です。具体的には、フィッシング (Phising) サイトのような、悪意のあるコンテンツを導入させられてしまう可能性があります。Gravatarのような著名なサイトではこのような事態は起こらないと思いますが、念のため、このセキュリティ上のリスクも排除しておきましょう。対処方法は、リンク用のaタグのrel (relationship) 属性に、"noopener"と設定するだけです。早速、リスト 10.2で使ったGravatarの編集ページへのリンクにこの設定をしてみましょう。
→ 下記。

<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で必要になるURLをリスト 10.5に渡してみるでしょう。)
→ やるだけよん。(なぜか7章の演習やってたけどビュー同士の違いが発生してなかった。考察してるときに戻してなかったか)

 

【10.1.2 編集の失敗 演習】

1. 編集フォームから有効でないユーザー名やメールアドレス、パスワードを使って送信した場合、編集に失敗することを確認してみましょう。
→ ちゃんと編集ページに戻されてエラーが出ました。

 

【10.1.3 編集失敗時のテスト 演習】

1. リスト 10.9のテストに1行追加し、正しい数のエラーメッセージが表示されているかテストしてみましょう。ヒント: 表 5.2で紹介したassert_selectを使ってalertクラスのdivタグを探しだし、「The form contains 4 errors.」というテキストを精査してみましょう。
→ 下記

assert_select "div.alert", "The form contains 4 errors."

 

【10.1.4 TDDで編集を成功させる 演習】

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

2. もしGravatarと紐付いていない適当なメールアドレス ([email protected]など) に変更した場合、プロフィール画像はどのように表示されるでしょうか? 実際に編集フォームからメールアドレスを変更して、確認してみましょう。
→ まとめて、サンプルユーザーのアドレスを適当に変更→成功、グラバターのデフォ画像が表示される。

 

【10.2 認可 メモ】

認証(authentication):サイトのユーザーを識別
認可(authorization):ユーザーが実行可能な操作を管理
 

【10.2.1 ユーザーにログインを要求する メモと演習】

before_action:
コントローラにおいて、何らかのアクションが実行される直前に、特定のメソッドを実行する。オプションにonly: [:アクション]を渡すことで、特定アクションにだけ適用する。

1. デフォルトのbeforeフィルターは、すべてのアクションに対して制限を加えます。今回のケースだと、ログインページやユーザー登録ページにも制限の範囲が及んでしまうはずです (結果としてテストも失敗するはずです)。リスト 10.15のonly:オプションをコメントアウトしてみて、テストスイートがそのエラーを検知できるかどうか (テストが失敗するかどうか) 確かめてみましょう。
→ Yes, RED !

 

【10.2.2 正しいユーザーを要求する 演習】

1. 何故editアクションとupdateアクションを両方とも保護する必要があるのでしょうか? 考えてみてください。
→ usersリソースのURLが異なる(editは/users/1/edit、updateは/users/1)から。第7章の表7.1参照。

 
2. 上記のアクションのうち、どちらがブラウザで簡単にテストできるアクションでしょうか?
→ editでしょ。HTTPリクエストがGETだから。ブラウザに表示してくれる。

 

【10.2.3 フレンドリーフォワーディング メモと演習】

requestオブジェクト:Railsガイド参照。URL以外にもいろいろなクライアント側の情報が含まれているよ。

1. フレンドリーフォワーディングで、渡されたURLに初回のみ転送されていることを、テストを書いて確認してみましょう。次回以降のログインのときには、転送先のURLはデフォルト (プロフィール画面) に戻っている必要があります。ヒント: リスト 10.29のsession[:forwarding_url]が正しい値かどうか確認するテストを追加してみましょう。
→ これが分からなかった。調べた結果が以下。このテストではまずedit_user_path(@user)にアクセスしようとしているから、session[:forwarding_url]がそのURLと等しいかチェック。そしてログイン後はアクセスしようとしていたedit_user_url(@user)に戻ってるかチェックして、session[:forwarding_url]がnilか(deleteされているか)を確かめていると。なるほどな〜。

users_edit_test.rb
  test "successful edit with friendly forwarding" do
    get edit_user_path(@user)
    assert_equal session[:forwarding_url], edit_user_url(@user)
    log_in_as(@user)
    assert_redirected_to edit_user_url(@user)
    assert_nil session[:forwarding_url]
    assert_redirected_to edit_user_url(@user)
    name = "Foo Bar"
    email = "[email protected]"
    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

 
2. 7.1.3で紹介したdebuggerメソッドをSessionsコントローラのnewアクションに置いてみましょう。その後、ログアウトして /users/1/edit にアクセスしてみてください (デバッガーが途中で処理を止めるはずです)。ここでコンソールに移り、session[:forwarding_url]の値が正しいかどうか確認してみましょう。また、newアクションにアクセスしたときのrequest.get?の値も確認してみましょう (デバッガーを使っていると、ときどき予期せぬ箇所でターミナルが止まったり、おかしい挙動を見せたりします。熟練の開発者になった気になって (コラム 1.1)、落ち着いて対処してみましょう)。
→ (byebug)にsession[:forwarding_url]を入れると格納されているURL(~/users/1/edit)が表示され、request.get?を入れるとtrueが返ってきます。

 

【10.3.1 ユーザーの一覧ページ 演習】

1. レイアウトにあるすべてのリンクに対して統合テストを書いてみましょう。ログイン済みユーザーとそうでないユーザーのそれぞれに対して、正しい振る舞いを考えてください。ヒント: log_in_asヘルパーを使ってリスト 5.32にテストを追加してみましょう。
→ 自分で試しに書いた下のコードでGREENだったんですがダメ?しかも2パターン。

site_layout_test
require 'test_helper'

class SiteLayoutTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "layout links" do
    get root_path
    assert_template 'static_pages/home'
    assert_select "a[href=?]", root_path, count: 2
    assert_select "a[href=?]", help_path
    assert_select "a[href=?]", about_path
    assert_select "a[href=?]", contact_path
    assert_select "a[href=?]", signup_path
    get contact_path
    assert_select "title", full_title("Contact")
    get signup_path
    assert_select "title", full_title("Sign up")
    log_in_as(@user)
    follow_redirect!  # もしくは get user_path(@user)
    assert_select "a[href=?]", users_path
    assert_select "a[href=?]", user_path(@user)
    assert_select "a[href=?]", edit_user_path(@user)
    assert_select "a[href=?]", logout_path
  end

end

調べてみた他の演習まとめとの違い
・ログイン済みユーザーのテストを非ログイン時のテストと分けている。
⇨これはコードの可読性を考えると分けた方がいいのかも。あとは明確に動作を分けた方がテストとしては確実?

・ログイン後にget root_pathしている。
⇨これは不自然じゃないか?ログイン後のデフォルト動作はユーザーページへ飛ぶんやろ?何でわざわざhomeに行く?んで、log_in_asではpostリクエストしてるわけだから、follow_redirect!で実際にそのページへ行ってテストしてもよし、get user_path(@user)でテスト対象のページを指定してもよし、と考えました。後々不具合が出るようであれば見直します。

 

【10.3.2 サンプルのユーザー メモと演習】

 ここでfakerジェム入れる時に赤文字が出てたのでbundle update。こういうのは慣れてきましたね。

1. 試しに他人の編集ページにアクセスしてみて、10.2.2で実装したようにリダイレクトされるかどうかを確かめてみましょう。
→ ためしに~/user/2/editにアクセスしようとすると、homeに飛ばされます。

 

【10.3.3 ページネーション 演習】

1. Railsコンソールを開き、pageオプションにnilをセットして実行すると、1ページ目のユーザーが取得できることを確認してみましょう。
→ 下記

>> User.paginate(page: nil)
  User Load (1.0ms)  SELECT  "users".* FROM "users" LIMIT ? OFFSET ?  [["LIMIT", 11], ["OFFSET", 0]]
   (0.2ms)  SELECT COUNT(*) FROM "users"
=> #<ActiveRecord::Relation [#<User id: 1, name: "Example User", email: "[email protected]", 
以下、長いから省略

 
2. 先ほどの演習課題で取得したpaginationオブジェクトは、何クラスでしょうか? また、User.allのクラスとどこが違うでしょうか? 比較してみてください。
→ User::ActiveRecord_Relationクラス。一緒ですね。

>> User.paginate(page: nil).class
=> User::ActiveRecord_Relation
>> User.paginate(page: nil).class.superclass
=> ActiveRecord::Relation
>> User.all.class
=> User::ActiveRecord_Relation

 

【10.3.4 ユーザー一覧のテスト メモと演習】

 ページネーションには他にもKaminariやPagyなど、いろいろなメソッドがある。(今後試してみよう)

1. 試しにリスト 10.45にあるページネーションのリンク (will_paginateの部分) を2つともコメントアウトしてみて、リスト 10.48のテストが redに変わるかどうか確かめてみましょう。
→ 当然REDです。

 
2. 先ほどは2つともコメントアウトしましたが、1つだけコメントアウトした場合、テストが greenのままであることを確認してみましょう。will_paginateのリンクが2つとも存在していることをテストしたい場合は、どのようなテストを追加すれば良いでしょうか? ヒント: 表 5.2を参考にして、数をカウントするテストを追加してみましょう。
→ GREENのままなので、count: 2を追加。

users_index_test.rb
test "index including pagination" do
    log_in_as(@user)
    get users_path
    assert_template 'users/index'
    assert_select 'div.pagination', count: 2
    User.paginate(page: 1).each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.name
    end
  end

 

【10.3.5 パーシャルのリファクタリング 演習】

1. リスト 10.52にあるrenderの行をコメントアウトし、テストの結果が redに変わることを確認してみましょう。
→ REDになります。

 

【10.4.1 管理ユーザー 演習】

1. Web経由でadmin属性を変更できないことを確認してみましょう。具体的には、リスト 10.56に示したように、PATCHを直接ユーザーのURL (/users/:id) に送信するテストを作成してみてください。テストが正しい振る舞いをしているかどうか確信を得るために、まずはadminをuser_paramsメソッド内の許可されたパラメータ一覧に追加するところから始めてみましょう。最初のテストの結果は redになるはずです。
→ いまいちFILL_INに何を入れたらいいか分からなかったので調べたところ、下記の解答に。(先にUserコントローラ内のuser_paramsメソッドのpermitに:adminを追加しています)

users_controller_test.rb
  test "should not allow the admin attribute to be edited via the web" do
    log_in_as(@other_user)
    assert_not @other_user.admin?
    patch user_path(@other_user), params: {
                                    user: { password:              @other_user.password,
                                            password_confirmation: @other_user.password,
                                            admin: true } }
    assert_not @other_user.reload.admin?
  end

 あれー?でもGREENになるよ?解答書いてる人たちは本当にREDになったのかな??ってことで調べるとこの記事が。@other_user.passwordやったらあかんやん!ってことで"password"に変えたらREDになりました。その後permitから:adminを消してテストはGREENです。

 

【10.4.2 destroyアクション 演習】

1. 管理者ユーザーとしてログインし、試しにサンプルユーザを2〜3人削除してみましょう。ユーザーを削除すると、Railsサーバーのログにはどのような情報が表示されるでしょうか?
→ データベースから該当IDのユーザーをDELETEしているのが分かります。

 

【10.4.3 ユーザー削除のテスト 演習】

1. 試しにリスト 10.59にある管理者ユーザーのbeforeフィルターをコメントアウトしてみて、テストの結果が redに変わることを確認してみましょう。
→ 無事REDでした。

 

第10章まとめ

・edit,update,deleteを実装。
・indexでユーザー一覧を表示。ページネーションをジェムで実装。
・admin属性を付与しユーザーの管理権限を実装。論理値を返すadmin?が使えるように。
・befoure_actionで特定アクションの前に特定メソッドを実行。(after_actionもあるよ)
・フレンドリーフォワーディング=元行きたかったページリクエスト(GETのみ)をセッションにstoreし、ログイン後にリダイレクト。その後、そのstoreしたセッションは削除。
・db/seeds.rbにサンプルデータ(ユーザー)を作成。

 
 それなりにボリュームのあった第10章が終わりました。これで基本的な機能が一通り実装できました。演習に悩む場面はありますが、調べれば理解できない内容はありません。落ち着いて取り組んでいきましょう。
 次だ次!第11章!…あ、メールアドレス使った認証機能かあ…(遠い目)。こうなってる理由は次章以降で説明します。

 
⇨ 第11章へ!
⇦ 第9章はこちら
学習にあたっての前提・著者ステータスはこちら
 

なんとなくイメージを掴む用語集

・フォワーディング(forwarding)
 何かを転送すること。フレンドリーフォワーディングは「親切な転送」ってところか。