Rails Tutorialで学べることメモ-第10章-
Rails Tutorial を改めてしっかりやり直してのメモ第 10 章編
https://railstutorial.jp/chapters/updating_and_deleting_users?version=7.0
便利なコマンド・メソッド
before_action :method, only: %i[action1 action2]
Controller のアクション前に実行する処理を設定できる。
デフォルトでは全てのアクションの前に処理が実行されるが、only
オプションに配列を渡すことで処理を行うアクションをフィルターできる。
request.original_url
request
オブジェクトにはリクエストに関する様々な情報が入っている。
これはリクエスト先が取得できる。
Faker::Name.name
Faker という gem のメソッド。実際にいそうな架空のユーザ名を作成してくれる。
rails db:reset
次の二つの操作を一気に行うコマンド
$ rails db:migrate:reset
$ rails db:seed
後者のコマンドはdb/seeds.rb
に定義したコードで、初期データを生成できる。
toggle!
ActiveRecord モデルの boolean 属性を引数に、DB 上の値と反転してアップデートするメソッド。
知識
Patch form
HTML の form タグに設定するmethod
では、PATCH
は設定できない。
https://developer.mozilla.org/ja/docs/Web/HTML/Element/form#method
Rails ではPOST
と隠し input を使って、PATCH
リクエストを偽装している。
また、ERB の記述がform_with(model: @user)
であり、new と全く同じになる。
Rails は@user.new_record?
を利用して、内部でPOST
かPATCH
か判別している。
has_secure_password
存在性バリデーションとUser に設定するpassword
属性に対して、allow_nil: true
を設定することができる。
この設定は、User#update 時などに毎度パスワードを入力しなくても良いように設定される。
この設定をすると初期のユーザー作成時も空のパスワードが有効になるように見える。
しかし、実際にはhas_secure_password
が存在性チェックを行ってくれるので問題にならない。
認証と認可
日本語でも英語でもややこしいこの 2 つの言葉は、次のような意味の違いがある。
- 認証(Authentication)
- ユーザーを識別すること。その本人だと証明して、特定すること。
- 認可(Authorization)
- そのユーザーが実行可能な操作を管理すること。アクセスできる情報、変更できる情報などの管理。
免許証で例えてみると、
- それ自体で本人の確認ができるので免許証は認証情報
- 免許証下部の「種類」は操作できるリソース(車両の種類)を表すので認可情報
のように言える。ざっくりの意味理解のための例えなので、細かなニュアンスの違いは目を瞑る。
個人的には言葉がややこしすぎるので、一旦このような例えで大きな意味の差を理解することが大事なように思う。
フレンドリーフォワーディング
ログインしていないユーザーが認可外ページにアクセスしようとしてログインページに飛ばされた際、ログイン後に元々アクセスしたかったページにリダイレクトさせてあげること。
Pagination
一覧ページで全レコードを表示せず、30 レコードずつ表示するような挙動をページネーションという。
Rails にはページネーションを実現する代表的な gem がいくつかある。
will_paginate
kaminari
Pagy
Rails の強力な Partial
Users インデックスページで次のような記述があったとする。
<ul class="users">
<% @users.each do |user| %>
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
</li>
<% end %>
</ul>
これまでの知識から、パーシャルを使うと次のような記述になる。
<ul class="users">
<% @users.each do |user| %>
<%= render user %>
<% end %>
</ul>
app/views/users/_user.html.erb
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
</li>
each
メソッド内の変数user
は User モデルのオブジェクトなので、Rails はusers/_user
というパスを探しに行くという仕組みである。
そしておかしいやろ!!と思わず突っ込んだ、次のような記述方法もある。なんというか Rails っぽい。
<ul class="users">
<%= render @users %>
</ul>
先ほどのパーシャルはそのまま利用する。Rails は User オブジェクトのコレクションであることから、勝手にイテレーションを回して_user
パーシャルをレンダリングしてくれるらしい。
DELETE リクエスト時のログ
演習に取り上げられていたdestroy
へのリクエストのログを見てみる。
この章では少なくとも解説がなかったのと、ログを見ると他の言語ではエンジニアがマニュアル実装しているような処理が見える(つまり Web アプリケーションとしての動きの全容に近い部分が見える)ので、この簡単なログを取り上げたい。
Started DELETE "/users/8" for 192.168.xxx.xxx at 2024-07-21 08:59:01 +0000
Cannot render console from 192.168.xxx.xxx! Allowed networks: 127.0.0.0/127.255.255.255, ::1
Processing by UsersController#destroy as TURBO_STREAM
Parameters: {"id"=>"8"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] ---①
↳ app/helpers/sessions_helper.rb:20:in `current_user'
CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] ---②
↳ app/helpers/sessions_helper.rb:20:in `current_user'
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 8], ["LIMIT", 1]] ---③
↳ app/controllers/users_controller.rb:43:in `destroy'
TRANSACTION (0.0ms) begin transaction
↳ app/controllers/users_controller.rb:43:in `destroy'
User Destroy (0.8ms) DELETE FROM "users" WHERE "users"."id" = ? [["id", 8]] ---④
↳ app/controllers/users_controller.rb:43:in `destroy'
TRANSACTION (1.1ms) commit transaction
↳ app/controllers/users_controller.rb:43:in `destroy'
Redirected to http://localhost:3000/users
Completed 303 See Other in 15ms (ActiveRecord: 2.6ms | Allocations: 7595)
見やすいように改行や番号を入れている。
ログの詳細の前に、UsersController#destroy
へリクエストが飛ぶと実行される処理の流れを簡単に整理する。
before_action :logged_in_user
でログイン済みユーザーか検証before_action :admin_user
で管理者特権を持つユーザーか確認- 削除対象のユーザーを探して削除
これらの処理とログの番号の結びつきを詳細に見てみる。
logged_in_user
1. logged_in_user
では、logged_in?
メソッドでログイン済みかどうかを評価している。
logged_in?
を定義しているSessionsHelper
を見てみると、current_user
がnil
かどうかでログイン状態を判定している。
では、current_user
メソッドは何をしているかといえば、session もしくは cookies の user_id を参照して、ユーザー情報を DB から取得している。
つまり、ログで ① と振った箇所はこのcurrent_user
情報取得のための SQL を表している。
admin_user
2. 次に実行されるのは、admin_user
メソッドであり、こちらはcurrent_user.admin?
を評価している。
したがって、またcurrent_user
メソッドへのアクセスになるが、先ほど DB には問い合わせ済みなのでログにはCACHE User Load
とある。つまり、② のログは ① の結果を再利用している様子を表している。
UsersController#destroy
3. ようやく削除の処理に入ってくる。
かと思いきや、よく見ると ③ のログで削除予定のユーザー情報をSELECT
つまり単に取得している。
おそらく今回の処理ではこの SELECT は必ずしも必要ではないが、依存関係の処理等の機能に関わった挙動の可能性がある。
Rails ではhas_many
など依存関係を指定する際に、関連モデルが削除された場合の振る舞いを指定できる。
つまり、親モデルが削除された際に子モデルも一緒に削除するような処理が簡単に書ける。
DB で外部キー制約をかけた場合は、参照されているレコードを削除しようとするとエラーが発生する。
こういったエラーを回避したり、正常な処理を行うために一度削除予定のモデルを取得していると考えられる。
その後、ようやく削除が実行される。
transaction
は DB の一連の処理であり、トランザクションの間にエラーが発生した場合はそこまでの処理を DB に反映せず、元の状態に戻す。
今回は単純な 1 レコードの削除なので、トランザクション間には ④ のDELETE
を行う SQL のみが存在しており、正常によりは終わるので DB に変更を反映(commit
)している。
そして、リクエスト自体は303 See Other
で終了していることが最後のログからわかる。
細かな知識
before_action
は以前before_filter
という名称だったDISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rails db:migrate:reset
のようにDISABLE_DATABASE_ENVIRONMENT_CHECK=1
とすると本番環境のデータリセットが可能になる
まとめ
User モデルに対する REST アクションが全て実装され、update
やdestroy
など特定ユーザーのみが実施可能であるべきアクションを制御する認可モデルの考え方や実装方法魔で学べる章だった。
Faker や will_paginate のような便利な gem も導入され、Rails で Web アプリケーションを実装していく実際の流れがイメージできる構成だと感じた。