ネストしたリソースのユーザー制御
完成したタスク管理アプリに、「ひとつのタスク内にサブタスクを作成できる機能」をアップデートで追加しました。
Subtaskモデルを作成したので、現在ルーティングはこうなっています。
Rails.application.routes.draw do get 'static_pages/home' root to: "static_pages#home" devise_for :users resources :users, only: [:index, :show] do resources :tasks do put :sort resources :subtasks, only: [:create, :new, :show, :update, :destroy] do put :sort end end end end
コントローラーのアクションもひと通り実装し、機能テストも作成しました。
ユーザー制御がややこしかったので、失敗するテストを先に書いてTDDで進めました。
例えばsubtasks#updateのURLは
/users/:user_id/tasks/:task_id/subtasks/:id
というふうに ID を3つ使います。
サブタスクモデルはtask_idでタスクモデルと紐付き、タスクモデルはuser_idでユーザーモデルと紐付いています。
user_idのみでユーザー制御をするとURLのtask_idを書き換えた場合他人の情報にアクセスできてしまうという問題がありました。
そして、考えに考えた結果実装した before_actionのメソッドが以下です。
#正しいユーザーかどうか確認する def correct_user if params[:id].present? @subtask = Subtask.find(params[:id]) @task = Task.find(@subtask.task_id) redirect_to(root_path, notice: "URLが間違っています") unless @task.user_id == current_user.id else @task = Task.find(params[:task_id]) @user = User.find(@task.user_id) redirect_to(root_path, notice: "URLが間違っています") unless @user == current_user end end
長いし、モデルを追加するたびにこの処理も追加しなくてはいけないのかと思うと先が思いやられます。
モデル側のバリデーションという形でユーザー制御を実施できるというような情報をチラッと見たので調べてみます。
テストファイル内の個々のテストやsetup、コントローラーのprivateアクションなどが複雑になってきました。
リファクタリングの必要がありそうです。
ちなみにPATCHリクエストを送る際、パラメータのtask_idを他人のIDに変更して送信できてしまうと結果として「他人のサブタスクを作成できてしまう」
状態になるのですが、それはストロングパラメーターの仕組みによって制御されています。
def subtask_params params.require(:subtask).permit( :title, :description, :row_order_position) end