tnkzw.sake

HomeSeriesTagsAbout

Rails Tutorialで学べることメモ-第10章-

Series
Railsチュートリアル

Rails Tutorial を改めてしっかりやり直してのメモ第 10 章編

https://railstutorial.jp/chapters/updating_and_deleting_users?version=7.0https://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#methodhttps://developer.mozilla.org/ja/docs/Web/HTML/Element/form#method

Rails ではPOSTと隠し input を使って、PATCHリクエストを偽装している。

また、ERB の記述がform_with(model: @user)であり、new と全く同じになる。
Rails は@user.new_record?を利用して、内部でPOSTPATCHか判別している。

存在性バリデーションと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へリクエストが飛ぶと実行される処理の流れを簡単に整理する。

  1. before_action :logged_in_userでログイン済みユーザーか検証
  2. before_action :admin_userで管理者特権を持つユーザーか確認
  3. 削除対象のユーザーを探して削除

これらの処理とログの番号の結びつきを詳細に見てみる。

1. logged_in_user

logged_in_userでは、logged_in?メソッドでログイン済みかどうかを評価している。
logged_in?を定義しているSessionsHelperを見てみると、current_usernilかどうかでログイン状態を判定している。

では、current_userメソッドは何をしているかといえば、session もしくは cookies の user_id を参照して、ユーザー情報を DB から取得している。

つまり、ログで ① と振った箇所はこのcurrent_user情報取得のための SQL を表している。

2. admin_user

次に実行されるのは、admin_userメソッドであり、こちらはcurrent_user.admin?を評価している。

したがって、またcurrent_userメソッドへのアクセスになるが、先ほど DB には問い合わせ済みなのでログにはCACHE User Loadとある。つまり、② のログは ① の結果を再利用している様子を表している。

3. UsersController#destroy

ようやく削除の処理に入ってくる。

かと思いきや、よく見ると ③ のログで削除予定のユーザー情報を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 アクションが全て実装され、updatedestroyなど特定ユーザーのみが実施可能であるべきアクションを制御する認可モデルの考え方や実装方法魔で学べる章だった。

Faker や will_paginate のような便利な gem も導入され、Rails で Web アプリケーションを実装していく実際の流れがイメージできる構成だと感じた。