はじめに
現在、Ruby on Railsにて睡眠を記録するアプリを作成しています。Userモデルの管理機能を追加しようとした際に、指定していないはずのコールバックメソッドが動作してしまうという現象に陥りました。備忘録として残すため、記事にすることにしました。
開発環境
Mac OS Big Sur 11.6
VSCode
Ruby 3.0.2p107
Rails 6.1.4.1
MySQL 8.0.27 for macos11.6 on arm64 (Homebrew)
原因
結論を先に述べます。原因はルーティング(routes.rb)でした。
Railsのルーティングは、ルーティングファイルの「上からの記載順に」マッチします。このため、たとえばresources :photosというルーティングがget 'photos/poll'よりも前の行にあれば、resources行のshowアクションがget行の記述よりも優先されますので、get行のルーティングは有効になりません。これを修正するには、get行をresources行よりも上の行に移動してください。これにより、get行がマッチするようになります。
Railsガイド v6.1 「2.2 CRUD、動詞、アクション」
以降は、具体例を示しながら上記の内容について解説します。
# 各ファイルについて
Userモデルやコントローラーなどは、基本の部分を作成済みの状態です。これから、管理者ユーザー用の、ユーザー一覧ページを作成しようとしています。ミスをしている状態を示したあと、原因の特定→修正の順で述べていきます。
routes.rb
name, emailなどを持つUserモデルに対して、リソースベースのルーティングを設定しました。また、ユーザーの一覧ページを作成し、ユーザーの削除をできるようにしたいと考え、'/users/admin'にルーティングを設定することにしました。
Rails.application.routes.draw do
resources :users
get '/users/admin', to: 'users#admin_users_index'
end
有効になっているルーティングは以下の通りです。
$ rails routes | grep users
users GET /users(.:format) users#index
POST /users(.:format) users#create
new_user GET /users/new(.:format) users#new
edit_user GET /users/:id/edit(.:format) users#edit
user GET /users/:id(.:format) users#show
PATCH /users/:id(.:format) users#update
PUT /users/:id(.:format) users#update
DELETE /users/:id(.:format) users#destroy
users_admin GET /users/admin(.:format) users#admin_users_index
users_controller.rb
一部省略しています。show, edit, updateアクションには、before_actionでcorrect_userメソッドが動作するようにしています。同じように、admin, admin_users_indexには、before_actionでadmin_userメソッドが動作するようにしています。
class UsersController < ApplicationController
before_action :correct_user, only: %i[show edit update]
before_action :admin_user, only: %i[admin admin_users_index]
def index
end
def show
@user = User.find(params[:id])
@sleep_logs = SleepLog.where(user_id: @user.id)
end
def admin_users_index
@users = User.all
end
end
sessions_helper.rb
users_controllerで使用しているcorrect_userとadmin_userは、sessions_helperに書いています。
module SessionsHelper
# 正しいユーザーかどうかを確認する
def correct_user
@user = User.find(params[:id])
redirect_to(current_user) unless current_user?(@user)
end
# 管理者ユーザーか確認する
def admin_user
return if current_user.admin?
# flashで警告を表示する
access_alert
redirect_to user_url(current_user)
end
end
view
画面に表示されている「ユーザー管理」のリンクをクリックしたら、「テスト」を表示できるようにしたいです。ルーティングの一覧にある通り、users_admin_pathは、users_controllerのadmin_users_indexアクションを通して、admin_users_index.html.erbを表示します。
<%= link_to "通知作成", new_notice_path %>
<%= link_to "ユーザー管理", users_admin_path %>
テスト
動作を確認する
ユーザー管理をクリックすると、'/users/admin'に遷移して「テスト」が表示されてほしいのですが…
correct_userメソッドが呼び出され、「paramsのid='admin'に該当するユーザーのデータはない」と警告が出てしまいました。
設定していないはずなのに
期待している動作は、
users_admin_pathへ遷移しようとする
↓
before_actionでadmin_userメソッドを呼び出す
↓
admin_users_indexで全ユーザーの情報を取得
↓
admin_users_index.html.erbを表示
この通りなので、一体どこでcorrect_userが呼び出されてしまっているのか…全くわかりませんでした。
users_controllerを再確認
admin_users_indexアクションのときは、admin_userメソッドだけ呼び出すはず…💦
class UsersController < ApplicationController
before_action :correct_user, only: %i[show edit update]
before_action :admin_user, only: %i[admin admin_users_index]
(省略)
end
before_actionをコメントアウトしてみる
「before_actionのcorrect_userが動いているかも」と思ったので、before_actionをコメントアウトしてみることにしました。
class UsersController < ApplicationController
# before_action :correct_user, only: %i[show edit update]
before_action :admin_user, only: %i[admin admin_users_index]
(省略)
end
動作確認
先程のエラーと内容が変わりました。
今度はusers_controllerのshowアクションが呼び出されています。なんで…???
ルーティングを変えてみる
「'/users/admin'に遷移しようとしたとき、何らかの原因でusersのshowが動作している」と仮説を立ててみました。ならば、ルーティングを変えたら動作するかもしれないと思ったので、URLを変えてみます。
Rails.application.routes.draw do
resources :users
get '/admin/users', to: 'users#admin_users_index'
end
adminページのURLを'/users/admin'から'/admin/users'に変更してみました。
## 動作した
'/admin/users'にアクセスしてみると、表示できるようになりました。
とりあえず動いたものの…
「動いたからヨシ!」と言いたい気持ちもありましたが、「なぜこうなったのか」を理解しないのは良くないと思い、Railsガイドを見てみることにしました。
上記「原因」にもある通り…
Railsのルーティングは、ルーティングファイルの「上からの記載順に」マッチします。このため、たとえばresources :photosというルーティングがget 'photos/poll'よりも前の行にあれば、resources行のshowアクションがget行の記述よりも優先されますので、get行のルーティングは有効になりません。これを修正するには、get行をresources行よりも上の行に移動してください。これにより、get行がマッチするようになります。
Railsガイド v6.1 「2.2 CRUD、動詞、アクション」
Railsガイドには、**まるっと自分の知りたいことが書いてありました。**これを今回の件に置き換えると
resources :usersというルーティングがget 'users/admin'よりも前の行にあれば、resources行のshowアクションがget行の記述よりも優先されますので、get行のルーティングは有効になりません。これを修正するには、get行をresources行よりも上の行に移動してください。
となります。
修正、そして解決
Railsガイドに沿って修正すると、期待通り'users/admin'でビューを表示できるようになりました。
Rails.application.routes.draw do
get '/users/admin', to: 'users#admin_users_index'
resources :users
end
最後に
自分の知識不足で、かなりの時間を費やしてしまいました。また、ルーティング1行で動作が全然違ってくることを身に沁みてわかることができました。Railsガイドをもっと早く見ればよかったと後悔しています😂