Rails中使用Pundit实现基于父模型Rental的Offer创建授权
Pundit 实现方案
不需要额外编写RentalPolicy处理这部分校验,所有和创建Offer相关的权限逻辑全部收敛在OfferPolicy中即可,核心是授权时传入绑定了父级@rental关联的Offer实例,就能在Policy层直接拿到关联的Rental数据。
第一步:调整OfferPolicy代码
修改Policy初始化逻辑,补全所有校验规则,同时增加属性存储校验失败时的提示信息和跳转路径,方便控制器层统一处理:
class OfferPolicy < ApplicationPolicy attr_reader :user, :offer attr_accessor :error_message, :redirect_path def initialize(user, offer) @user = user @offer = offer end def create? # 校验1:用户必须绑定Stripe账号 unless user.stripe_id? @error_message = "No stripe id." @redirect_path = Rails.application.routes.url_helpers.billing_path return false end rental = offer.rental # 校验2:不能给自己发布的租房信息发Offer if rental && rental.user_id == user.id @error_message = "Invalid action." @redirect_path = :back return false end # 校验3:已有被接受的Offer时不能新建 if user.offers.accepted.any? @error_message = "Already accepted offer." @redirect_path = :back return false end # 校验4:已经创建过Offer时不能重复创建 if Offer.exists?(user_id: user.id) @error_message = "Invalid." @redirect_path = :back return false end true end end
这里直接通过
offer.rental拿到父级Rental记录的前提是,控制器里传入授权方法的Offer实例已经提前绑定了对应的Rental关联。
第二步:精简控制器代码
先通过before_action提前加载@rental(通常从路由的rental_id参数查询得到),在create动作里构建绑定了rental关联的新Offer实例,再调用Pundit的authorize方法做校验,最后统一处理授权失败的逻辑:
class OffersController < ApplicationController # 提前加载关联的Rental记录,可根据实际路由调整参数名 before_action :set_rental, only: [:create] def create # 构建绑定了rental的新Offer实例,传入授权方法 @offer = @rental.offers.build authorize @offer, :create? # 校验通过后执行原有Offer创建业务逻辑 # ...... end private def set_rental @rental = Rental.find(params[:rental_id]) if params[:rental_id].present? end # 统一处理Pundit授权失败的响应 rescue_from Pundit::NotAuthorizedError do |e| policy = e.policy if policy.redirect_path == :back redirect_back(fallback_location: root_path, alert: policy.error_message) else redirect_to policy.redirect_path, alert: policy.error_message end end end
补充说明
- 不需要为当前场景拆分
RentalPolicy:Pundit的权限判断核心围绕当前要操作的资源,本次操作的核心资源是Offer,所有创建Offer的前置规则都属于Offer创建权限范畴,放在OfferPolicy里职责更清晰。 - 如果后续需要实现Rental资源的编辑、删除等权限判断,再单独编写
RentalPolicy即可,和当前逻辑不冲突。 - 如果不想在Policy里存储跳转路径和错误信息,也可以在
create?方法中抛出自定义错误,再在rescue_from块里匹配错误类型返回对应响应,两种实现方式都可,按团队代码习惯选择即可。
内容的提问来源于stack exchange,提问作者Darksi
相关产品推荐
相关产品推荐

