【116日目】【1日20分のRailsチュートリアル】【第9章】演習の1.
今日は「9.6 演習」から。
9.6 演習
なお、演習とチュートリアル本編の食い違いを避ける方法については、演習用のトピックブランチに追加したメモ (3.6) を参考にしてください。
まず演習用にブランチ分けます。
$ git checkout updating-users $ git checkout -b updating-users-exercises
1.フレンドリーフォワーディングで、最初に与えられたURLにのみ確実に転送されていることを確認するテストを作成してください。
続けてログインを行った後、転送先のURLはデフォルト (プロフィール画面) に戻る必要もありますので、これもテストで確認してください。
ヒント: リスト9.26にsession[:forwarding_url]の正しい値を確認するテストを追加してください。
リスト9.26のテストに4行追加してみた。(# add
ってつけてるとこ)
test/integration/users_edit_test.rb
test "successful edit with friendly forwarding" do get edit_user_path(@user) assert session[:forwarding_url].include?(edit_user_path(@user)) # add log_in_as(@user) assert_redirected_to edit_user_path(@user) assert_nil session[:forwarding_url] # add 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 log_in_as(@user) # add assert_redirected_to user_path(@user) # add end
assert session[:forwarding_url].include?(edit_user_path(@user))
は間違ってる気しかしない。。。
assert_equal session[:forwarding_url], edit_user_path(@user)
だとセッションに保存してるURLがフルパス、edit_user_path(@user)が相対パス、でテストNGになった。
なので含まれているかどうかをテストするようにしたけど…もっといい書き方あるんじゃないかな。。
最後の2行もこれでいいのかよく分からない。でもテストは通った。
難しいなぁ…。
今日の学習時間は【35分】。
次は「9.6 演習」の2.から。
【115日目】【1日20分のRailsチュートリアル】【第9章】第9章の内容をコミット&本番環境で動かしてみる
今日は「9.5 最後に」から。
9.5 最後に
メールアドレスを使ってアカウントを有効化する機能と (すなわち本当に有効なメールアドレスか検証するプロセスと)、ユーザーがパスワードを忘れてしまったときのためのパスワードリセット機能です。
そんな機能まで…!
チュートリアルでしょ、って思ってたけどこれやれば実際のサービスに必要な最低限の機能は実装できるんだね。。
次の章に進む前に、すべての変更をmasterブランチにマージしておきましょう。
変更をコミットしてmasterブランチにマージする。
$ git add -A
$ git commit -m "Finish user edit, update, index, and destroy actions"
$ git checkout master
$ git merge updating-users
$ git push
コミットできました。
アプリケーションを本番展開したり、サンプルデータを本番データとして作成することもできます (本番データベースをリセットするにはpg:resetタスクを使用します)。
本番環境にもpushしておきましょう。
$ bundle exec rake test
$ git push heroku
$ heroku pg:reset DATABASE
$ heroku run rake db:migrate
$ heroku run rake db:seed
$ heroku restart
$ heroku pg:reset DATABASE
で下記のようなワーニングが表示されたので言われた通りにコンソールにinfinite-depths-40805
入力した。
データベースリセットしてもいいの?ほんとに?っていう警告っぽい。
! WARNING: Destructive Action ! This command will affect the app: infinite-depths-40805 ! To proceed, type "infinite-depths-40805" or re-run this command with --confirm infinite-depths-40805
それは、図9.16が示すように、サンプルユーザーの表示順序が変化してしまい、図9.11にあるようなローカル環境での表示順序と異なってしまうことです。
ほんとだ…1ページ目の一番上にあったはずの管理者権限のユーザーがいない。。。
4ページ目の一番下にいた。
9.5.1 本章のまとめ
・Strong Parametersを使うことで、安全にWeb上から更新させることができる
・beforeフィルターを使うと、特定のアクションが実行される直前にメソッドを呼び出すことができる
・beforeフィルターを使って、認可 (アクセス制御) を実現した
・認可に対するテストでは、特定のHTTPリクエストを直接送信する低級なテストと、ブラウザの操作をシミュレーションする高級なテスト (統合テスト) の2つを利用した
管理者権限も肝な機能だけどこの辺が難しいかなぁ…。1回じゃなかなか身につかない。
今日の学習時間は【33分】。
次は「9.6 演習」から。
【114日目】【1日20分のRailsチュートリアル】【第9章】ユーザー削除のテストを追加する
今日は「9.4.3 ユーザー削除のテスト」から。
9.4.3 ユーザー削除のテスト
ユーザー削除と同じくらい重要なことは、その振る舞いが期待されたかどうかを確かめる良いテストを書くことです。
そこで、まずはユーザー用fixtureファイルを修正し、今いるサンプルユーザーの一人を管理者にしてみます。
サンプルユーザーの1人目に管理者権限を追加。
test/fixtures/users.yml
michael: name: Michael Example email: michael@example.com password_digest: <%= User.digest('password') %> admin: true
9.2.1で経験してきたように、Usersコントローラをテストするために、アクション単位でアクセス制御をテストします。
(中略)
1つは、ログインしていないユーザーであれば、ログイン画面にリダイレクトされることです。もう1つは、ログイン済みではあっても管理者でなければ、ホーム画面にリダイレクトされることです。
DELETEリクエストを発行してdestroyアクションを動作させたときの挙動をテストする。
test/controllers/users_controller_test.rb
test "should redirect destroy when not logged in" do assert_no_difference 'User.count' do delete :destroy, id: @user end assert_redirected_to login_url end test "should redirect destroy when logged in as a non-admin" do log_in_as(@other_user) assert_no_difference 'User.count' do delete :destroy, id: @user end assert_redirected_to root_url end
このとき、リスト9.56ではassert_no_differenceメソッド (リスト7.21) を使って、ユーザー数が変化しないことを確認している点に注目してください。
ふーむ。
管理者であればユーザー一覧画面に削除リンクが表示される仕様を利用して、リスト9.44のテストに今回のテストを追加していくことにします。
元々あったページネーションを確認するテストに「削除」リンクをクリックしたときのテストも追加。
あと管理者権限をもたないユーザーがログインした場合は「削除」リンクが表示されていないテストも追加。
test/integration/users_index_test.rb
require 'test_helper' class UsersIndexTest < ActionDispatch::IntegrationTest def setup @admin = users(:michael) @non_admin = users(:archer) end test "index as admin including pagination and delete links" do log_in_as(@admin) get users_path assert_template 'users/index' assert_select 'div.pagination' first_page_of_users = User.paginate(page: 1) first_page_of_users.each do |user| assert_select 'a[href=?]', user_path(user), text: user.name unless user == @admin assert_select 'a[href=?]', user_path(user), text: 'delete' end end assert_difference 'User.count', -1 do delete user_path(@non_admin) end end test "index as non-admin" do log_in_as(@non_admin) get users_path assert_select 'a', text: 'delete', count: 0 end end
1個目のテストではページネーションの表示と1ページ目に表示されているユーザー(管理者ユーザー以外)に「削除」リンクが表示されていることを確認。
プラス、@non_admin
のユーザーを削除してユーザー数が1減ることも確認。
2個目のテストでは管理者権限をもたないユーザーがログインした場合は「削除」リンクが表示されていないことを確認。
うーん、説明読みながらなら解読はできるかな。。。
最後にテストを実行してテストが通ることを確認。
$ bundle exec rake test 40 tests, 152 assertions, 0 failures, 0 errors, 0 skips
今日の学習時間は【30分】。
次は「9.5 最後に」から。
9章の演習が迫ってきた…!
【113日目】【1日20分のRailsチュートリアル】【第9章】destroyアクションを実装する
今日は「9.4.2 destroyアクション」から。
9.4.2 destroyアクション
Usersリソースの最後の仕上げとして、destroyアクションへのリンクを追加しましょう。
まず各ユーザーにユーザー削除用のリンクを追加。
app/views/users/_user.html.erb
<% if current_user.admin? && !current_user?(user) %> | <%= link_to "delete", user, method: :delete, data: { confirm: "You sure?" } %> <% end %>
ログイン中のユーザーが管理者権限を持っていて、かつログイン中とは違うユーザーだったらリンクを表示する。
自分自身を削除できたら色々問題になるからかな。
追加できたら見た目見てみよう。
サーバーを起動して
$ rails server -b $IP -p $PORT
<ローカルアドレス>/users
にアクセス。
delete
リンクが表示されました。
この削除リンクが動作するためには、destroyアクション (表7.1) を追加する必要があります。
destroyアクションを追加。
app/controllers/users_controller.rb
: before_action :logged_in_user, only: [:index, :edit, :update, :destroy] : def destroy User.find(params[:id]).destroy flash[:success] = "User deleted" redirect_to users_url end
これでユーザー削除処理が実装できました。
と思いきや
ある程度の腕前を持つ攻撃者なら、コマンドラインでDELETEリクエストを直接発行するという方法でサイトの全ユーザーを削除してしまうことができるでしょう。
それは危ない。防衛しましょう。
9.2.1と9.2.2と同じように、今回はbeforeフィルターを使ってdestroyアクションへのアクセスを制御します。実装するadmin_userフィルターをリスト9.54に示します。
管理者権限をもつユーザーのみがdestroyアクションにアクセスできるようにコード追加。
app/controllers/users_controller.rb
: before_action :admin_user, only: :destroy : private : # 管理者かどうか確認 def admin_user redirect_to(root_url) unless current_user.admin? end end
これでユーザー削除処理の実装完了。
実際にユーザー削除してみたいけど追加し直すのもめんどくさいので動作確認はテストを書いて確認しよう。
今日の学習時間は【26分】。
次は「9.4.3 ユーザー削除のテスト」から。
【112日目】【1日20分のRailsチュートリアル】【第9章】Strong Parametersでadmin属性の変更をガードする
今日は「9.4.1 管理ユーザー」の「Strong Parameters、再び」から。
9.4.1 管理ユーザー
Strong Parameters、再び
任意のWebリクエストの初期化ハッシュをオブジェクトに渡せるとなると、攻撃者は以下のようなPATCHリクエストを送信してくるかもしれません。
patch /users/17?admin=1
リクエスト送られて改ざんできるのはやばいね。。。ガードしておかないと。
以下のようにparamsハッシュに対してrequireとpermitを呼び出します。
app/controllers/users_controller.rb
def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end
上記コードは7.3.2で追加済み。
上のコードでは、許可された属性リストにadminが含まれていないことに注目してください。これにより、任意のユーザーが自分自身にアプリケーションの管理者権限を与えることを防止できます。
許可された属性リストにadmin入れてたら変更できちゃうのか。
アプリケーションに登録した(悪意のある)任意のユーザーが自分自身に管理者権限を与えて好き放題する、ということを防げるんだね。
前の内容も読み返してたら時間かかった。
今日の学習時間は【18分】。
次は「9.4.2 destroyアクション」から。
【111日目】【1日20分のRailsチュートリアル】【第9章】管理ユーザーを識別するadmin属性を追加する
今日は「9.4 ユーザーを削除する」から。
9.4 ユーザーを削除する
削除を行うのに必要なdestroyアクションも実装します。しかしその前に、削除を実行できる権限を持つ管理 (admin) ユーザーのクラスを作成しましょう。
indexアクションの追加が終わったので次はdestroyアクション。
の前にユーザー削除を実行できる権限を持った管理ユーザーを実装する。
9.4.1 管理ユーザー
特権を持つ管理ユーザーを識別するために、論理値をとるadmin属性をUserモデルに追加します。
マイグレーションを実行してUserモデルにadmin属性を追加。
$ rails generate migration add_admin_to_users admin:boolean invoke active_record create db/migrate/20170215024617_add_admin_to_users.rb
リスト9.50では、default: falseという引数をadd_columnに追加しています。これは、デフォルトでは管理者になれないということを示すためです。
default: false
は追加しなくてもデフォルトで管理者になれないらしいけど、明記しておくことでRailsと開発者にコードの意図を示せるらしい。
というわけでadd_cloumにdefault: false
を追加。
db/migrate/20170215024617_add_admin_to_users.rb
class AddAdminToUsers < ActiveRecord::Migration def change add_column :users, :admin, :boolean, default: false end end
マイグレーションを更新できたら実行する。
$ bundle exec rake db:migrate : -- add_column(:users, :admin, :boolean, {:default=>false}) -> 0.0051s :
admin属性追加できました。
Rails consoleで動作を確認すると、期待どおりadmin属性が追加されて論理値をとり、さらに疑問符の付いたadmin?メソッドも利用できるようになっています。
Rails consoleでadmin?
メソッドが使えることを確認。
$ rails console --sandbox >> user = User.first >> user.admin? => false >> user.toggle!(:admin) => true >> user.admin? => true
仕上げに、最初のユーザーだけをデフォルトで管理者にするようサンプルデータを更新しましょう。
最初のテストユーザーだけadmin: true
を追加。
db/seeds.rb
User.create!(name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar", admin: true) :
次に、データベースをリセットして、サンプルデータを再度生成します。
$ bundle exec rake db:migrate:reset $ bundle exec rake db:seed
サンプルデータを再生成しました。
これで最初のユーザーだけ管理権限持ってるはず。
アプリケーション設計時にデータベース設計も済ませて実装する、ウォーターフォール型の開発が多かったので こうやって後からデータベースにカラム追加とかなんか新鮮。
でもこれできないと後から機能追加とかしにくいよね。上手くできてますなー
今日の学習時間は【20分】。
次は「9.4.1 管理ユーザー」の「Strong Parameters、再び」から。
【110日目】【1日20分のRailsチュートリアル】【第9章】パーシャルのリファクタリングを行う
今日は「9.3.5 パーシャルのリファクタリング」から。
9.3.5 パーシャルのリファクタリング
実はRailsにはコンパクトなビューを作成するための素晴らしいツールがいくつもあります。
この節ではそれらのツールを使用して一覧ページのリファクタリング (動作を変えずにコードを整理すること) を行うことにします。
へー。ツール使ってシンプルに書けるならそれに越したことはないね。
リファクタリングの第一歩は、リスト9.41のユーザーのliをrender呼び出しに置き換えることです (リスト9.46)。
<li>
タグ使ってた箇所を<%= render user %>
に変更。
app/views/users/index.html.erb
: <ul class="users"> <% @users.each do |user| %> <%= render user %> <% end %> </ul> :
この場合、Railsは自動的に_user.html.erbという名前のパーシャルを探します。このパーシャルを作成する必要があります (リスト9.47)。
touchコマンドでファイルを作成して、
$ touch app/views/users/_user.html.erb
下記内容に更新。
さっき置き換えた内容をパーシャルに持って来る感じ。
app/views/users/_user.html.erb
<li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li>
これは間違いなく大きな進歩です。しかしここで終わらせず、さらに改良してみましょう。今度はrenderを@users変数に対して直接実行します (リスト9.48)。
更にすっきりした。eachメソッドすらなくなった。
app/views/users/index.html.erb
: <ul class="users"> <%= render @users %> </ul> :
Railsは@usersをUserオブジェクトのリストであると推測します。さらに、ユーザーのコレクションを与えて呼び出すと、Railsは自動的にユーザーのコレクションを列挙し、それぞれのユーザーを_user.html.erbパーシャルで出力します。
へー。追加したパーシャルの方にeachメソッド移すわけじゃないんだ。
そんなツールがあるんだなぁ…。
リファクタリングを行う場合には、アプリケーションのコードを変更する前と後で必ずテストを実行し、いずれも成功になることを確認するようにしてください
リファクタリングできたのでテスト実行。
$ bundle exec rake test 37 tests, 117 assertions, 0 failures, 0 errors, 0 skips
問題ないですね。
今日の学習時間は【15分】。
次は「9.4 ユーザーを削除する」から。