Pundit 在调用授权对象的循环中引发 AuthorizationNotPerformedError
Pundit raising AuthorizationNotPerformedError on a loop where authorize object is called
我有两个 rails 应用程序:
一个是 "front-end app" 负责显示数据,从用户那里获取输入并将数据发送到 API(第二个应用程序)。第二个是 API 处理数据库操作并发送 JSON 到前端应用程序。
我有一个操作,我的用户可以决定他想在他的 hotel
中创建多少 rooms
以及他应该在每个 room
中创建多少 beds
。前端应用程序的表单如下所示:
<h1>Add Rooms</h1>
<form action="http://localhost:3000/hotels/<%=params[:hotel_id]%>/rooms/multiple_create" method="post">
<input name="authenticity_token" value="<%= form_authenticity_token %>" type="hidden">
<div class="form-group">
<%= label_tag 'room[room_number]', "Number of rooms" %>
<input type="number" name="room[room_number]" required>
</div>
<div class="form-group">
<%= label_tag "room[bed_number]", "Number of beds by room" %>
<input type="number" name= "room[bed_number]" required>
</div>
<div class="form-group">
<%= label_tag "room[content]", "Room Type" %>
<input type="text" name= "room[content]" required>
</div>
<input type="submit">
<% if @errors %>
<ul class="list-unstyled">
<%@errors.each do |error|%>
<li class="has-error"><%=error%></li>
<% end -%>
</ul>
<% end -%>
</form>
此表单链接到我的 RoomsController#multiple_create 前端应用程序上的操作,负责将表单数据发送到 API :
class RoomsController < ApplicationController
def multiple_new
end
def multiple_create
@response = HTTParty.post(ENV['API_ADDRESS']+'api/v1/hotels/'+ params[:hotel_id]+'/rooms/multiple_create',
:body => { :room =>{
:room_number => params[:room][:room_number],
:bed_number => params[:room][:bed_number],
}
}.to_json,
:headers => { 'X-User-Email' => session[:user_email], 'X-User-Token'=> session[:user_token], 'Content-Type' => 'application/json' } )
# Erreur Mauvais Auth Token
if @response["error"] == "You need to sign in or sign up before continuing."
redirect_to unauthorized_path
# erreur de validation
elsif @response["error"]
raise
@errors = @response["errors"]
render :multiple_new
else
raise
redirect_to account_path(account_id)
end
end
end
我的 room_controller.rb 中有一个相应的方法负责创建房间、每个房间的床以及每个床的插槽。在 API 上,我使用 Pundit 进行授权。
def multiple_create
i = 0
start_date = Date.today
ActiveRecord::Base.transaction do
while i < params[:room_number].to_i
@room = @hotel.rooms.build(room_params)
authorize @room
if @room.save
(0...params[:bed_number].to_i).each do
@bed = @room.beds.create!
for a in 0...60
@bed.slots.create!(available: true, date: start_date + a.days )
end
end
i+=1
else
break
end
end
end
if i == params[:room_number]
render json: {success: "All rooms and beds where successfully created"}
else
render json: {error: "There was a problem during room creation process. Please try again later"}
end
end
每次我尝试post这个方法时,我得到:
Pundit::AuthorizationNotPerformedError - Pundit::AuthorizationNotPerformedError:
pundit (1.0.1) lib/pundit.rb:103:in `verify_authorized
在我看来,我实际上是在循环期间保存新房间之前调用授权@room。我的 room_policy.rb
:
中有一个方法 multiple_create
def multiple_create?
(user && record.hotel.account.admin == user) || (user && user.manager && (user.account == record.hotel.account))
end
在我的 API base_controller 中我有 :
after_action :verify_authorized, except: :index
为什么我会在这里收到专家错误?
这是一个非常有创意的解决方案 - 然而,有一种更简单、更好的方法来处理插入/更新多条记录。
class Hotel
has_many :rooms
accepts_nested_attributes_for :rooms
end
accepts_nested_attributes_for
表示您可以创建房间:
@hotel = Hotel.new(
rooms_attributes: [
{ foo: 'bar' },
{ foo: 'baz' }
]
)
@hotel.save # will insert both the hotel and rooms
您可以像这样创建一个表单:
<%= form_for(@hotel) |f| %>
<%= f.fields_for :rooms do |rf| %>
<%= f.foo %>
<% end %>
<% end %>
然后像这样在你的控制器中处理它:
class HotelsController
def update
@hotel.update(hotel_params)
respond_with(@hotel)
end
private
def hotel_params
params.require(:hotel).permit(rooms_attributes: [ :foo ])
end
end
您可以使用多个 accepts_nested_attributes_for
和 fields
将其嵌套得更深。但总的来说,当你往下走不止一个层次时,就会产生严重的代码味道。而是将其拆分为多个控制器。
请注意,您并不是真的需要 create_multiple
或 update_multiple
操作。
那么回到问题的核心,我该如何认证呢? 保持简单愚蠢。
def update
authorize @hotel
# ...
end
并在您的 HotelPolicy
.
中处理
def update?
return false unless user
record.account.admin == user || (user.manager && (user.account == record.account))
end
编辑
根据您对想要执行的操作的描述,您可以简单地在酒店模型上添加自定义 getter / setter。
class Hotel < ActiveRecord::Base
has_many :rooms
# custom getter for binding form inputs
def number_of_rooms
rooms.count
end
# custom setter which actually creates the associated records
# this subtracts the existing number of rooms so that setting
# hotel.number_of_rooms = 50 on an existing record with 20 rooms
# will result in a total of 50 not 70.
def number_of_rooms=(number)
(number.to_i - number_of_rooms).times { rooms.new }
end
end
当您像这样创建或更新记录时:
[14] pry(main)> h = Hotel.new(number_of_rooms: 3)
=> #<Hotel:0x007feeb6ee2b20 id: nil, created_at: nil, updated_at: nil>
[15] pry(main)> h.save
(0.1ms) begin transaction
SQL (0.4ms) INSERT INTO "hotels" ("created_at", "updated_at") VALUES (?, ?) [["created_at", "2016-02-08 13:17:06.723803"], ["updated_at", "2016-02-08 13:17:06.723803"]]
SQL (0.2ms) INSERT INTO "rooms" ("hotel_id", "created_at", "updated_at") VALUES (?, ?, ?) [["hotel_id", 3], ["created_at", "2016-02-08 13:17:06.726157"], ["updated_at", "2016-02-08 13:17:06.726157"]]
SQL (0.4ms) INSERT INTO "rooms" ("hotel_id", "created_at", "updated_at") VALUES (?, ?, ?) [["hotel_id", 3], ["created_at", "2016-02-08 13:17:06.728636"], ["updated_at", "2016-02-08 13:17:06.728636"]]
SQL (0.1ms) INSERT INTO "rooms" ("hotel_id", "created_at", "updated_at") VALUES (?, ?, ?) [["hotel_id", 3], ["created_at", "2016-02-08 13:17:06.730291"], ["updated_at", "2016-02-08 13:17:06.730291"]]
(1.6ms) commit transaction
=> true
[16] pry(main)>
它将添加构建 N 个关联房间。然后就可以正常保存记录了。
您只需为 number_of_rooms
:
添加一个表单输入,即可将其添加到您的普通酒店更新/创建操作中
<%= form_for(@hotel) |f| %>
<% # ... %>
<%= f.label :number_of_rooms %>
<%= f.number_field :number_of_rooms %>
<% end %>
此输入将在 params[:hotel][:number_of_rooms]
中。您应该将其添加到强参数白名单中。
请注意,您无法为每个房间设置属性。按照上面的建议进行授权。
我有两个 rails 应用程序: 一个是 "front-end app" 负责显示数据,从用户那里获取输入并将数据发送到 API(第二个应用程序)。第二个是 API 处理数据库操作并发送 JSON 到前端应用程序。
我有一个操作,我的用户可以决定他想在他的 hotel
中创建多少 rooms
以及他应该在每个 room
中创建多少 beds
。前端应用程序的表单如下所示:
<h1>Add Rooms</h1>
<form action="http://localhost:3000/hotels/<%=params[:hotel_id]%>/rooms/multiple_create" method="post">
<input name="authenticity_token" value="<%= form_authenticity_token %>" type="hidden">
<div class="form-group">
<%= label_tag 'room[room_number]', "Number of rooms" %>
<input type="number" name="room[room_number]" required>
</div>
<div class="form-group">
<%= label_tag "room[bed_number]", "Number of beds by room" %>
<input type="number" name= "room[bed_number]" required>
</div>
<div class="form-group">
<%= label_tag "room[content]", "Room Type" %>
<input type="text" name= "room[content]" required>
</div>
<input type="submit">
<% if @errors %>
<ul class="list-unstyled">
<%@errors.each do |error|%>
<li class="has-error"><%=error%></li>
<% end -%>
</ul>
<% end -%>
</form>
此表单链接到我的 RoomsController#multiple_create 前端应用程序上的操作,负责将表单数据发送到 API :
class RoomsController < ApplicationController
def multiple_new
end
def multiple_create
@response = HTTParty.post(ENV['API_ADDRESS']+'api/v1/hotels/'+ params[:hotel_id]+'/rooms/multiple_create',
:body => { :room =>{
:room_number => params[:room][:room_number],
:bed_number => params[:room][:bed_number],
}
}.to_json,
:headers => { 'X-User-Email' => session[:user_email], 'X-User-Token'=> session[:user_token], 'Content-Type' => 'application/json' } )
# Erreur Mauvais Auth Token
if @response["error"] == "You need to sign in or sign up before continuing."
redirect_to unauthorized_path
# erreur de validation
elsif @response["error"]
raise
@errors = @response["errors"]
render :multiple_new
else
raise
redirect_to account_path(account_id)
end
end
end
我的 room_controller.rb 中有一个相应的方法负责创建房间、每个房间的床以及每个床的插槽。在 API 上,我使用 Pundit 进行授权。
def multiple_create
i = 0
start_date = Date.today
ActiveRecord::Base.transaction do
while i < params[:room_number].to_i
@room = @hotel.rooms.build(room_params)
authorize @room
if @room.save
(0...params[:bed_number].to_i).each do
@bed = @room.beds.create!
for a in 0...60
@bed.slots.create!(available: true, date: start_date + a.days )
end
end
i+=1
else
break
end
end
end
if i == params[:room_number]
render json: {success: "All rooms and beds where successfully created"}
else
render json: {error: "There was a problem during room creation process. Please try again later"}
end
end
每次我尝试post这个方法时,我得到:
Pundit::AuthorizationNotPerformedError - Pundit::AuthorizationNotPerformedError:
pundit (1.0.1) lib/pundit.rb:103:in `verify_authorized
在我看来,我实际上是在循环期间保存新房间之前调用授权@room。我的 room_policy.rb
:
def multiple_create?
(user && record.hotel.account.admin == user) || (user && user.manager && (user.account == record.hotel.account))
end
在我的 API base_controller 中我有 :
after_action :verify_authorized, except: :index
为什么我会在这里收到专家错误?
这是一个非常有创意的解决方案 - 然而,有一种更简单、更好的方法来处理插入/更新多条记录。
class Hotel
has_many :rooms
accepts_nested_attributes_for :rooms
end
accepts_nested_attributes_for
表示您可以创建房间:
@hotel = Hotel.new(
rooms_attributes: [
{ foo: 'bar' },
{ foo: 'baz' }
]
)
@hotel.save # will insert both the hotel and rooms
您可以像这样创建一个表单:
<%= form_for(@hotel) |f| %>
<%= f.fields_for :rooms do |rf| %>
<%= f.foo %>
<% end %>
<% end %>
然后像这样在你的控制器中处理它:
class HotelsController
def update
@hotel.update(hotel_params)
respond_with(@hotel)
end
private
def hotel_params
params.require(:hotel).permit(rooms_attributes: [ :foo ])
end
end
您可以使用多个 accepts_nested_attributes_for
和 fields
将其嵌套得更深。但总的来说,当你往下走不止一个层次时,就会产生严重的代码味道。而是将其拆分为多个控制器。
请注意,您并不是真的需要 create_multiple
或 update_multiple
操作。
那么回到问题的核心,我该如何认证呢? 保持简单愚蠢。
def update
authorize @hotel
# ...
end
并在您的 HotelPolicy
.
def update?
return false unless user
record.account.admin == user || (user.manager && (user.account == record.account))
end
编辑
根据您对想要执行的操作的描述,您可以简单地在酒店模型上添加自定义 getter / setter。
class Hotel < ActiveRecord::Base
has_many :rooms
# custom getter for binding form inputs
def number_of_rooms
rooms.count
end
# custom setter which actually creates the associated records
# this subtracts the existing number of rooms so that setting
# hotel.number_of_rooms = 50 on an existing record with 20 rooms
# will result in a total of 50 not 70.
def number_of_rooms=(number)
(number.to_i - number_of_rooms).times { rooms.new }
end
end
当您像这样创建或更新记录时:
[14] pry(main)> h = Hotel.new(number_of_rooms: 3)
=> #<Hotel:0x007feeb6ee2b20 id: nil, created_at: nil, updated_at: nil>
[15] pry(main)> h.save
(0.1ms) begin transaction
SQL (0.4ms) INSERT INTO "hotels" ("created_at", "updated_at") VALUES (?, ?) [["created_at", "2016-02-08 13:17:06.723803"], ["updated_at", "2016-02-08 13:17:06.723803"]]
SQL (0.2ms) INSERT INTO "rooms" ("hotel_id", "created_at", "updated_at") VALUES (?, ?, ?) [["hotel_id", 3], ["created_at", "2016-02-08 13:17:06.726157"], ["updated_at", "2016-02-08 13:17:06.726157"]]
SQL (0.4ms) INSERT INTO "rooms" ("hotel_id", "created_at", "updated_at") VALUES (?, ?, ?) [["hotel_id", 3], ["created_at", "2016-02-08 13:17:06.728636"], ["updated_at", "2016-02-08 13:17:06.728636"]]
SQL (0.1ms) INSERT INTO "rooms" ("hotel_id", "created_at", "updated_at") VALUES (?, ?, ?) [["hotel_id", 3], ["created_at", "2016-02-08 13:17:06.730291"], ["updated_at", "2016-02-08 13:17:06.730291"]]
(1.6ms) commit transaction
=> true
[16] pry(main)>
它将添加构建 N 个关联房间。然后就可以正常保存记录了。
您只需为 number_of_rooms
:
<%= form_for(@hotel) |f| %>
<% # ... %>
<%= f.label :number_of_rooms %>
<%= f.number_field :number_of_rooms %>
<% end %>
此输入将在 params[:hotel][:number_of_rooms]
中。您应该将其添加到强参数白名单中。
请注意,您无法为每个房间设置属性。按照上面的建议进行授权。