Pundit使用非直接关联模型授权索引操作

时间:2018-06-01 09:00:45

标签: ruby-on-rails ruby pundit

我正在努力为与我的用户模型没有直接关系的模型授权索引。实际上,我正在努力围绕Pundit范围的想法。

我知道我无法在SitePolicy中授权@sites,而且从我所做的所有阅读中,我相信我只需要在SitePolicy类的范围内进行,但我不确定如何执行此操作。

这就是我所拥有的:

模型

User
  has_one :business
  has_many :locations, :through => :business
end

Business
  belongs_to :user
  has_many :locations
end

Location
  extend FriendlyId
  belongs_to :business
  has_one :user, :through => :business
  has_many :sites, dependent: :destroy
  friendly_id :custom_url, use: :slugged
end

Site
  belongs_to :location
end

的routes.rb

 resources :locations do
    resources :sites
  end

sites_controller.rb

class SitesController < ApplicationController
  before_action :set_site, only: [:show, :edit, :update, :destroy]
  before_action :set_location, only: [:new, :show, :edit, :index, :update, :destroy]


  def index
    @sites = @location.sites.all
    authorize Site
  end

  private
    def set_site
      @site = Site.find(params[:id])
    end
    def set_location
      @location = Location.friendly.find(params[:location_id])
    end
    def site_params
      params.require(:site).permit(:location_id, :site, :url, :review_site_id, :number_of_reviews, :average_rating, :extra_data)
    end
end

site_policy.rb

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

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

      def resolve
        if user.has_role? :admin
          scope.all
        else
          scope.where(scope.location.user == user)
        end
      end
    end

  def index? 
    return true if user.present? and user.has_role? :admin
  end
  ...

schema.rb

ActiveRecord::Schema.define(version: 2018_05_28_085645) do

  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"

  create_table "businesses", force: :cascade do |t|
    t.string "name"
    t.text "description"
    t.bigint "user_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.boolean "more_than_one_location"
    t.boolean "signed_up"
    t.string "slug"
    t.index ["more_than_one_location"], name: "index_businesses_on_more_than_one_location"
    t.index ["user_id"], name: "index_businesses_on_user_id"
  end

  create_table "friendly_id_slugs", force: :cascade do |t|
    t.string "slug", null: false
    t.integer "sluggable_id", null: false
    t.string "sluggable_type", limit: 50
    t.string "scope"
    t.datetime "created_at"
    t.index ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true
    t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type"
    t.index ["sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_id"
    t.index ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type"
  end

  create_table "locations", force: :cascade do |t|
    t.string "location_name"
    t.string "address_line_1"
    t.string "address_line_2"
    t.string "city"
    t.string "region"
    t.string "country"
    t.string "postal_code"
    t.string "website"
    t.string "phone_number"
    t.string "location_contact_email"
    t.bigint "business_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "slug", null: false
    t.string "custom_url", null: false
    t.index ["business_id"], name: "index_locations_on_business_id"
    t.index ["custom_url"], name: "index_locations_on_custom_url", unique: true
    t.index ["slug"], name: "index_locations_on_slug", unique: true
  end

  create_table "roles", force: :cascade do |t|
    t.string "name"
    t.string "resource_type"
    t.bigint "resource_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id"
    t.index ["resource_type", "resource_id"], name: "index_roles_on_resource_type_and_resource_id"
  end

  create_table "sites", force: :cascade do |t|
    t.bigint "location_id"
    t.string "site"
    t.string "url"
    t.string "review_site_id"
    t.integer "number_of_reviews"
    t.decimal "average_rating"
    t.jsonb "extra_data", default: {}, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["extra_data"], name: "index_sites_on_extra_data", using: :gin
    t.index ["location_id"], name: "index_sites_on_location_id"
  end

  create_table "users", force: :cascade do |t|
    t.string "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer "sign_in_count", default: 0, null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.inet "current_sign_in_ip"
    t.inet "last_sign_in_ip"
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

  create_table "users_roles", id: false, force: :cascade do |t|
    t.bigint "user_id"
    t.bigint "role_id"
    t.index ["role_id"], name: "index_users_roles_on_role_id"
    t.index ["user_id", "role_id"], name: "index_users_roles_on_user_id_and_role_id"
    t.index ["user_id"], name: "index_users_roles_on_user_id"
  end

  add_foreign_key "businesses", "users"
  add_foreign_key "locations", "businesses"
  add_foreign_key "sites", "locations"
end

1 个答案:

答案 0 :(得分:3)

要授权记录集合,通常使用范围执行此操作,而不是授权操作本身。换句话说,而不是:

def index
  @sites = @location.sites.all
  authorize Site
end

你需要这样做:

def index
  @sites = policy_scope(@location.sites)
end

由于站点和用户之间的链接经历了多个模型,因此您不幸需要JOIN一直回到用户,以便在SQL中执行此查询:

class SitePolicy < ApplicationPolicy
  class Scope < Scope
    def resolve
      if user.has_role? :admin
        scope.all
      else
        scope.joins(location: :business)
             .where(locations: {businesses: {user: user}})
      end
    end
  end
end

遵循此设计模式是Pundit recommends向您的应用程序添加以下代码的原因:

class ApplicationController < ActionController::Base
  include Pundit
  after_action :verify_authorized, except: :index
  after_action :verify_policy_scoped, only: :index
end