LoginSignup
0
0

More than 1 year has passed since last update.

Railsで指定していないはずのコールバックメソッドが動作してしまう

Posted at

はじめに

現在、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'にルーティングを設定することにしました。

config/routes.rb
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メソッドが動作するようにしています。

controllers/users_controller.rb
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に書いています。

helpers/sessions_helper.rb
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を表示します。

views/admin.html.erb
<%= link_to "通知作成", new_notice_path %>
<%= link_to "ユーザー管理", users_admin_path %>
views/admin_users_index.html.erb
テスト

動作を確認する

スクリーンショット 2021-12-14 10.58.07.png

ユーザー管理をクリックすると、'/users/admin'に遷移して「テスト」が表示されてほしいのですが…

スクリーンショット 2021-12-14 10.58.56.png

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メソッドだけ呼び出すはず…💦

controllers/users_controller.rb
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をコメントアウトしてみることにしました。

controllers/users_controller.rb
class UsersController < ApplicationController
  # before_action :correct_user, only: %i[show edit update]
  before_action :admin_user, only: %i[admin admin_users_index]

  (省略)
end

動作確認

先程のエラーと内容が変わりました。
スクリーンショット 2021-12-14 11.48.33.png
今度はusers_controllerのshowアクションが呼び出されています。なんで…???

ルーティングを変えてみる

「'/users/admin'に遷移しようとしたとき、何らかの原因でusersのshowが動作している」と仮説を立ててみました。ならば、ルーティングを変えたら動作するかもしれないと思ったので、URLを変えてみます。

config/routes.rb
Rails.application.routes.draw do
  resources :users
  get '/admin/users', to: 'users#admin_users_index'
end

adminページのURLを'/users/admin'から'/admin/users'に変更してみました。

 動作した

'/admin/users'にアクセスしてみると、表示できるようになりました。

スクリーンショット 2021-12-14 11.24.07.png

とりあえず動いたものの…

「動いたからヨシ!」と言いたい気持ちもありましたが、「なぜこうなったのか」を理解しないのは良くないと思い、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'でビューを表示できるようになりました。

config/routes.rb
Rails.application.routes.draw do
  get '/users/admin', to: 'users#admin_users_index'
  resources :users
end

最後に

自分の知識不足で、かなりの時間を費やしてしまいました。また、ルーティング1行で動作が全然違ってくることを身に沁みてわかることができました。Railsガイドをもっと早く見ればよかったと後悔しています😂

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0