【问题标题】:Pundit Authorizing Index Action with Non-directly associated modelsPundit 使用非直接关联模型授权索引操作
【发布时间】:2018-06-01 09:00:45
【问题描述】:

我正在努力为与我的用户模型没有直接关系的模型授权索引。实际上,我正在努力思考 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

【问题讨论】:

  • 已编辑以包含 schema.rb

标签: ruby-on-rails ruby pundit


【解决方案1】:

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

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

【讨论】:

  • 嗨,汤姆,感谢您在这方面的帮助!运行时出现以下错误:``` PG::UndefinedTable: ERROR: missing FROM-clause entry for table "business" LINE 1: ...business_id" WHERE "sites"."location_id" = $1 AND “business”... ^ : SELECT "sites".* FROM "sites" INNER JOIN "locations" ON "locations"."id" = "sites"."location_id" INNER JOIN "businesses" ON "businesses"。 "id" = "locations"."business_id" WHERE "sites"."location_id" = $1 AND "business"."users" = $2 /跨度>
  • 我确实说过我可能弄错了复数形式...您可能需要joins(locations: :businesses) 或其他东西。
  • 您确实对此提出了警告 :) 我将尝试使用它,看看我能做什么 - SQL 对我来说是另一个弱点。感谢您迄今为止的帮助!
  • 啊,所以将其更改为您建议的 joins(locations: :businesses) 得到了以下错误:Can't join 'Site' to association named 'locations'; perhaps you misspelled it? 我会继续玩这个。非常感谢!
  • 所以....也许是where 中的部分实际上需要复数? :P
猜你喜欢
  • 1970-01-01
  • 2015-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多