我的"unpermitted parameter: :passenger" Rails错误从何而来?



我正在用Rails构建一个航班预订应用程序,可以让您选择机场,日期和乘客数量。一旦你选择了机场和日期,它就会给你一个单选按钮来选择你想要的航班,一旦你点击提交,你就会被带到一个预订确认页面,在那里你被要求提供乘客信息。

确认页面(booking #new)有一个嵌套表单,用于在预订对象中包含乘客。为此,我首先设置了以下模型和关联:

class Passenger < ApplicationRecord
belongs_to :booking
end
class Booking < ApplicationRecord
belongs_to :flight
has_many :passengers 
accepts_nested_attributes_for :passengers 

end

以及产生以下模式表的相关迁移:


create_table "bookings", force: :cascade do |t|
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "flight_id"
t.integer "passenger_id"
end
create_table "passengers", force: :cascade do |t|
t.string "name"
t.string "email"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "booking_id"
t.index ["booking_id"], name: "index_passengers_on_booking_id"
end

据我所知,流程是这样的:用户选择航班->用户提交航班,通过控制器上的#new方法转到预订#new:

class BookingsController < ApplicationController
def new
@booking = Booking.new
@flight = Flight.find(params[:flight_id]) 
params[:passengers_number].to_i.times do #params passed from select flight page
@booking.passengers.build
end
end

然后,我构建的表单接管new.html.erb:

<%= form_with model: @booking, url: bookings_path do |f| %>
<%= f.hidden_field :flight_id, value: @flight.id %>
<% @booking.passengers.each_with_index do |passenger, index| %>
<%= f.fields_for passenger, index: index do |form| %>
<h4><%= "Passenger #{index+1}"%> <br> </h4>
<%= form.label :name, "Full name:" %>
<%= form.text_field :name %>
<%= form.label :email, "Email:" %>
<%= form.email_field :email %>
<% end %>
<% end %>
<%= f.submit "Confirm details"%>
<% end %>

我填写姓名和电子邮件,点击"确认详细信息",我在我的终端上得到这个错误:

Unpermitted parameter: :passenger

为什么?我已经在我的预订模型上设置了accepts_nested_attributes_for :passengers,我的booking_params方法是:

def booking_params
params.require(:booking).permit(:flight_id,
:passengers_attributes => [:name, :email, :passenger_id, :created_at, :updated_at])
end
我的#create方法是:
def create
@booking = Booking.new(booking_params)
respond_to do |format|
if @booking.save
format.html { redirect_to @booking, notice: "booking was successfully created." }
format.json { render :show, status: :created, location: @booking }
else
format.html { redirect_to root_path,  alert: "booking failed, #{@booking.errors.full_messages.first}" , status: :unprocessable_entity }
format.json { render json: @booking.errors, status: :unprocessable_entity }
end
end
end

有什么我没有允许的吗?注意,如果我将booking_params设置为params.require(:booking).permit!,它会给我一个unknown attribute 'passenger' for Booking错误。但至少就我所知,我已经定义了乘客和预订的关联和数据库。

Thanks in advance

编辑:产生错误的服务器日志是:

Started POST "/bookings" for ::1 at 2021-08-27 17:52:17 +0300
Processing by BookingsController#create as HTML
Parameters: {"authenticity_token"=>"[FILTERED]", "booking"=>{"flight_id"=>"5", "passenger"=>{"0"=>{"name"=>"Jason Smason", "email"=>"jason@ymail.com"}, "1"=>{"name"=>"Joe Smith", "email"=>"Joe@smith.com"}}}, "commit"=>"Confirm details"}
Unpermitted parameter: :passenger

编辑2:我听从了PCurell的建议,将我的fields_for更改为<%= f.fields_for :passengers, passenger, index: index do |form| %>,将我的预订参数更改为:passenger => [:name, :email, :passenger_id, :created_at, :updated_at])

生成了一个不同的错误:Unpermitted parameter: passengers_attributes'. So I changed my controller'sbooking_params ' to:

def booking_params 
params.require(:booking).permit(:flight_id, 
:passengers_attributes => [:name, :email, :passenger_id, :created_at, :updated_at],
:passenger => [:name, :email, :passenger_id, :created_at, :updated_at])
end

有了这个,我成功地创建了一个有2个乘客的预订。然而,乘客是空白的;其中nameemail为nil。日志显示:

Parameters: {"authenticity_token"=>"[FILTERED]", "booking"=>{"flight_id"=>"1", "passengers_attributes"=>{"0"=>{"0"=>{"name"=>"John Smith", "email"=>"John@smith.com"}}, "1"=>{"1"=>{"name"=>"Burger King", "email"=>"bk@bk.com"}}}}, "commit"=>"Confirm details"}
Unpermitted parameter: :0
Unpermitted parameter: :1

我可能能够硬编码:0和:1传递,但肯定不是Rails的方式。有没有办法动态地让他们进来?还是我整件事都做错了?

您对fields_for的使用不正确。当您查看指南中的示例时,您将注意到,如果用于集合,则不需要使用.each来包装它。

10.2嵌套表单

下面的表单允许用户创建一个Person和它的相关地址。

<%= form_with model: @person do |form| %>
Addresses:
<ul>
<%= form.fields_for :addresses do |addresses_form| %>
<li>
<%= addresses_form.label :kind %>
<%= addresses_form.text_field :kind %>
<%= addresses_form.label :street %>
<%= addresses_form.text_field :street %>
...
</li>
<% end %>
</ul>
<% end %>

当一个关联接受嵌套属性时,fields_for为关联的每个元素呈现一次block。特别是,如果一个人没有地址,那就什么也没有。一个常见的模式是使控制器生成一个或多个空子节点,以便at至少向用户显示一组字段。下面的例子将结果将在新人员上呈现两组地址字段形式。

def new
@person = Person.new
2.times { @person.addresses.build }
end

将此应用于代码时,删除.each包装器并将passenger更改为:passengers应该可以达到目的。可以通过传递给fields_for块的FormBuilder实例(form)访问index

<%= form_with model: @booking, url: bookings_path do |f| %>
<%= f.hidden_field :flight_id, value: @flight.id %>
<%= f.fields_for :passengers do |form| %>
<h4>Passenger <%= form.index + 1 %></h4>
<%= form.label :name, "Full name:" %>
<%= form.text_field :name %>
<%= form.label :email, "Email:" %>
<%= form.email_field :email %>
<% end %>
<%= f.submit "Confirm details"%>
<% end %>

正如@Rockwell Rice在他的评论中提到的:

这里的问题是您允许使用错误的参数。

def booking_params
params.require(:booking).permit(:flight_id,
:passenger => [:name, :email, :passenger_id, :created_at, :updated_at])
end

虽然你可能会遇到另一个错误。

我认为你的fields_for构造错了。

这应该是你正在寻找的(你不需要改变booking_params)

<%= form_with model: @booking, url: bookings_path do |f| %>
<%= f.hidden_field :flight_id, value: @flight.id %>
<% @booking.passengers.each_with_index do |passenger, index| %>
<%= f.fields_for :passengers, passenger, index: index do |form| %>
<h4><%= "Passenger #{index+1}"%> <br> </h4>
<%= form.label :name, "Full name:" %>
<%= form.text_field :name %>
<%= form.label :email, "Email:" %>
<%= form.email_field :email %>
<% end %>
<% end %>
<%= f.submit "Confirm details"%>
<% end %>

以上应该可以运行。

如果你不太关心索引,这样也可以:

<%= form_with model: @booking, url: bookings_path do |f| %>
<%= f.hidden_field :flight_id, value: @flight.id %>
<%= f.fields_for @booking.passengers do |form| %>
<%= form.label :name, "Full name:" %>
<%= form.text_field :name %>
<%= form.label :email, "Email:" %>
<%= form.email_field :email %>
<% end %>
<%= f.submit "Confirm details"%>
<% end %>