在 Rails 中使用 Pundit 限制整个控制器的 DRY 方法是什么?
What is the DRY way to restrict an entire controller with Pundit in Rails?
我正在使用 Pundit 和 Rails,我有一个控制器,我需要完全限制特定用户角色。我的角色是 "Staff" 和 "Consumer." 工作人员应该拥有对控制器的完全访问权限,但消费者应该没有访问权限。
有没有比逐个限制每个操作更干的方法?
例如,这是我的政策:
class MaterialPolicy < ApplicationPolicy
attr_reader :user, :material
def initialize(user, material)
@user = user
@material = material
end
def index?
user.staff?
end
def show?
index?
end
def new?
index?
end
def edit?
index?
end
def create?
index?
end
def update?
create?
end
def destroy?
update?
end
end
还有我的控制器:
class MaterialsController < ApplicationController
before_action :set_material, only: [:show, :edit, :update, :destroy]
# GET /materials
def index
@materials = Material.all
authorize @materials
end
# GET /materials/1
def show
authorize @material
end
# GET /materials/new
def new
@material = Material.new
authorize @material
end
# GET /materials/1/edit
def edit
authorize @material
end
# POST /materials
def create
@material = Material.new(material_params)
authorize @material
respond_to do |format|
if @material.save
format.html { redirect_to @material, notice: 'Material was successfully created.' }
else
format.html { render :new }
end
end
end
# PATCH/PUT /materials/1
def update
authorize @material
respond_to do |format|
if @material.update(material_params)
format.html { redirect_to @material, notice: 'Material was successfully updated.' }
else
format.html { render :edit }
end
end
end
# DELETE /materials/1
def destroy
authorize @material
@material.destroy
respond_to do |format|
format.html { redirect_to materials_url, notice: 'Material was successfully destroyed.' }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_material
@material = Material.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def material_params
params.require(:material).permit(:name)
end
end
是否有我不理解的方法来做到这一点,或者 Pundit 的设计方式要求您明确说明?
使用 authorize
方法的第二个参数。例如:
authorize @material, :index?
您现在可以删除仅调用 index?
的所有其他方法
Pundit 不要求您明确,但允许您这样做。如果您的策略中的 index?
方法没有重复,您希望能够明确显示。
您可以先考虑将一些授权检查移到 set_material
方法中,这样可以减少一半以上的检查。
如果你愿意,另一半可以抽象成其他私有方法,但我认为它们原样就很好。
您还可以考虑添加 before_action 回调以根据操作名称调用授权方,在您通过其他回调记住 @material
之后,但可读性可能会受到影响。
第一步只是将授权调用移至您的回调:
def set_material
@material = Material.find(params[:id])
authorize @material
end
如果您的 Pundit 版本是最新的,您也可以写 @material = authorize Material.find(params[:id])
(以前的版本返回 true/false 而不是记录)。
Pundit 在您选择使用它的方式上有很大的灵活性。例如,您可以创建一个 separate headless policy:
class StaffPolicy < ApplicationPolicy
# the second argument is just a symbol (:staff) and is not actually used
def initialize(user, symbol)
@user = user
end
def access?
user.staff?
end
end
然后在回调中使用它来授权整个控制器:
class MaterialsController < ApplicationController
before_action :authorize_staff
# ...
def authorize_staff
authorize :staff, :access?
end
end
或者你可以直接使用继承或者mixins来干你的策略class:
class StaffPolicy < ApplicationPolicy
%i[ show? index? new? create? edit? update? delete? ].each do |name|
define_method name do
user.staff?
end
end
end
class MaterialPolicy < StaffPolicy
# this is how you would add additional restraints in a subclass
def show?
super && some_other_condition
end
end
Pundit 毕竟只是普通的老 Ruby OOP。
我正在使用 Pundit 和 Rails,我有一个控制器,我需要完全限制特定用户角色。我的角色是 "Staff" 和 "Consumer." 工作人员应该拥有对控制器的完全访问权限,但消费者应该没有访问权限。
有没有比逐个限制每个操作更干的方法?
例如,这是我的政策:
class MaterialPolicy < ApplicationPolicy
attr_reader :user, :material
def initialize(user, material)
@user = user
@material = material
end
def index?
user.staff?
end
def show?
index?
end
def new?
index?
end
def edit?
index?
end
def create?
index?
end
def update?
create?
end
def destroy?
update?
end
end
还有我的控制器:
class MaterialsController < ApplicationController
before_action :set_material, only: [:show, :edit, :update, :destroy]
# GET /materials
def index
@materials = Material.all
authorize @materials
end
# GET /materials/1
def show
authorize @material
end
# GET /materials/new
def new
@material = Material.new
authorize @material
end
# GET /materials/1/edit
def edit
authorize @material
end
# POST /materials
def create
@material = Material.new(material_params)
authorize @material
respond_to do |format|
if @material.save
format.html { redirect_to @material, notice: 'Material was successfully created.' }
else
format.html { render :new }
end
end
end
# PATCH/PUT /materials/1
def update
authorize @material
respond_to do |format|
if @material.update(material_params)
format.html { redirect_to @material, notice: 'Material was successfully updated.' }
else
format.html { render :edit }
end
end
end
# DELETE /materials/1
def destroy
authorize @material
@material.destroy
respond_to do |format|
format.html { redirect_to materials_url, notice: 'Material was successfully destroyed.' }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_material
@material = Material.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def material_params
params.require(:material).permit(:name)
end
end
是否有我不理解的方法来做到这一点,或者 Pundit 的设计方式要求您明确说明?
使用 authorize
方法的第二个参数。例如:
authorize @material, :index?
您现在可以删除仅调用 index?
Pundit 不要求您明确,但允许您这样做。如果您的策略中的 index?
方法没有重复,您希望能够明确显示。
您可以先考虑将一些授权检查移到 set_material
方法中,这样可以减少一半以上的检查。
如果你愿意,另一半可以抽象成其他私有方法,但我认为它们原样就很好。
您还可以考虑添加 before_action 回调以根据操作名称调用授权方,在您通过其他回调记住 @material
之后,但可读性可能会受到影响。
第一步只是将授权调用移至您的回调:
def set_material
@material = Material.find(params[:id])
authorize @material
end
如果您的 Pundit 版本是最新的,您也可以写 @material = authorize Material.find(params[:id])
(以前的版本返回 true/false 而不是记录)。
Pundit 在您选择使用它的方式上有很大的灵活性。例如,您可以创建一个 separate headless policy:
class StaffPolicy < ApplicationPolicy
# the second argument is just a symbol (:staff) and is not actually used
def initialize(user, symbol)
@user = user
end
def access?
user.staff?
end
end
然后在回调中使用它来授权整个控制器:
class MaterialsController < ApplicationController
before_action :authorize_staff
# ...
def authorize_staff
authorize :staff, :access?
end
end
或者你可以直接使用继承或者mixins来干你的策略class:
class StaffPolicy < ApplicationPolicy
%i[ show? index? new? create? edit? update? delete? ].each do |name|
define_method name do
user.staff?
end
end
end
class MaterialPolicy < StaffPolicy
# this is how you would add additional restraints in a subclass
def show?
super && some_other_condition
end
end
Pundit 毕竟只是普通的老 Ruby OOP。