我有两个rails app: 一个是“前端应用程序”,负责显示数据,从用户获取输入并将数据发送到API(第二个应用程序)。第二个是处理数据库操作并将JSON发送到前端应用程序的API。
我有一个操作,我的用户决定他想要在rooms
中创建多少hotel
,以及他应该在每个beds
中创建多少room
。前端应用程序上的表单如下所示:
<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
每当我尝试发布这种方法时,我得到:
Pundit::AuthorizationNotPerformedError - Pundit::AuthorizationNotPerformedError:
pundit (1.0.1) lib/pundit.rb:103:in `verify_authorized
在我看来,在循环中保存新房间之前,我实际上是在调用authorize @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
为什么我在这里得到这个权威错误?
答案 0 :(得分:0)
这是一个非常有创意的解决方案 - 但是有一种更简单,更好的方式来处理插入/更新多个记录。
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]
中。您应该将其添加到强参数白名单中。
请注意,您无法为每个房间设置属性。按照上面的建议进行授权。