演習1
target="_blank"で新しいページを開くと、古いブラウザでセキュリティ上の小さな問題が生じます。それは、リンク先のサイトがHTMLドキュメントのwindowオブジェクトを扱えてしまう、という点です。具体的には、フィッシング(Phising)サイトのような、悪意のあるコンテンツを導入されてしまう可能性があります。Gravatarのような著名なサイトではこのような事態はめったに起きないと思いますが、念のため、このセキュリティ上のリスクも排除しておきましょう。対処方法は、リンク用のaタグのrel(relationship)属性に、"noopener"と設定するだけです。リスト 10.2で使ったGravatarの編集ページへのリンクでこの設定を行ってください。
edit.html.erbにのaタグに以下を付け足します
<div class="gravatar_edit">
<%= gravatar_for @user %>
<a href="https://gravatar.com/emails" target="_blank" rel="noopener">change</a>
</div>
</div>
</div>
リスト 10.5のパーシャルを使って、new.html.erbビュー(リスト 10.6)とedit.html.erbビュー(リスト 10.7)をリファクタリングし、コードの重複を解消してください。(ヒント: 3.4.3で使ったprovideメソッドを使うと、重複を取り除けます3 。)
まずはパーシャル用の_form.html.erbを作成しましょう
app/views/users/_form.html.erb
<%= form_with(model: @user) 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 %>
そしてnew.html.erbとedit.html.erbに以下の記述を加え、formの部分を分割します
<% provide(:url, signup_path) %>
edit.html.erb
<% provide(:title, 'Edit user') %>
<% provide(:button_text, 'Save changes') %>
<% provide(:url, signup_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="https://gravatar.com/emails" target="_blank">Change</a>
</div>
</div>
</div>
new.html.erb
<% provide(:title, 'Sign up') %>
<% provide(:button_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>
演習2
編集フォームから有効でないユーザー名やメールアドレス、パスワードを使って送信した場合、編集に失敗することを確認してみましょう。
編集ページから何でも良いので編集に失敗しましょう。今回は全て空で送信し、編集に失敗することを確認します
演習3
リスト 10.9のテストに1行追加して、エラーメッセージが正しい個数で表示されているかテストしてみましょう。(ヒント: 表 5.2で紹介したassert_selectを使ってalertクラスのdivタグを探しだし、「The form contains 4 errors.」というテキストを精査してみましょう。)
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), params: { user: { name: "",
email: "foo@invalid",
password: "foo",
password_confirmation: "bar" } }
assert_template 'users/edit'
assert_select "div.alert","The form contains 4 errors."
end
end
演習4
実際に編集が成功するかどうか、有効な情報を送信して確かめてみましょう。
プロフィール編集が出来れば成功です。
Gravatarと紐付いていない適当なメールアドレス(foobar@example.comなど)に変更すると、プロフィール画像はどのように表示されるでしょうか? 実際に編集フォームでメールアドレスを変更して確認してみましょう。
デフォルトのアイコンに切り替わります
演習5
デフォルトのbeforeフィルターは、すべてのアクションに対して制限を加えます。今回のケースだと、ログインページやユーザー登録ページにも制限の範囲が及んでしまい、結果としてテストも失敗するはずです。リスト 10.15のonly:オプションをコメントアウトしてみて、テストスイートがそのエラーを検知できるかどうか(テストが失敗するかどうか)確かめてみましょう。
この部分をコメントアウトしてみましょう。テストは失敗するはずです
before_action :logged_in_user, only: [:edit, :update]
演習6
editアクションとupdateアクションを両方とも保護する必要がある理由は何でしょうか?考えてみてください。
回答は様々だと思いますが、一般論で書くとedit(編集)もupdate(更新)も自分の名前やパスワードを入力するのでプライベートかつデリケートな部分です。それを保護しないというのは公衆トイレで鍵を掛けずに用を足すのと同じですね。気が気じゃありません。なのでどちらも保護する必要性があるというわけです。
上記のアクションのうち、どちらがブラウザで簡単にテストできますか?
editは編集ページ(get)にあたる部分ですので、そちらをテストするのが簡単ですね。
updateは更新作業自体は表では見ることが出来ないので、editの方が簡単そうです
演習7
フレンドリーフォワーディングで、渡されたURLに転送されるのが初回のみであることを、テストを書いて確認してみましょう。次回以降のログインでは、転送先のURLをデフォルトのプロフィール画面に戻しておく必要があります。(ヒント: リスト 10.30のsession[:forwarding_url]が正しい値かどうか確認するテストを追加してみましょう。)
2週目以降の課題
7.1.3で紹介したdebuggerメソッドをSessionsコントローラのnewアクションに置いてみましょう。その後、ログアウトして/users/1/editにアクセスしてみてください。デバッガーによって処理が途中で止まるはずです。ここでコンソール画面に移動し、session[:forwarding_url]の値が正しいかどうか確認してみましょう。また、newアクションにアクセスしたときのrequest.get?の値も確認してみましょう。なお、デバッガーを使っている最中に、ターミナルが不意に動かなくなったり挙動がおかしくなったりすることもあります。そのようなときは、熟練開発者の精神を思い出しながら落ち着いて対処しましょう(コラム 1.2)。
2週目以降の課題
演習8
レイアウトにあるすべてのリンクに対して統合テストを書いてみましょう。ログイン済みユーザーとそうでないユーザーのそれぞれに対して、正しい振る舞いを考えてください。(ヒント: log_in_asヘルパーを使ってリスト 5.31にテストを追加してみましょう。)
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=?]", about_path
assert_select "a[href=?]", signup_path
get contact_path
assert_select "title", full_title("Contact")
end
def setup
@user = users(:michael)
end
#ログイン済みのユーザー
test "layout links when logged in user" do
log_in_as(@user)
get root_path
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
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
#ログインしていないユーザー
test "layout links when not logged in user" do
get users_path
follow_redirect!
assert_template 'sessions/new'
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
end
end
演習9
試しに他のユーザーの編集ページにアクセスしてみて、10.2.2で実装した通りにリダイレクトされるかどうかを確かめてみましょう。
上のURLから users/5/editなど、自分以外のユーザーの編集ページに移ってみましょう。TOPページにリダイレクトされていればOKです
演習10
Railsコンソールを開き、pageオプションにnilをセットして実行すると、1ページ目のユーザーが取得できることを確認してみましょう。
$ rails c
Loading development environment (Rails 7.0.4.3)
irb(main):001:0> User.paginate(page: nil)
User Load (0.2ms) SELECT "users".* FROM "users" LIMIT ? OFFSET ? [["LIMIT", 30], ["OFFSET", 0]]
=>
[#<User:0x00007f1ef9f5c8b8
id: 1,
name: "Example User",
email: "example@railstutorial.org",
created_at: Sat, 30 Sep 2023 07:30:28.631514000 UTC +00:00,
updated_at: Sat, 30 Sep 2023 07:30:28.631514000 UTC +00:00,
password_digest: "[FILTERED]",
remember_digest: nil>,
#<User:0x00007f1efbdfc958
id: 2,
name: "Barbera Schroeder CPA",
email: "example-1@railstutorial.org",
created_at: Sat, 30 Sep 2023 07:30:29.951966000 UTC +00:00,
updated_at: Sat, 30 Sep 2023 07:30:29.951966000 UTC +00:00,
password_digest: "[FILTERED]",
remember_digest: nil>,
#<User:0x00007f1efbdfc458
id: 3,
name: "Kaye Boyer",
email: "example-2@railstutorial.org",
created_at: Sat, 30 Sep 2023 07:30:30.223217000 UTC +00:00,
updated_at: Sat, 30 Sep 2023 07:30:30.223217000 UTC +00:00,
password_digest: "[FILTERED]",
remember_digest: nil>,
#<User:0x00007f1efbdfc3b8
id: 4,
name: "Chu Trantow",
email: "example-3@railstutorial.org",
created_at: Sat, 30 Sep 2023 07:30:30.479479000 UTC +00:00,
updated_at: Sat, 30 Sep 2023 07:30:30.479479000 UTC +00:00,
password_digest: "[FILTERED]",
remember_digest: nil>,
#<User:0x00007f1efbdfc318
id: 5,
name: "Temeka Conroy",
email: "example-4@railstutorial.org",
created_at: Sat, 30 Sep 2023 07:30:30.735419000 UTC +00:00,
updated_at: Sat, 30 Sep 2023 07:30:30.735419000 UTC +00:00,
password_digest: "[FILTERED]",
remember_digest: nil>,
#<User:0x00007f1efbdfbf58
・
・
(中略)
id: 30,
name: "Elmer Bergstrom LLD",
email: "example-29@railstutorial.org",
created_at: Sat, 30 Sep 2023 07:30:37.336598000 UTC +00:00,
updated_at: Sat, 30 Sep 2023 07:30:37.336598000 UTC +00:00,
password_digest: "[FILTERED]",
remember_digest: nil>]
先ほどの演習課題で取得したpaginationオブジェクトは、どのクラスでしょうか? また、User.allのクラスとどこが違うかを比較してみてください。
先ほどの演習で使ったオブジェクトをpageに代入してあげて、classメソッドを使って調べることができます
page = User.paginate(page: nil)
page.class
=> User::ActiveRecord_Relation
演習11
試しにリスト 10.46にあるページネーションのリンク(will_paginateの部分)を2つともコメントアウトしてみて、リスト 10.49のテストが red に変わるかどうか確かめてみましょう。
rails testでredとなります
先ほどは2つともコメントアウトしましたが、1つだけコメントアウトした場合、テストが green のままであることを確認してみましょう。will_paginateのリンクが2つとも存在していることをテストしたい場合は、どのようなテストを追加すれば良いでしょうか?(ヒント: 表 5.2を参考にして、個数をカウントするテストを追加してみましょう。)
count: 2 を追加してみましょう
下記の状態で1つだけコメントアウトすると、pagenationクラスは1つしかないのでredになります
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
補足
※pagenationクラスどこなん?という方には、検証ツールでwill_paginateで生成されたページネーションを確認してもらえばpagenationクラスを見ることが出来ます
演習12
リスト 10.53にあるrenderの行をコメントアウトし、テストの結果が red に変わることを確認してみましょう。
結果がREDに変わることを確認しましょう
演習13
Web経由でadmin属性を変更できないことを確認してみましょう。具体的には、リスト 10.57に示したように、PATCHを直接ユーザーのURL(/users/:id)に送信するテストを作成してみてください。テストの振る舞いが正しいことを確信できるように、最初にadminをuser_paramsメソッド内の許可されたパラメータ一覧に追加しておきましょう。最初のテストの結果は red になるはずです。最後の行では、更新済みのユーザー情報をデータベースから読み込めることを確認します(6.1.5)。
test/controllers/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: "password",
password_confirmation: "password",
admin: true } }
assert_not @other_user.reload.admin?
end
演習14
管理者ユーザーとしてログインし、試しにサンプルユーザを2〜3人削除してみましょう。ユーザーを削除すると、Railsサーバーのログにはどのような情報が表示されるでしょうか?
まずは管理者としてログインしましょう。チュートリアルに沿って管理者を設定しているのであれば、最初のユーザーでログインします。
Usersページにユーザーリストと管理者であればdeleteのリンクがあると思うのでそちらをクリックすると
上部にアラートが表示されるのでOKを押しましょう
ログを確認してみましょう。
ログはlogの中のdevelopment.logに格納されています
削除項目を確認しづらい場合は「ctrl」+「F」で「started delete」など、単語検索すると便利です