【问题标题】:Rails 4 - Pundit - scoped policy for indexRails 4 - Pundit - 索引范围政策
【发布时间】:2017-01-04 08:11:41
【问题描述】:

我正在尝试学习如何将 Pundit 与我的 Rails 4 应用程序一起使用。

我有以下型号:

class User < ActiveRecord::Base
  has_one :profile
  has_many :eois
end

class Profile < ActiveRecord::Base
  belongs_to :user
  has_many :projects, dependent: :destroy
end

class Project < ActiveRecord::Base
  belongs_to :profile
  has_many :eois
end

class Eoi < ActiveRecord::Base
  belongs_to :project
  belongs_to :user
end

我有一个作用域EoiPolicy :

class EoiPolicy < ApplicationPolicy

  class Scope
    attr_reader :user, :scope

    def initialize(user, scope)
      @user  = user
      @scope = scope
    end

    def resolve
      if user.profile.project.id == @eoi.project_id?
        scope.where(project_id: @user.profile.project.id)
      elsif user.id == eoi.user_id?
        scope.where(user_id: user.id)
      else
        nil
      end
    end
  end

  def index?
    user.profile.project.id == @eoi.project_id? or user.id == eoi.user_id?
  end

  def new?
    true
  end

  def show?
    user.profile.project.id == @eoi.project_id? or user.id == eoi.user_id?
  end

  def edit?
    user.id == eoi.user.id?
  end

  def create?
    true 
  end

  def update?
    user.id == eoi.user.id?
  end

  def destroy?
    user.id == eoi.user.id?
  end    
end

在我的EoisController 中,我尝试将范围用于:

def index
  # @eois = @project.eois
  @eois = policy_scope(Eoi)
  # @eois = Eois.find_by_project_id(params[:project_id])
end

然后在我的view/eois/index 中,我尝试使用以下方式显示索引:

<% policy_scope(@user.eois).each do |group| %>

我无法让它工作。错误消息在策略中突出显示了我的范围方法的这一行:

if user.profile.project.id == @eoi.project_id?

对我来说,这看起来是正确的,尽管我仍在努力解决这个问题。任何人都可以看到要完成这项工作需要发生什么,因此如果用户是用户,其个人资料拥有相关项目,则与该项目相关的所有 eois 都是可见的。

否则,如果用户是创建eoi的用户,那么他们创建的所有eois都是可见的?

错误信息说:

undefined method `project' for #<Profile:0x007fa03f3faf48>
Did you mean?  projects
               projects=

我想知道这是不是因为一个索引会有很多记录,它需要在策略中显示不同的内容才能识别多个记录?

我也试过用:

替换那行
if  @eoi.project_id == @user.profile.project.id?

虽然这也是错误的并且给了

undefined method `project_id' for nil:NilClass
Did you mean?  object_id

我也尝试过制作范围:

 def resolve
      # cant figure what is wrong with this
      if  eoi.project_id == user.profile.project.id?
        scope.where(project_id: @user.profile.project.id)
      else
        nil
      end
    end

但这也是错误的,并给出了这个错误:

  undefined local variable or method `eoi' for #<EoiPolicy::Scope:0x007ffb505784f8>

我也试过了:

    def resolve
      # cant figure what is wrong with this

      if  @eoi.project_id == user.profile.project.id? or Eoi.project_id == user.profile.project.id?
        scope.where(project_id: @user.profile.project.id)
      elsif user.id == eoi.user_id?
        scope.where(user_id: user.id)
      else
        nil
      end
    end
  end



def index?
    user.profile.project.id == Eoi.project_id? or user.id == Eoi.user_id?
  end

但该尝试给出了以下错误消息:

undefined method `project_id' for nil:NilClass
Did you mean?  object_id

目前的想法

我认为我需要将更多的用户和范围传递给范围方法。如果我也可以通过项目,那么我可以使范围可参考与意向书相关的项目。

如果我可以让这个工作,那么也许我可以让范围方法为控制器上的索引视图工作:

class Scope
  attr_reader :user, :scope

  def initialize(user, scope, project)
    @user  = user
    @scope = scope
    @project = project
  end
end

然后在控制器中:

 def index
   # @eois = @project.eois

   @eois = policy_scope(Eoi, @project)
   # authorize @eois
   # @eois = Eois.find_by_project_id(params[:project_id])
 end

这不起作用,当我尝试时收到一条错误消息,提示该策略

wrong number of arguments (given 2, expected 1)

请帮忙!

下一次尝试

我的下一个尝试是尝试从 [this]Pundit issue 中获取建议,并实施该想法,以了解如何为特定用户获取正确的范围。

在我的 Eoi Policy 中,我将解决方法更改为:

class Scope
    attr_reader :user, :scope

    def initialize(user, scope) #project
      @user  = user
      @scope = scope
      # @project = project

    end

    def resolve
      # if  Eoi.project_id == user.profile.project.id? or Eoi.project_id == user.profile.project.id?
      if user.id == eoi.projects.profile.user.map(&:id)
        scope.joins(eois: :projects).where(project_id: user.profile.projects.map(&:id)).empty?
      # if scope.eoi.project_id == user.profile.projects.map(&:id)  
        # scope.where(project_id: user.profile.projects.map(&:id)).empty? 
      #   scope.where(project_id: user.profile.project.id)
      # elsif user.id == eoi.user_id?
      #   scope.where(user_id: user.id)
      else
      #   nil
       end
    end
  end

然后在我的 eoi 控制器索引操作中,我尝试了这个:

def index
    # @eois = @project.eois

    # @eois = policy_scope(Eoi, @project)
    policy_scope(Eoi).where(project_id: params[:project_id])
    # authorize @eois
    # @eois = Eois.find_by_project_id(params[:project_id])
  end

那也行不通。此尝试的错误消息显示:

undefined local variable or method `eoi' for #<EoiPolicy::Scope:0x007f98677c9cf8>

我对尝试的东西没有想法。任何人都可以找到一种方法来为范围提供正确的输入来进行设置吗?

观察 我注意到 github 上许多使用 Pundit 和范围的 repos 还包括这样的方法:

def scope
  Pundit.policy_scope!(user, record.class)
end

该方法是对 Scope 类的补充,并且未显示在 Pundit gem 文档中。如果有必要包括,它有什么作用? 1

重写

我现在浏览了 github 上的 200 多个存储库,以深入了解我应该如何编写策略来实现我的目标。我不知道如何按预期使用 Pundit。

我已经完全改变了我的设置,试图解决我无法理解的问题。我现在有:

Eois 控制器

class EoisController < ApplicationController

  def index
    @eois = Eoi.by_user_id(current_user.id)
  end
end

项目:: Eois 控制器

module Projects
  class EoisController < ApplicationController
    before_action :get_project
    before_action :set_eoi, only: [:edit, :update, :destroy]
    # after_action :verify_authorized

    def index
      @eois = Project.by_user_id(current_user.id).find_by(id: params[:project_id]).try(:eois) || []
    end

 def show
      @eoi = Eoi.find(params[:id])
      authorize @eoi
    end

def set_eoi
        @eoi = EoiPolicy::Scope.new(current_user, params[:project_id]).resolve.find(params[:id])
      end

      def get_project
        @project = Project.find(params[:project_id])
      end

Eoi Policy(决定何时显示用户制作的所有 eois)

class EoiPolicy < ApplicationPolicy

  class Scope
    attr_reader :user, :scope

    def initialize(user, scope)
      @user  = user
      @scope = scope
    end

    def resolve
      if scope.present?
          Eoi.by_user_id(user.id)
        # end
      else
        []
      end
    end

  end

  def index?
    user.profile.project.id == Eoi.project_id? or user.id == Eoi.user_id?
  end

  def new?
    true
  end

  def show?
    record.user_id == user.id || user.profile.project_id == record.project_id
    # user.profile.project.id == @eoi.project_id? or user.id == eoi.user_id?
  end

  def edit?
    user.id == eoi.user.id?
  end

  def create?
    true
  end

  def update?
    user.id == eoi.user.id?
  end

  def destroy?
    user.id == eoi.user.id?
  end


end

路线

resources :eois

resources :projects do
    member do
    resources :eois, controller: 'projects/eois
  end

当我想显示提交的与项目相关的意向书时,我使用项目意向书政策,当我想展示用户创建的意向书时,我使用意向书政策 - 没有范围。

我很想弄清楚这一点,这样我就可以按照预期的方式使用这个宝石。建议将不胜感激。我确信这种尝试不是 Pundit 的本意——但我不知道如何使用这个 gem,如文档中所示。

我不能使用 policy_scope,因为我需要将 project_id 参数传递到项目 eoi 控制器索引操作的索引操作中。

PaReeOhNos 建议

下面列出了我尝试实施 PareeOhNos 建议的尝试。我不确定我是否理解正确,因为 eois 总是有一个项目 id 和一个用户 id,但也许我没有明白 load_parent 方法在做什么。

在我的 Eois 控制器中,我有:

class EoisController < ApplicationController
  before_action :load_parent
  before_action :load_eoi, only: [:show, :edit, :update, :destroy]



  def index
    authorize @parent
    @eois = EoiPolicy::Scope.new(current_user, @parent).resolve
  end



  def show

  end

  # GET /eois/new
  def new
    @project = Project.find(params[:project_id])
    @eoi = @project.eois.build
    @contribute = params[:contribute] || false
    @participate = params[:participate] || false
    @partner = params[:partner] || false
    @grant = params[:grant] || false
    @invest = params[:invest] || false
  end

  # GET /eois/1/edit
  def edit
  end

  # POST /eois
  # POST /eois.json
  def create
    @eoi = Project.find(params[:project_id]).eois.build(eoi_params)
    @eoi.user_id = @current_user.id

    respond_to do |format|
      if @eoi.save
        format.html { redirect_to Project.find(params[:project_id]), notice: 'Eoi was successfully created.' }
        format.json { render :show, status: :created, location: @project }
      else
        format.html { render :new }
        format.json { render json: @eoi.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /eois/1
  # PATCH/PUT /eois/1.json
  def update
    respond_to do |format|
      if @eoi.update(eoi_params)
        format.html { redirect_to @project, notice: 'Eoi was successfully updated.' }
        format.json { render :show, status: :ok, location: @eoi }
      else
        format.html { render :edit }
        format.json { render json: @eoi.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /eois/1
  # DELETE /eois/1.json
  def destroy
    @eoi.destroy
    respond_to do |format|
      format.html { redirect_to @project, notice: 'Eoi was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private

    def load_parent
      # @parent = (params[:project_id] ? Project.find(params[:project_id] : current_user)
      @parent =  params[:project_id] ? Project.find(params[:project_id]) : current_user
    end

    def load_eoi
      @eoi = Eoi.find(params[:id])
      authorize @eoi
    end

在我的 Eoi 政策中,我有:

class EoiPolicy < ApplicationPolicy
class Scope
    attr_reader :user, :scope

    def initialize(user, scope)
      @user  = user
      @scope = scope
    end

    def resolve
      if scope.is_a?(User)
        Eoi.where(user_id: scope.id)
      elsif scope.is_a?(Project)
        Eoi.where(project_id: scope.id)
      else
        []
      end
    end

  end

  def index?
    record.is_a?(User) || user.profile.project.id == record.project_id
  end

  def new?
    true
  end

  def show?
    record.user_id == user.id || user.profile.project_id == record.project_id
  end

  def edit?
    user.id == eoi.user.id?
  end

  def create?
    true
  end

  def update?
    user.id == eoi.user.id?
  end

  def destroy?
    user.id == eoi.user.id?
  end


end

在我的 routes.rb 中,我有:

resources :projects do
    member do
  resources :eois, shallow: true

resources :eois, only: [:index]

在我的 eois/index 中,我有:

    <% @eois.sort_by(&:created_at).in_groups_of(2) do |group| %>
        <% group.compact.each do |eoi| %>
            <h4><%= link_to eoi.user.full_name %></h4>
            <%= link_to 'VIEW DETAILS', eoi_path(eoi), :class=>"portfolio-item-view" %>
<% end %>  
<% end %>  

在我的 eois/ 节目中,我有:

"test"

当我尝试所有这些时,会加载 eois/index 页面。当我尝试显示特定的 eoi 页面时,我收到一条错误消息:

wrong number of arguments (given 2, expected 0)

错误信息指向授权控制器的@eoi行:

def load_eoi
      @eoi = Eoi.find(params[:id])
      authorize @eoi
    end

如果我将 authorize @eoi 放在 show 操作而不是 load eoi 方法中,也会出现同样的错误。

应用政策有

class ApplicationPolicy
  attr_reader :user,  :scope

  class Scope
    def initialize(user, scope)
      #byebug        
      @user = user
      # record = record
      @scope = scope
    end

    def resolve
      scope
    end
  end

  def index?
    false
  end

  def show?
    scope.where(:id => record.id).exists?
  end

  def create?
    false
  end

  def new?
    create?
  end

  def update?
    false
  end

  def edit?
    update?
  end

  def destroy?
    false
  end

  def scope
    Pundit.policy_scope!(user, record.class)
  end

下一次尝试

采纳 PaReeOhNos 的建议(复制在上面),我尝试对其进行一些调整以更好地适应我的用例。

现在,我有:

Eoi 控制器

class EoisController < ApplicationController
  # before_action :get_project
  # before_action :set_eoi, only: [:show, :edit, :update, :destroy]
  before_action :load_parent
  before_action :load_eoi, only: [:show, :edit, :update, :destroy]


  # GET /eois
  # GET /eois.json
  # def index
  #   @eois = @project.eois
  #   # @eois = Eois.find_by_project_id(params[:project_id])
  # end

  def index
    # authorize @parent
    @eois = policy_scope(Eoi.where(project_id: params[:project_id]))
    # @eois = EoiPolicy::Scope.new(current_user, @parent).resolve
  end


  # GET /eois/1
  # GET /eois/1.json
  def show

  end

  # GET /eois/new
  def new
    @project = Project.find(params[:project_id])
    @eoi = @project.eois.build
    @contribute = params[:contribute] || false
    @participate = params[:participate] || false
    @partner = params[:partner] || false
    @grant = params[:grant] || false
    @invest = params[:invest] || false
  end

  # GET /eois/1/edit
  def edit
  end

  # POST /eois
  # POST /eois.json
  def create
    @eoi = Project.find(params[:project_id]).eois.build(eoi_params)
    @eoi.user_id = @current_user.id

    respond_to do |format|
      if @eoi.save
        format.html { redirect_to Project.find(params[:project_id]), notice: 'Eoi was successfully created.' }
        format.json { render :show, status: :created, location: @project }
      else
        format.html { render :new }
        format.json { render json: @eoi.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /eois/1
  # PATCH/PUT /eois/1.json
  def update
    respond_to do |format|
      if @eoi.update(eoi_params)
        format.html { redirect_to @project, notice: 'Eoi was successfully updated.' }
        format.json { render :show, status: :ok, location: @eoi }
      else
        format.html { render :edit }
        format.json { render json: @eoi.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /eois/1
  # DELETE /eois/1.json
  def destroy
    @eoi.destroy
    respond_to do |format|
      format.html { redirect_to @project, notice: 'Eoi was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private

    def load_parent
      # @parent = (params[:project_id] ? Project.find(params[:project_id] : current_user)
      @parent = params[:project_id] ? Project.find(params[:project_id]) : current_user
    end

    def load_eoi
      @eoi = Eoi.find(params[:id])
      # authorize @eoi
    end

Eoi 政策

class EoiPolicy < ApplicationPolicy

  class Scope
    attr_reader :user, :scope

    def initialize(user, scope)
      @user  = user
      @scope = scope
    end

    def resolve
      # since we send the scoped eois from controller, we can pick
      # any eoi and get its project id

      # check if the current user is the owner of the project
    #   if (user.profile.projects.map(&:id).include?(project_id))
    #     # user is the owner of the project, get all the eois 
    #     scope.all 
    #   end
    #   #not the owner , then get only the eois created by the user
    #   scope.where(user_id: user.id)
    # end 
      if scope.is_a?(User)
        Eoi.where(user_id: scope.id)
      elsif scope.is_a?(Project) && (user.profile.projects.map(&:id).include?(project_id))
        project_id = scope.first.project_id 
        Eoi.where(project_id: scope.id)
      else
        Eoi.none
      end
    end

  end

  def index?
    record.is_a?(User) || user.profile.project.id == record.project_id
  end

  def new?
    true
  end

  def show?
    record.user_id == user.id || user.profile.project_id == record.project_id
  end

  def edit?
    user.id == eoi.user.id?
  end

  def create?
    true
  end

  def update?
    user.id == eoi.user.id?
  end

  def destroy?
    user.id == eoi.user.id?
  end


end

路线

resources :eois#, only: [:index]
  concern :eoiable do
    resources :eois
  end

resources :projects do
    concerns :eoiable
  end

索引

   <% @eois.sort_by(&:created_at).in_groups_of(2) do |group| %>
     <% group.compact.each do |eoi| %>
     <h4><%= link_to eoi.user.full_name %></h4>
     <%= link_to 'VIEW DETAILS', project_eoi_path(eoi.project, eoi), :class=>"portfolio-item-view" %>
                            <% end %>  
                        <% end %>   

查看

'test'

这不起作用,因为当我导航到一个项目然后尝试呈现具有匹配项目 ID 的 eois 索引时,我得到一个空索引页面,而我的数据库中有 4 条记录应该是渲染。

LEITO 的建议

听从 Leito 的建议,我也试过这个:

Eoi 控制器

class EoisController < ApplicationController
  before_action :get_project
  before_action :set_eoi, only: [:show, :edit, :update, :destroy]
  # before_action :load_parent
  # before_action :load_eoi, only: [:show, :edit, :update, :destroy]


  # GET /eois
  # GET /eois.json
  # def index
  #   @eois = @project.eois
  #   # @eois = Eois.find_by_project_id(params[:project_id])
  # end

  def index
    # authorize @eois
    # authorize @parent
    # policy_scope(@project.eois)
    @eois = policy_scope(Eoi.where(project_id: params[:project_id]))
    # @eois = EoiPolicy::Scope.new(current_user, @parent).resolve
  end


  # GET /eois/1
  # GET /eois/1.json
  def show

  end

  # GET /eois/new
  def new
    @project = Project.find(params[:project_id])
    @eoi = @project.eois.build
    @contribute = params[:contribute] || false
    @participate = params[:participate] || false
    @partner = params[:partner] || false
    @grant = params[:grant] || false
    @invest = params[:invest] || false
  end

  # GET /eois/1/edit
  def edit
  end

  # POST /eois
  # POST /eois.json
  def create
    @eoi = Project.find(params[:project_id]).eois.build(eoi_params)
    @eoi.user_id = @current_user.id

    respond_to do |format|
      if @eoi.save
        format.html { redirect_to Project.find(params[:project_id]), notice: 'Eoi was successfully created.' }
        format.json { render :show, status: :created, location: @project }
      else
        format.html { render :new }
        format.json { render json: @eoi.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /eois/1
  # PATCH/PUT /eois/1.json
  def update
    respond_to do |format|
      if @eoi.update(eoi_params)
        format.html { redirect_to @project, notice: 'Eoi was successfully updated.' }
        format.json { render :show, status: :ok, location: @eoi }
      else
        format.html { render :edit }
        format.json { render json: @eoi.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /eois/1
  # DELETE /eois/1.json
  def destroy
    @eoi.destroy
    respond_to do |format|
      format.html { redirect_to @project, notice: 'Eoi was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private

    # def load_parent
    #   # @parent = (params[:project_id] ? Project.find(params[:project_id] : current_user)
    #   @parent = params[:project_id] ? Project.find(params[:project_id]) : current_user
    # end

    # def load_eoi
    #   @eoi = Eoi.find(params[:id])
    #   # authorize @eoi
    # end
    # # Use callbacks to share common setup or constraints between actions.
    def set_eoi
      @eoi = Eoi.find(params[:id])
    end

    def get_project
      @project = Project.find(params[:project_id])
    end

Eoi 政策

def initialize(user, scope)
      @user  = user
      @scope = scope
    end

    def resolve

      if scope.joins(project: :profile).where profiles: { user_id: user }
        Eoi.where(project_id: scope.ids)
      elsif scope.joins(eoi: :user).where eois: { user_id: user }  
        Eoi.where(user_id: scope.ids)
      else
        Eoi.none
      end  
      # since we send the scoped eois from controller, we can pick
      # any eoi and get its project id

      # check if the current user is the owner of the project
    #   if (user.profile.projects.map(&:id).include?(project_id))
    #     # user is the owner of the project, get all the eois 
    #     scope.all 
    #   end
    #   #not the owner , then get only the eois created by the user
    #   scope.where(user_id: user.id)
    # end 
      # if scope.is_a?(User)
      #   Eoi.where(user_id: scope.id)
      # elsif scope.is_a?(Project) && (user.profile.projects.map(&:id).include?(project_id))
      #   project_id = scope.first.project_id 

      #   Eoi.where(project_id: scope.id)
      # else
      #   Eoi.none
      # end
    end

  end

  def index?
    true
    # record.is_a?(User) || user.profile.project.id == record.project_id
  end

  def new?
    true
  end

  def show?
    true
    # record.user_id == user.id || user.profile.project_id == record.project_id
  end

  def edit?
    user.id == eoi.user.id?
  end

  def create?
    true
  end

  def update?
    user.id == eoi.user.id?
  end

  def destroy?
    user.id == eoi.user.id?
  end


end

路线和视图与上面的尝试相同

这里的问题在于我的控制器中的 get project 方法。对于我试图在特定项目上显示所有 eois 的场景,我需要它。当我试图显示用户的所有 eois 时,我不需要它。

当我保存所有这些并尝试时,项目中的 eois 正确显示。然而,应该向我展示我(作为用户)所有 eois 的 eois(未嵌套在项目中)显示的错误是:

Couldn't find Project with 'id'=

错误消息突出显示“get_project 方法”。

LEITO 的更新建议

接受 Leito 的更新建议,我已经开始了当前的尝试。

在此之前,我想澄清一下,所有 Eois 都将同时具有用户 ID 和项目 ID。我用这张表让用户表达对项目的兴趣。我的目标是让其个人资料拥有该项目的用户查看在该项目上提交的所有 eois。然后,我还希望用户看到他们自己提交的所有 eois(跨所有项目)。

Eoi 政策

def resolve
  if scope.joins(project: :profile).where 'profiles.user_id = ? OR eois.user_id = ?', user.id, user.id
   Eoi.all
  else
    Eoi.none
  end  

Eoi 控制器

def index
    @eois = policy_scope(Eoi)
    @eois = @eois.where(project_id: params[:project_id]) if params[:project_id]
  end

目前,这在查找嵌套在项目 (project/26/eois) 下的 eois 时效果很好。但是,当我尝试执行 eois/index(未嵌套在项目下),我想返回所有用户的 eois 时,我收到一条错误消息:

Couldn't find Project with 'id'=

它突出显示了eoi控制器的这一行:

def get_project
  @project = Project.find(params[:project_id])
end

我不确定我现在是否理解解析方法或控制器剔除的想法。我看不出范围线有什么问题,无法查看要尝试更改的内容。

【问题讨论】:

  • 请一次一个!第一个是一个简单的错误:您的个人资料没有一个项目,而是多个项目。这就是错误的原因。让我们摆脱那个并专注于您尝试使用.projects
  • 您是否更改了您的application_policy.rb 还是专家在您安装它时生成的相同?
  • 我在帖子末尾添加了申请政策
  • 梅尔,那个新错误,仅仅是因为params[:project_id] 是零。它与 Pundit 和政策没有太大关系。 Pundit 的政策关注点是:此用户可以访问哪些 Eoi?。英文响应是:那些属于属于用户的配置文件的项目和属于用户的那些。如果 Eoi belongs_to :user,您缺少用户 has_many: eois?你能告诉我们更多关于这种关系的信息吗?
  • 嗨 Leito,我忘记将 has many eois 关联复制到我上面的用户模型。关联存在于用户模型中。我的数据库中有 5 个 eois,它们都有一个项目 ID 和一个用户 ID。

标签: ruby-on-rails ruby scope pundit


【解决方案1】:

在您的第一个示例中,有几个问题。首先,@eoi 不存在,也不可能存在。 @eoi 变量在控制器中设置,这是一个不同的对象。它与可访问的视图的工作方式不同,因此永远不会设置。

同样,eoi 变量不会被设置,因为您的 initialize 方法只分配了 user 和 resource 变量,所以它们是您唯一可以访问的两个变量(除非您重命名)

政策中的范围与您认为的运作方式略有不同。策略本身通常采用登录的用户,以及您授权的课程或记录。然而,范围通常不会将记录作为第二个参数。它是一个范围,因此要么是活动记录子类,要么是关系。但是,您不限于此,您可以通过提供记录来解决它,但请注意这不是 Pundit 的正常行为。

为了达到你所追求的,你应该只需要做一些调整:

class EoiPolicy < ApplicationPolicy

  class Scope
    attr_reader :user, :eoi

    def initialize(user, eoi)
      @user = user
      @eoi  = eoi
    end

    def resolve
      if user.profile.project.id == eoi.project_id
        Eoi.where(project_id: user.profile.project.id)
      elsif user.id == eoi.user_id
        Eoi.where(user_id: user.id)
      else
        nil
      end
    end
  end

  def index?
    user.profile.project.id == record.project_id or user.id == record.user_id
  end

  def new?
    true
  end

  def show?
    user.profile.project.id == record.project_id? or user.id == record.user_id
  end

  def edit?
    user.id == record.user.id
  end

  def create?
    true 
  end

  def update?
    user.id == record.user.id
  end

  def destroy?
    user.id == record.user.id
  end


end

这里的主要变化是attr_reader :user, :scope 现在是attr_reader :user, :eoi,这将使您可以在该范围内访问eoi。

对此的访问不再以@ 为前缀,因为这与权威人士的工作方式一致。

在策略的其余部分中,@eoi 再次无法工作,但这已更改为 record(假设这是它在 ApplicationPolicy 中的内容)。请记住 Scope 和其余的策略是两个不同的类。

通过此设置,您现在应该能够从控制器中简单地调用 policy_scope(@eoi)。注意这里使用了@eoi 变量,而不是之前的Eoi 类。这一点至关重要,因为没有这个,您将无法访问 user_id 或 project_id 之类的东西,因为这些方法在 Eoi 类中不存在,而只是一条记录。

我还删除了 if 条件末尾的 ? 符号。这些通常用于表示被调用的方法返回一个布尔值,而您将它们放在只返回一个整数的东西的末尾。我想你实际上会收到一个错误,说该方法不存在,但如果你重命名了一些东西,那么你可能想把它们放回去,但正如我所说的那样,这确实违背了 ruby​​ 编码风格。

顺便说一句,在语句中使用or 或and 而不是|| 或&amp;&amp; 在奇怪的情况下可能会与您的预期不同。在大多数情况下都可以,但从技术上讲,这并不意味着同样的事情。

希望这对您有所帮助,如果您有任何其他问题,请告诉我。

【讨论】:

  • 嗨,非常感谢您的解释。我很想尝试让这个工作。目前,当我尝试此操作时,我收到一条错误消息:无法找到 nil 的策略范围并在 eois 控制器中的索引操作中指向此行:policy_scope(@eoi)
  • 这表明@eoi 为零。这肯定是设置的吗?
  • 肯定有一个eoi,它的外键肯定在正确的项目上
  • 我尝试了一种我在上面复制的解决方法。我很想知道这可能与专家的意图有多接近
  • @Mel 试试这个gist.github.com/pareeohnos/b45d021047553cee8e77ca4fdd18aaa2 没有真正需要拥有多个控制器,您可以从一个控制器中完成所有操作。我再次认为您混淆了 Pundit 的工作方式。该要点对所有事情都使用一个控制器,并且策略中的范围会根据它是项目还是父用户来更改加载的 EOI
【解决方案2】:

我是该问题的前评论者。

对于您的 EoiScope,您只需要用户有权访问的 Eois(因为它们属于此配置文件下的项目),独立于项目(此要求仅适用于控制器,因为是嵌套的),因此您的控制器应该看起来像这样:

编辑:根据您最近的尝试,我已经更新了范围以说明 Eois 直接属于用户(而不是通过项目),您应该简单地将其范围限定为项目基于 params[:project_id] 的存在,请参阅更新的答案。

@eois = policy_scope(Eoi)
@eois = @eios.where(project_id: params[:project_id]) if params[:project_id]

并且您的作用域应该在到达用户之前进行连接,或者只是在 Eoi 上查找 user_id 属性。

  class EoiPolicy < ApplicationPolicy
    class Scope < Scope
      def resolve
        scope.joins(project: : profile).where 'profiles.user_id = ? OR eois.user_id = ?', user.id, user.id
      end
    end

    # Other methods that differ from ApplicationPolicy's methods
  end

请注意,Scope 不调用eoi,但默认* 范围只知道scope 和user。 * 默认情况下,我的意思是它继承自ApplicationPolicy::Scope

【讨论】:

  • 非常感谢您帮助 Leito。我尝试了您的建议,但收到此错误:#<:scope:0x007fbd884d7db0> 的未定义局部变量或方法 `eoi'。错误信息指向这一行: if user.id == eoi.projects.profile.user.map(&:id)
  • 那是show? 方法?您可能要继承的默认 ApplicationPolicy 有一个 record 方法,它可能包含 eoi,但没有 eoi 方法,因此会出现错误。
  • 它在索引上?方法。我不明白你描述的错误。您对我如何弄清楚这个错误的含义或如何解决它有什么建议吗?
  • 我正在描述您的错误,如果您遵循 Pundit 的 README 并使用生成器,它会创建一个 ApplicationPolicy。那有一个通用的record 而不是eoi。为什么你有一个index?,只需使用 hte 范围。用户将获得他们有权访问的记录,不多不少,无需授权:index 操作,最坏的情况是他们没有 Eois,因为范围。
  • 不是 Ruby/rails 关注点(模式),而是关注点分离(概念)。 Scope 的结果应该只是根据用户(而不是控制器、参数或其他任何东西)来限制结果,这取决于控制器。
【解决方案3】:

对于其他人,我不确定这是否是按预期方式使用 Pundit 的解决方案,但它确实在我的能力范围内生成了我想要的流程。

感谢所有为此提供帮助的人。我确信我还有很多东西要学习如何改进这一点,但就目前而言,这是一个可行的解决方案。

总而言之 - 我现在有两个针对 1 个控制器的策略。

Eoi 政策

class EoiPolicy < ApplicationPolicy

  class Scope

    def initialize(user, scope)
      @user  = user
      @scope = scope
    end

    def resolve
      # selects all the EOI's for a given user
      @scope.where(user_id: @user.id)
    end

  end

  def index?
    true
  end

Eoi 计划政策

class ProjectEoiPolicy < ApplicationPolicy
  class Scope < Scope
    def resolve(project_id)
      project = Project.find(project_id)
      if project.owner?(@user)
        # if the user is the owner of the project, then get
        # all the eois
        project.eois
      else
        # select all the eois for the project
        # created by this user
        Eoi.for_user(@user.id).for_project(project_id)
      end
    end
  end

end

Eoi 控制器索引操作

class EoisController < ApplicationController
  before_action :get_project, except: [:index, :show]
  before_action :set_eoi, only: [:show, :edit, :update, :destroy]


  def index
    if params[:project_id]
      @eois = ProjectEoiPolicy::Scope.new(current_user, Eoi).resolve(params[:project_id])
    else
      @eois = policy_scope(Eoi)
    end
  end

【讨论】:

  • POST SCRIPT:从头开始。它不起作用。当我将 authorize @eoi 添加到我的控制器操作(仅测试显示操作)时,我收到一条错误消息:参数数量错误(给定 2,预期为 0)。回到绘图板。我还是卡住了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-01-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多