枚举角色专家
Pundit for enum roles
上下文
在我的应用程序中,一个 user.admin 可以创建多个酒店。
对于这些个人酒店中的每一个,user.admin可以邀请一个(或多个)user.employee或另一个user.admin.
因此,用户与酒店之间存在多对多的关系。
问题
- 当受邀用户是 user.admin 时,一切都很顺利。受邀 user.admin 可以访问
hotels#show
。
- 但是当受邀用户是 user.employee he/she 时无法访问
hotels#show
- 角色分配似乎有效
current_user.employee? => true
- Hotels/show.html.erb是含有
<p>show_page</p>
的碱液
错误信息
网络浏览器中的消息:
localhost redirected you too many times.
Try clearing your cookies.
ERR_TOO_MANY_REDIRECTS
控制台
Started GET "/" for ::1 at 2019-11-07 09:27:16 +0100
Processing by PagesController#home as HTML
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ORDER BY "users"."id" ASC LIMIT [["id", 20], ["LIMIT", 1]]
↳ app/controllers/pages_controller.rb:6
Hotel Exists (0.5ms) SELECT 1 AS one FROM "hotels" INNER JOIN "user_hotels" ON "hotels"."id" = "user_hotels"."hotel_id" WHERE "user_hotels"."user_id" = LIMIT [["user_id", 20], ["LIMIT", 1]]
↳ app/controllers/pages_controller.rb:7
Hotel Load (0.2ms) SELECT "hotels".* FROM "hotels" WHERE "hotels"."id" IS NULL LIMIT [["LIMIT", 1]]
↳ app/controllers/pages_controller.rb:8
Hotel Load (0.2ms) SELECT "hotels".* FROM "hotels" INNER JOIN "user_hotels" ON "hotels"."id" = "user_hotels"."hotel_id" WHERE "user_hotels"."user_id" = ORDER BY "hotels"."id" DESC LIMIT [["user_id", 20], ["LIMIT", 1]]
↳ app/controllers/pages_controller.rb:12
Redirected to http://localhost:3000/hotels/9
Completed 302 Found in 6ms (ActiveRecord: 1.2ms)
代码
路线
Rails.application.routes.draw do
devise_for :users
resources :hotels do
devise_for :users, :controllers => { :invitations => 'users/invitations' }
end
end
application_controller
class ApplicationController < ActionController::Base
before_action :set_locale
before_action :configure_permitted_parameters, if: :devise_controller?
def set_locale
I18n.locale = params.fetch(:locale, I18n.default_locale).to_sym
end
def default_url_options
{ locale: I18n.locale == I18n.default_locale ? nil : I18n.locale }
end
protect_from_forgery with: :exception
before_action :authenticate_user!
include Pundit
# Pundit: white-list approach.
after_action :verify_authorized, except: :index, unless: :skip_pundit?
after_action :verify_policy_scoped, only: :index, unless: :skip_pundit?
# Uncomment when you *really understand* Pundit!
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized
flash[:alert] = "You are not authorized to perform this action."
redirect_to(root_path)
end
def skip_pundit?
devise_controller? || params[:controller] =~ /(^(rails_)?admin)|(^pages$)/
end
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:invite, keys: [:role, :user_parks_attributes])
end
end
页数#home
class PagesController < ApplicationController
skip_before_action :authenticate_user!, :raise => false
skip_after_action :verify_authorized
def home
if !current_user.nil?
if current_user.hotels.any?
if Hotel.find_by_id params[:id]
@hotel = Hotel.find(params[:id])
redirect_to hotel_path(@hotel)
else
@hotel = current_user.hotels.last
redirect_to hotel_path(@hotel)
# redirect_to :controller => 'hotels' , :action => 'show', :id => @hotel.id
end
else
redirect_to hotels_path
end
else
redirect_to pages_landing_page_path
end
end
end
hotel_policy
class HotelPolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.admin? || user.employee?
scope.joins(hotel: :user_hotels).where(user_hotels: { user_id: user.id })
else
raise Pundit::NotAuthorizedError
end
end
end
def show?
user.admin? || user.employee?
end
end
型号
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
has_many :user_hotels, dependent: :destroy
has_many :hotels, through: :user_hotels
accepts_nested_attributes_for :user_hotels
enum role: [:owner, :admin, :employee]
after_initialize :set_default_role, :if => :new_record?
def set_default_role
self.role ||= :admin
end
devise :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :invitable
end
class UserHotel < ApplicationRecord
belongs_to :hotel
belongs_to :user
end
class Hotel < ApplicationRecord
has_many :user_hotels, dependent: :destroy
has_many :users, through: :user_hotels
accepts_nested_attributes_for :users, allow_destroy: true, reject_if: ->(attrs) { attrs['email'].blank? || attrs['role'].blank?}
end
hotels_controller
class HotelsController < ApplicationController
def show
if Hotel.find_by_id params[:id]
@hotel = Hotel.find(params[:id])
else
@hotel = current_user.hotels.last
end
authorize @hotel
@reservations = @hotel.reservations
end
end
原来是一个愚蠢的错误,这个错误在我的 application_policy.rb 中应该包含 user.employee 在初始化中。
def initialize(user, record)
raise Pundit::NotAuthorizedError, "must be logged in with a user account" unless (user.admin? || user.employee?)
@user = user
@record = record
end
上下文
在我的应用程序中,一个 user.admin 可以创建多个酒店。
对于这些个人酒店中的每一个,user.admin可以邀请一个(或多个)user.employee或另一个user.admin.
因此,用户与酒店之间存在多对多的关系。
问题
- 当受邀用户是 user.admin 时,一切都很顺利。受邀 user.admin 可以访问
hotels#show
。 - 但是当受邀用户是 user.employee he/she 时无法访问
hotels#show
- 角色分配似乎有效
current_user.employee? => true
- Hotels/show.html.erb是含有
<p>show_page</p>
的碱液
错误信息
网络浏览器中的消息:
localhost redirected you too many times.
Try clearing your cookies.
ERR_TOO_MANY_REDIRECTS
控制台
Started GET "/" for ::1 at 2019-11-07 09:27:16 +0100
Processing by PagesController#home as HTML
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ORDER BY "users"."id" ASC LIMIT [["id", 20], ["LIMIT", 1]]
↳ app/controllers/pages_controller.rb:6
Hotel Exists (0.5ms) SELECT 1 AS one FROM "hotels" INNER JOIN "user_hotels" ON "hotels"."id" = "user_hotels"."hotel_id" WHERE "user_hotels"."user_id" = LIMIT [["user_id", 20], ["LIMIT", 1]]
↳ app/controllers/pages_controller.rb:7
Hotel Load (0.2ms) SELECT "hotels".* FROM "hotels" WHERE "hotels"."id" IS NULL LIMIT [["LIMIT", 1]]
↳ app/controllers/pages_controller.rb:8
Hotel Load (0.2ms) SELECT "hotels".* FROM "hotels" INNER JOIN "user_hotels" ON "hotels"."id" = "user_hotels"."hotel_id" WHERE "user_hotels"."user_id" = ORDER BY "hotels"."id" DESC LIMIT [["user_id", 20], ["LIMIT", 1]]
↳ app/controllers/pages_controller.rb:12
Redirected to http://localhost:3000/hotels/9
Completed 302 Found in 6ms (ActiveRecord: 1.2ms)
代码
路线
Rails.application.routes.draw do
devise_for :users
resources :hotels do
devise_for :users, :controllers => { :invitations => 'users/invitations' }
end
end
application_controller
class ApplicationController < ActionController::Base
before_action :set_locale
before_action :configure_permitted_parameters, if: :devise_controller?
def set_locale
I18n.locale = params.fetch(:locale, I18n.default_locale).to_sym
end
def default_url_options
{ locale: I18n.locale == I18n.default_locale ? nil : I18n.locale }
end
protect_from_forgery with: :exception
before_action :authenticate_user!
include Pundit
# Pundit: white-list approach.
after_action :verify_authorized, except: :index, unless: :skip_pundit?
after_action :verify_policy_scoped, only: :index, unless: :skip_pundit?
# Uncomment when you *really understand* Pundit!
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized
flash[:alert] = "You are not authorized to perform this action."
redirect_to(root_path)
end
def skip_pundit?
devise_controller? || params[:controller] =~ /(^(rails_)?admin)|(^pages$)/
end
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:invite, keys: [:role, :user_parks_attributes])
end
end
页数#home
class PagesController < ApplicationController
skip_before_action :authenticate_user!, :raise => false
skip_after_action :verify_authorized
def home
if !current_user.nil?
if current_user.hotels.any?
if Hotel.find_by_id params[:id]
@hotel = Hotel.find(params[:id])
redirect_to hotel_path(@hotel)
else
@hotel = current_user.hotels.last
redirect_to hotel_path(@hotel)
# redirect_to :controller => 'hotels' , :action => 'show', :id => @hotel.id
end
else
redirect_to hotels_path
end
else
redirect_to pages_landing_page_path
end
end
end
hotel_policy
class HotelPolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.admin? || user.employee?
scope.joins(hotel: :user_hotels).where(user_hotels: { user_id: user.id })
else
raise Pundit::NotAuthorizedError
end
end
end
def show?
user.admin? || user.employee?
end
end
型号
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
has_many :user_hotels, dependent: :destroy
has_many :hotels, through: :user_hotels
accepts_nested_attributes_for :user_hotels
enum role: [:owner, :admin, :employee]
after_initialize :set_default_role, :if => :new_record?
def set_default_role
self.role ||= :admin
end
devise :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :invitable
end
class UserHotel < ApplicationRecord
belongs_to :hotel
belongs_to :user
end
class Hotel < ApplicationRecord
has_many :user_hotels, dependent: :destroy
has_many :users, through: :user_hotels
accepts_nested_attributes_for :users, allow_destroy: true, reject_if: ->(attrs) { attrs['email'].blank? || attrs['role'].blank?}
end
hotels_controller
class HotelsController < ApplicationController
def show
if Hotel.find_by_id params[:id]
@hotel = Hotel.find(params[:id])
else
@hotel = current_user.hotels.last
end
authorize @hotel
@reservations = @hotel.reservations
end
end
原来是一个愚蠢的错误,这个错误在我的 application_policy.rb 中应该包含 user.employee 在初始化中。
def initialize(user, record)
raise Pundit::NotAuthorizedError, "must be logged in with a user account" unless (user.admin? || user.employee?)
@user = user
@record = record
end