演習
先ほど触れたように、target="_blank"で新しいページを開くときには、セキュリティ上の小さな問題があります。それは、リンク先のサイトがHTMLドキュメントのwindowオブジェクトを扱えてしまう、という点です。具体的には、フィッシング (Phising) サイトのような、悪意のあるコンテンツを導入させられてしまう可能性があります。Gravatarのような著名なサイトではこのような事態は起こらないと思いますが、念のため、このセキュリティ上のリスクも排除しておきましょう。対処方法は、リンク用のaタグのrel (relationship) 属性に、"noopener"と設定するだけです。早速、リスト 10.2で使ったGravatarの編集ページへのリンクにこの設定をしてみましょう。
リスト 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に渡してみるでしょう。)
リスト 10.
1
<a href="http://gravatar.com/emails" target="_blank" rel ="noopener">change</a>
2
<%= form_for(@user, url: signup_path) 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 %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit yield(:button_text), class: "btn btn-primary" %>
<% end %>
<% provide(:title, 'Sign up') %>
<% provide(:button_text, 'Create my account') %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= render 'form' %>
</div>
</div>
<% provide(:title, "Edit user") %>
<% provide(:button_text, 'Save changes') %>
<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>
10.1.2
演習
編集フォームから有効でないユーザー名やメールアドレス、パスワードを使って送信した場合、編集に失敗することを確認してみましょう。
確認
10.1.3
演習
リスト 10.9のテストに1行追加し、正しい数のエラーメッセージが表示されているかテストしてみてましょう。ヒント: 表 5.2で紹介したassert_selectを使ってalertクラスのdivタグを探しだし、「The form contains 4 errors.」というテキストを精査してみましょう。
assert_select "div.alert", "The form contains 4 errors."
10.2.1
演習
デフォルトのbeforeフィルターは、すべてのアクションに対して制限を加えます。今回のケースだと、ログインページやユーザー登録ページにも制限の範囲が及んでしまうはずです (結果としてテストも失敗するはずです)。リスト 10.15のonly:オプションをコメントアウトしてみて、テストスイートがそのエラーを検知できるかどうか (テストが失敗するかどうか) 確かめてみましょう
FAIL["test_invalid_signup_information", UsersSignupTest, 0.16151111999897694]
test_invalid_signup_information#UsersSignupTest (0.16s)
expecting <"users/new"> but rendering with <[]>
test/integration/users_signup_test.rb:13:in `block in <class:UsersSignupTest>'
FAIL["test_valid_signup_information", UsersSignupTest, 0.1693089410000539]
test_valid_signup_information#UsersSignupTest (0.17s)
"User.count" didn't change by 1.
Expected: 2
Actual: 1
test/integration/users_signup_test.rb:21:in `block in <class:UsersSignupTest>'
FAIL["test_should_get_new", UsersControllerTest, 0.18392029399910825]
test_should_get_new#UsersControllerTest (0.18s)
Expected response to be a <2XX: success>, but was a <302: Found> redirect to <http://www.example.com/login>
Response body: <html><body>You are being <a href="http://www.example.com/login">redirected</a>.</body></html>
test/controllers/users_controller_test.rb:11:in `block in <class:UsersControllerTest>'
FAIL["test_layout_links", SiteLayoutTest, 0.552733630000148]
test_layout_links#SiteLayoutTest (0.55s)
Expected at least 1 element matching "title", found 0..
Expected 0 to be >= 1.
test/integration/site_layout_test.rb:15:in `block in <class:SiteLayoutTest>'
31/31: [=========] 100% Time: 00:00:00, Time: 00:00:00
Finished in 0.66486s
31
10.2.2
演習
何故editアクションとupdateアクションを両方とも保護する必要があるのでしょうか? 考えてみてください。
上記のアクションのうち、どちらがブラウザで簡単にテストできるアクションでしょうか?
1
updateアクションにpatchを送る不正を防ぐため
2edit
10.2.3
演習
フレンドリーフォワーディングで、渡されたURLに初回のみ転送されていることを、テストを書いて確認してみましょう。次回以降のログインのときには、転送先のURLはデフォルト (プロフィール画面) に戻っている必要があります。ヒント: リスト 10.29のsession[:forwarding_url]が正しい値かどうか確認するテストを追加してみましょう。
7.1.3で紹介したdebuggerメソッドをSessionsコントローラのnewアクションに置いてみましょう。その後、ログアウトして /users/1/edit にアクセスしてみてください (デバッガーが途中で処理を止めるはずです)。ここでコンソールに移り、session[:forwarding_url]の値が正しいかどうか確認してみましょう。また、newアクションにアクセスしたときのrequest.get?の値も確認してみましょう (デバッガーを使っていると、ときどき予期せぬ箇所でターミナルが止まったり、おかしい挙動を見せたりします。熟練の開発者になった気になって (コラム 1.1)、落ち着いて対処してみましょう)。
1
assert_equal session[:forwarding_url], edit_user_url(@user)
を追加
2
(byebug) session[:forwarding_url]
"https://dfa974f98d004f37899984bee93a2473.vfs.cloud9.us-east-2.amazonaws.com/users/1/edit"
(byebug) request.get?
true
10.3.1
演習
レイアウトにあるすべてのリンクに対して統合テストを書いてみましょう。ログイン済みユーザーとそうでないユーザーのそれぞれに対して、正しい振る舞いを考えてください。ヒント: log_in_asヘルパーを使ってリスト 5.32にテストを追加してみましょう。
require 'test_helper'
class SiteLayoutTest < ActionDispatch::IntegrationTest
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=?]", login_path
get contact_path
assert_select "title", full_title("Contact")
get signup_path
assert_select "title", full_title("Sign up")
end
def setup
@user = users(:michael)
end
test "layout links when logged in" do
log_in_as(@user)
get root_path
assert_template 'static_pages/home'
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
10.3.3
演習
Railsコンソールを開き、pageオプションにnilをセットして実行すると、1ページ目のユーザーが取得できることを確認してみましょう。
先ほどの演習課題で取得したpaginationオブジェクトは、何クラスでしょうか? また、User.allのクラスとどこが違うでしょうか? 比較してみてください。
>> User.paginate(page: nil)
User Load (1.6ms) SELECT "users".* FROM "users" LIMIT ? OFFSET ? [["LIMIT", 11], ["OFFSET", 0]]
(0.1ms) SELECT COUNT(*) FROM "users"
=> #<ActiveRecord::Relation [#<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2019-04-19 03:03:48", updated_at: "2019-04-19 03:03:48", password_digest: "$2a$10$oRVP6/uf4bTsMnVdfJcESeKsQJrkJiQVRwpDijF7iQh...", remember_digest: nil>, #<User id: 2, name: "Mr. Elliot Gusikowski", email: "example-1@railstutorial.org", created_at: "2019-04-19 03:03:49", updated_at: "2019-04-19 03:03:49", password_digest: "$2a$10$Ap53rFBM7XbX1/eP3gIv0u9MP2bHC9sksAP95ivD3Ur...", remember_digest: nil>, #<User id: 3, name: "Mrs. Tyshawn Kiehn", email: "example-2@railstutorial.org", created_at: "2019-04-19 03:03:49", updated_at: "2019-04-19 03:03:49", password_digest: "$2a$10$Ag8GuXmtQ3KTzGVE7uLBsexsdBu9iFQYQeahGJxlSYx...", remember_digest: nil>, #<User id: 4, name: "Arvilla Mraz", email: "example-3@railstutorial.org", created_at: "2019-04-19 03:03:49", updated_at: "2019-04-19 03:03:49", password_digest: "$2a$10$5kM5R.Nc09o0qQ5cPXJpueoOB8hPkyiximVd2FLVVq2...", remember_digest: nil>, #<User id: 5, name: "Mrs. Giovani Gleichner", email: "example-4@railstutorial.org", created_at: "2019-04-19 03:03:49", updated_at: "2019-04-19 03:03:49", password_digest: "$2a$10$N9KN5.5eqLx.MWoZfUNyQutoIAIQr1vgIkhHVmy.Rg8...", remember_digest: nil>, #<User id: 6, name: "Clementine Fahey", email: "example-5@railstutorial.org", created_at: "2019-04-19 03:03:49", updated_at: "2019-04-19 03:03:49", password_digest: "$2a$10$jmpI4o2bGud9TDtFT/mcY.YTkYj4OaEzzddjCsu4iDr...", remember_digest: nil>, #<User id: 7, name: "Ashleigh Beatty III", email: "example-6@railstutorial.org", created_at: "2019-04-19 03:03:49", updated_at: "2019-04-19 03:03:49", password_digest: "$2a$10$eatM0i5qrVqFPiux4GcH4Oy/xci2bIYjQbp0WRvE5DU...", remember_digest: nil>, #<User id: 8, name: "Mozell Hand PhD", email: "example-7@railstutorial.org", created_at: "2019-04-19 03:03:49", updated_at: "2019-04-19 03:03:49", password_digest: "$2a$10
$HrtwtTD89CTq1r9W0PoLpe6UGF2VNrHC2V5JWKyjodn...", remember_digest: nil>, #<User id: 9, name: "Hubert Dach", email: "example-8@railstutorial.org", created_at: "2019-04-19 03:03:49", updated_at: "2019-04-19 03:03:49", password_digest: "$2a$10$gU8/szgZ/kwfzmvG/K0CG.c/T553KLdt.U/JORjZ.Wp...", remember_digest: nil>, #<User id: 10, name: "Alfonso Gleason", email: "example-9@railstutorial.org", created_at: "2019-04-19 03:03:50", updated_at: "2019-04-19 03:03:50", password_digest: "$2a$10$othdu0cgbuOHW5NGNIiYUuK3D4iIwwFijdThgJMQvKa...", remember_digest: nil>, ...]>
取得できる情報が多すぎる??
2
User::ActiveRecord_Relation User.allと同じ
10.3.4
演習
試しにリスト 10.45にあるページネーションのリンク (will_paginateの部分) を2つともコメントアウトしてみて、リスト 10.48のテストが redに変わるかどうか確かめてみましょう。
先ほどは2つともコメントアウトしましたが、1つだけコメントアウトした場合、テストが greenのままであることを確認してみましょう。will_paginateのリンクが2つとも存在していることをテストしたい場合は、どのようなテストを追加すれば良いでしょうか? ヒント: 表 5.2を参考にして、数をカウントするテストを追加してみましょう。
1
ec2-user:~/environment/sample_app (updating-users) $ rails test
Running via Spring preloader in process 14326
Started with run options --seed 170
FAIL["test_index_including_pagination", UsersIndexTest, 0.9265030969982035]
test_index_including_pagination#UsersIndexTest (0.93s)
Expected at least 1 element matching "div.pagination", found 0..
Expected 0 to be >= 1.
test/integration/users_index_test.rb:13:in `block in <class:UsersIndexTest>'
36/36: [=========] 100% Time: 00:00:01, Time: 00:00:01
Finished in 1.29562s
36 tests, 95 assertions, 1 failures, 0 errors, 0 skips
2
require 'test_helper'
class UsersIndexTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
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
end
10.3.5
演習
リスト 10.52にあるrenderの行をコメントアウトし、テストの結果が redに変わることを確認してみましょう。
ec2-user:~/environment/sample_app (updating-users) $ rails t
Running via Spring preloader in process 14980
Started with run options --seed 46492
FAIL["test_index_including_pagination", UsersIndexTest, 0.9864713970018784]
test_index_including_pagination#UsersIndexTest (0.99s)
Expected at least 1 element matching "a[href="/users/14035331"]", found 0..
Expected 0 to be >= 1.
test/integration/users_index_test.rb:15:in `block (2 levels) in <class:UsersIndexTest>'
test/integration/users_index_test.rb:14:in `block in <class:UsersIndexTest>'
36/36: [=========] 100% Time: 00:00:01, Time: 00:00:01
Finished in 1.27942s
36 tests, 96 assertions, 1 failures, 0 errors, 0 skips
10.4.1
演習
Web経由でadmin属性を変更できないことを確認してみましょう。具体的には、リスト 10.56に示したように、PATCHを直接ユーザーのURL (/users/:id) に送信するテストを作成してみてください。テストが正しい振る舞いをしているかどうか確信を得るために、まずはadminをuser_paramsメソッド内の許可されたパラメータ一覧に追加するところから始めてみましょう。最初のテストの結果は redになるはずです。
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_confirmation,
admin: true} }
assert_not @other_user.reload.admin?
end
10.4.2
演習
管理者ユーザーとしてログインし、試しにサンプルユーザを2〜3人削除してみましょう。ユーザーを削除すると、Railsサーバーのログにはどのような情報が表示されるでしょうか?
Started DELETE "/users/26" for 49.251.69.218 at 2019-04-19 14:15:18 +0000
Cannot render console from 49.251.69.218! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#destroy as HTML
Parameters: {"authenticity_token"=>"wTGANy5OLSwBiyfHuM1O4pIYdCodCFO8NOOVV/TmoEzbcrgh48DeLvQkZvYLCRl63McuzkmCW5xUL+W6CEXEYA==", "id"=>"26"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 26], ["LIMIT", 1]]
(0.1ms) begin transaction
SQL (1.7ms) DELETE FROM "users" WHERE "users"."id" = ? [["id", 26]]
(6.0ms) commit transaction
Redirected to https://dfa974f98d004f37899984bee93a2473.vfs.cloud9.us-east-2.amazonaws.com/users
Completed 302 Found in 14ms (ActiveRecord: 8.0ms)
10.4.3
演習
試しにリスト 10.59にある管理者ユーザーのbeforeフィルターをコメントアウトしてみて、テストの結果が redに変わることを確認してみましょう。
ec2-user:~/environment/sample_app (updating-users) $ rails t
Running via Spring preloader in process 15455
Started with run options --seed 42948
FAIL["test_should_redirect_destroy_when_logged_in_as_a_non-admin", UsersControllerTest, 0.3178548699943349]
test_should_redirect_destroy_when_logged_in_as_a_non-admin#UsersControllerTest (0.32s)
"User.count" didn't change by 0.
Expected: 34
Actual: 33
test/controllers/users_controller_test.rb:67:in `block in <class:UsersControllerTest>'
40/40: [=========] 100% Time: 00:00:01, Time: 00:00:01
Finished in 1.47976s
40 tests, 161 assertions, 1 failures, 0 errors, 0 skips