Rails 5 - 使用Pundit Scopes和Statesman状态机:结构不兼容?

时间:2016-12-17 02:34:53

标签: ruby-on-rails ruby scope state-machine pundit

经过多年努力学习如何在我的Rails应用程序中使用权威范围,我刚刚了解了为什么我无法使用它。显然,Pundit无法运行SQL查询,其中一个查询参数是一个政治家状态。

我收到的建议是使用不同的状态机。在我这样做之前,我把它扔到那里看看是否有人设法找到使用Pundit(带范围)和政治家状态机的方法。

我的设置是:

ApplicationPolicy

class ApplicationPolicy
  attr_reader :user, :record

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

  def index?
    true
  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

  class Scope
    attr_reader :user, :scope

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

    def resolve
      scope
    end
  end
end

ProposalPolicy

class ProposalPolicy < ApplicationPolicy
  class Scope < Scope
     def resolve
       accum_scope = scope.where(user_id: @user.id)
  #     # Depends on moving off statesman
  #     # accum_scope = accum_scope.or(accum_scope.reviewable) if @user.has_role?(:research_management, Organisation.first)
       accum_scope
     end
  end


  # I think index isnt necessary when I have a Scope
  # def index?
  #   true
  # end

  def new?
   true 
  end

  def create?
    new?
  end

  def show?
    #if its in the index results for that user
    true
  end

  def edit?
    update?
  end

  def update?
    true if record.try(:user) == user
  end

  def destroy?
    true if record.try(:user) == user
  end

  def draft?
    create?
  end

  def under_review?
    true if record.try(:user) == user
  end

  def approved?
    @user.has_role?(:research_management, Organisation.matching) 
  end

  def not_approved?
    approved?
  end

  def publish_openly?
    true if record.try(:user) == user && record.can_transition_to?(:publish_openly)
  end

  def publish_to_invitees?
    true if record.try(:user) == user && record.can_transition_to?(:publish_to_invitees)
  end

  def publish_counterparties_only?
    true if record.try(:user) == user && record.can_transition_to?(:publish_counterparties_only)
  end

  def remove?
    true if record.try(:user) == user #|| @user.has_role? :admin
  end

  private

  def matching
    @user.organisation_id == record.user.organisation_id
  end
end

Proposal.rb

class Proposal < ApplicationRecord
  include Statesman::Adapters::ActiveRecordQueries

has_many :proposal_transitions, class_name: "ProposalTransition", autosave: false
scope :reviewable,  -> { in_state(:under_review) }
  def state_machine
    @state_machine ||= ProposalStateMachine.new(self, transition_class: ProposalTransition,
                                                   association_name: :proposal_transitions)
  end

  delegate :can_transition_to?, :transition_to!, :transition_to, :current_state,
           to: :state_machine


  private

  def self.transition_class
    ProposalTransition
  end

  def self.initial_state
    :draft
  end

当前的问题:

 ProposalPolicy::Scope.new(User.first, Proposal).resolve.all
  User Load (1.0ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
  Organisation Load (0.7ms)  SELECT  "organisations".* FROM "organisations" ORDER BY "organisations"."id" ASC LIMIT $1  [["LIMIT", 1]]
   (0.9ms)  SELECT COUNT(*) FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = $1 AND "roles"."name" = $2 AND "roles"."resource_type" = $3 AND "roles"."resource_id" = $4  [["user_id", 43], ["name", :research_management], ["resource_type", "Organisation"], ["resource_id", 5]]
  Proposal Load (0.3ms)  SELECT "proposals".* FROM "proposals" WHERE "proposals"."user_id" = $1  [["user_id", 43]]
 => #<ActiveRecord::Relation [#<Proposal id: 17, user_id: 43, title: "asdf", description: "adsf", byline: "asdf", nda_required: true, created_at: "2016-11-16 00:28:31", updated_at: "2016-11-28 01:16:47", trl_id: 1, invitee_id: nil>]> 
2.3.1p112 :012 > Proposal.where(user_id: User.first).or(Proposal.reviewable)
  User Load (2.3ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
ArgumentError: Relation passed to #or must be structurally compatible. Incompatible values: [:joins]

我刚刚收到的见解是,专家范围无法查询提案状态,因为政治家在幕后管理转换时有一系列表格。

有没有人看到一个可以让我继续使用政治家以及使用Pundit范围的解决方案?

1 个答案:

答案 0 :(得分:0)

还没有一个完整的答案 - 我仍在试图弄清楚如何做到这一点 - 但我在这里找到了帮助:https://github.com/elabs/pundit/issues/440。帮助解释了以下内容:

Pundit范围没有做任何特别的事情。在阅读Pundit范围时,只需在思想上将范围关键字替换为类名,并尝试在Rails控制台中输入完全相同的内容。

该错误并非特定于Pundit。这是与or运算符相关的错误。我看了一下政治家如何实现in_state调用,并且它与你的ProposalTransition类进行了连接。

ActiveRecord或操作员要求左侧和右侧兼容。在您的情况下,您的左侧查询(Proposal.where(user:User.first))与您的右侧查询不兼容:

Proposal.reviewable转换为Proposal.in_state(:under_review),转换为Proposal.joins(most_recent_transition_join).where(states_where(most_recent_transition_alias,states),:under_review)与转换表进行LEFT OUTER JOIN(proposal_transitions) ,我相信)[1]

要使它们兼容,您必须将相同的连接应用于或。

的两侧

既然你已经拨打了一个昂贵的电话,你可以做一个穷人或做以下事情:

def resolve proposal_ids = current_user.proposal_ids + Proposal.reviewable.pluck(:id)Proposal.where(id:proposal_ids)end [1]我查看了政治家的源代码:https://github.com/gocardless/statesman/blob/77958f1c2ae613ac29c6db5329c67538a5655ddf/lib/statesman/adapters/active_record_queries.rb