Ruby on Rails应用管理员无法删除他人慢跑记录问题
问题描述
我正在开发一款Web应用,用户可创建并管理自己的慢跑记录(jogs)。管理员用户应能删除任意慢跑记录,普通用户仅可删除自己的记录,经理用户同样仅能删除自己的记录。我已在控制器的destroy动作中配置了对应的权限条件,但删除功能出现异常:
作为管理员,我可以删除自己的慢跑记录,但无法删除其他用户创建的记录——删除按钮正常显示,点击后会跳转至root_url,无任何删除操作,也未出现错误提示。
相关代码
JogsController 代码
class JogsController < ApplicationController before_action :logged_in_user, only: [:create, :destroy] before_action :correct_user, only: :destroy before_action :set_jog, only: [:show, :edit, :update, :destroy] before_action :authorize_jog_deletion, only: :destroy def weekly_report @jog = Jog.find(params[:id]) @selected_week = params[:week].to_i if params[:week].present? end def create @jog = current_user.jogs.build(jog_params) if @jog.save flash[:success] = "Jog session created!" redirect_to root_url else @feed_items = current_user.feed.paginate(page: params[:page]) render 'static_pages/home' end end def destroy authorize_jog_deletion @jog.destroy flash[:success] = "Jog deleted" redirect_to request.referrer || root_url end private def jog_params params.require(:jog).permit(:distance_km, :duration_minutes, :date) end def correct_user @jog = current_user.jogs.find_by(id: params[:id]) redirect_to root_url if @jog.nil? end def set_jog @jog = Jog.find(params[:id]) end def authorize_jog_deletion unless current_user.admin? || (@jog.user == current_user) || (current_user.manager? && current_user == @jog.user) flash[:danger] = "You are not authorized to delete this jog" redirect_to (root_url) end end end
视图文件 _jog.html.erb
<div class="weekly-report-button-container"> <%= link_to "View Weekly Report", weekly_report_jog_path(jog), class: "btn btn-primary" %> </div class="weekly-report-button-container"> <li id="jog-<%= jog.id %>"> <%= link_to gravatar_for(jog.user, size: 50), jog.user %> <span class="user"><%= link_to jog.user.name, jog.user %></span> <span class="content"> Distance: <%= jog.distance_km %> km | Duration: <%= jog.duration_minutes %> minutes | Date: <%= jog.date.strftime('%Y-%m-%d') %> </span> <span class="average-speed">Average Speed: <%= jog.average_speed %> km/h</span> <span class="timestamp"> Jogged on the site <%= time_ago_in_words(jog.date) %> ago. <% if current_user.admin? || (current_user.manager? && jog.user_id == current_user.id) || (jog.user_id == current_user.id) %> <%= button_to "delete", jog_path(jog), method: :delete, data: { confirm: "Are you sure you want to delete this jog?" } %> <% end %> </span> </li>
Jog 模型代码
class Jog < ApplicationRecord belongs_to :user validates :user_id, presence: true validates :date, presence: true , inclusion: { in: (Date.new(2020)..Date.today) } validates :distance_km, presence: true, numericality: { greater_than: 0 } validates :duration_minutes, presence: true def admin? admin end def manager? manager end def self.calculate_weekly_total_distance(user) weekly_total_distance = {} user.jogs.each do |jog| week = jog.date.cweek weekly_total_distance[week] ||= 0 weekly_total_distance[week] += jog.distance_km end weekly_total_distance end def average_speed return 0 if duration_minutes.zero? (distance_km / duration_hours).round(2) end private def duration_hours duration_minutes / 60.0 end end
路由文件 routes.rb
Rails.application.routes.draw do root 'static_pages#home' get 'help' => 'static_pages#help' get 'about' => 'static_pages#about' get 'contact' => 'static_pages#contact' get 'signup' => 'users#new' get 'login' => 'sessions#new' post 'login' => 'sessions#create' delete 'logout' => 'sessions#destroy' resources :users do member do get :following, :followers end end resources :users resources :account_activations, only: [:edit] resources :password_resets, only: [:new, :create, :edit, :update] resources :jogs, only: [:create, :destroy] resources :relationships, only: [:create, :destroy] resources :jogs do member do get :weekly_report end end end
已尝试操作
- 将删除链接改为按钮
- 确认控制器destroy动作中的条件设置无误
- 应用中其他删除功能均可正常使用
问题排查与修复
核心问题:before_action 执行顺序错误
你的JogsController中,before_action的执行顺序是:
logged_in_usercorrect_userset_jogauthorize_jog_deletion
当管理员尝试删除其他用户的 jog 时,correct_user方法会先执行:
def correct_user @jog = current_user.jogs.find_by(id: params[:id]) redirect_to root_url if @jog.nil? end
这个方法会尝试从当前用户的 jogs 中查找目标记录,而管理员要删除的是其他用户的 jog,所以@jog会是nil,直接触发重定向到root_url,后续的set_jog和authorize_jog_deletion根本不会执行,这就是为什么删除操作没执行就跳转了。
修复方案
- 调整before_action执行顺序:确保
set_jog在correct_user和authorize_jog_deletion之前执行,让@jog先被正确赋值:
class JogsController < ApplicationController before_action :logged_in_user, only: [:create, :destroy] before_action :set_jog, only: [:show, :edit, :update, :destroy] # 移到最前面 before_action :correct_user, only: :destroy before_action :authorize_jog_deletion, only: :destroy # ... 其他方法保持不变
- 修改correct_user方法:让管理员跳过所属用户验证:
def correct_user # 管理员无需验证记录所属用户 return if current_user.admin? @jog = current_user.jogs.find_by(id: params[:id]) redirect_to root_url if @jog.nil? end
- 简化authorize_jog_deletion方法:经理用户的权限逻辑和普通用户一致,无需重复判断:
def authorize_jog_deletion unless current_user.admin? || @jog.user == current_user flash[:danger] = "You are not authorized to delete this jog" redirect_to root_url end end
额外优化点
- 合并重复路由定义:路由文件中重复定义了
resources :jogs,合并为一个避免冲突:
resources :jogs, only: [:create, :destroy] do member do get :weekly_report end end
删除原来单独的resources :jogs, only: [:create, :destroy]和resources :jogs do ... end。
- 简化视图删除按钮条件:经理用户的判断可以合并到普通用户逻辑中:
<% if current_user.admin? || jog.user_id == current_user.id %> <%= button_to "delete", jog_path(jog), method: :delete, data: { confirm: "Are you sure you want to delete this jog?" } %> <% end %>
内容的提问来源于stack exchange,提问作者agent000flour
相关产品推荐
相关产品推荐

