Ruby on Rails:单表单创建多关联对象问题求助
问题描述
开发一款可选择航班与乘客数量的应用,根据params[:_passengers]和params[:_chosen_flight_id]生成乘客信息收集表单,提交至bookings#create动作时,出现Unpermitted parameters及ActiveModel::ForbiddenAttributesError错误,需解决如何遍历参数创建booking-passenger关联对。
错误日志
Started POST "/bookings" for 127.0.0.1 at 2024-12-18 17:18:29 +0100
Processing by BookingsController#create as TURBO_STREAM
Parameters: {"authenticity_token"=>"[FILTERED]", "_passengers"=>"2", "_chosen_flight_id"=>"7", "booking"=>{"0"=>{"passenger_attributes"=>{"passport_no"=>"123123123", "name"=>"Marek", "surname"=>"Surname"}}, "1"=>{"passenger_attributes"=>{"passport_no"=>"123123121", "name"=>"Marek", "surname"=>"Example"}}}, "button"=>""}
Flight Load (0.2ms) SELECT "flights".* FROM "flights" WHERE "flights"."id" = $1 LIMIT $2 [["id", 7], ["LIMIT", 1]]
↳ app/controllers/bookings_controller.rb:14:in `create'
Unpermitted parameters: :0, :1. Context: { controller: BookingsController, action: create, request: #ActionDispatch::Request:0x00007f49722664a8, params: {"authenticity_token"=>"[FILTERED]", "_passengers"=>"2", "_chosen_flight_id"=>"7", "booking"=>{"0"=>{"passenger_attributes"=>{"passport_no"=>"123123123", "name"=>"Marek", "surname"=>"Surname"}}, "1"=>{"passenger_attributes"=>{"passport_no"=>"123123121", "name"=>"Marek", "surname"=>"Example"}}}, "button"=>"", "controller"=>"bookings", "action"=>"create"} }
Completed 500 Internal Server Error in 2ms (ActiveRecord: 0.2ms (1 query, 0 cached) | GC: 0.0ms)ActiveModel::ForbiddenAttributesError (ActiveModel::ForbiddenAttributesError):
相关代码
BookingsController
class BookingsController < ApplicationController def new @bookings = [] params[:_passengers].to_i.times do @bookings << Booking.new end @bookings.each do |booking| booking.build_passenger end @flight = Flight.find(params[:_chosen_flight_id]) end def create @flight = Flight.find(params[:_chosen_flight_id]) params[:_passengers].to_i.times do i = 0 @booking = Booking.new @booking.flight = @flight @booking.build_passenger(params[:booking]["#{i}"][:passenger_attributes]) i = i + 1 puts "Booking no#{i}:" puts @booking.inspect end end private def booking_params params.require(:booking).permit(:flight_id, passenger_attributes: [:passport_no, :name, :surname]) end end
bookings/new.html.erb 表单
<%= form_tag bookings_path do %> <%= hidden_field_tag :_passengers, params[:_passengers] %> <%= hidden_field_tag :_chosen_flight_id, params[:_chosen_flight_id] %> <ul> <% @bookings.each_with_index do |booking, i| %> Passenger information: <%= fields_for "booking[#{i}]", booking do |booking_form| %> <%= booking_form.fields_for :passenger do |passenger_form| %> <li> <%= passenger_form.label :passport_no %> <%= passenger_form.text_field :passport_no %> <%= passenger_form.label :name %> <%= passenger_form.text_field :name %> <%= passenger_form.label :surname %> <%= passenger_form.text_field :surname %> </li> <% end %> <% end %> <% end %> <%= button_tag "SUBMIT" %> </ul> <% end %>
解决方案
1. 修正参数强允许规则
原booking_params无法处理带索引键的多预订参数,需修改为支持多条目格式:
private def booking_params # 允许所有索引键,并放行对应的乘客属性 params.require(:booking).permit( *params[:booking].keys, passenger_attributes: [:passport_no, :name, :surname] ) end
2. 修复create动作的遍历逻辑
原循环中i的初始化位置错误,导致永远只处理第一个乘客数据,同时必须使用强参数而非直接操作原始params:
def create @flight = Flight.find(params[:_chosen_flight_id]) permitted_bookings = booking_params permitted_bookings.each_value do |booking_attrs| booking = Booking.new(flight: @flight) booking.build_passenger(booking_attrs[:passenger_attributes]) # 处理保存逻辑,可添加错误捕获 if booking.save puts "预订创建成功:#{booking.inspect}" else puts "预订创建失败:#{booking.errors.full_messages.join(', ')}" end end # 添加响应逻辑,示例: redirect_to flights_path, notice: "预订提交完成" end
3. 优化表单参数格式(推荐)
将表单字段改为数组格式,更符合Rails嵌套参数规范,简化参数处理:
<%= form_tag bookings_path do %> <%= hidden_field_tag :_passengers, params[:_passengers] %> <%= hidden_field_tag :_chosen_flight_id, params[:_chosen_flight_id] %> <ul> <% @bookings.each do |booking| %> Passenger information: <%= fields_for "bookings[]", booking do |booking_form| %> <%= booking_form.fields_for :passenger do |passenger_form| %> <li> <%= passenger_form.label :passport_no %> <%= passenger_form.text_field :passport_no %> <%= passenger_form.label :name %> <%= passenger_form.text_field :name %> <%= passenger_form.label :surname %> <%= passenger_form.text_field :surname %> </li> <% end %> <% end %> <% end %> <%= button_tag "SUBMIT" %> </ul> <% end %>
对应强参数调整为:
private def booking_params params.require(:bookings).map do |booking_attrs| booking_attrs.permit(passenger_attributes: [:passport_no, :name, :surname]) end end
create动作遍历数组参数:
def create @flight = Flight.find(params[:_chosen_flight_id]) permitted_bookings = booking_params permitted_bookings.each do |booking_attrs| booking = Booking.new(flight: @flight) booking.build_passenger(booking_attrs[:passenger_attributes]) booking.save! # 或添加错误处理逻辑 end redirect_to flights_path, notice: "所有预订已创建" end
关键问题说明
- Unpermitted parameters: 原强参数规则仅支持单个预订的属性,无法识别表单提交的带索引键(0、1)的多预订数据,需调整规则允许这些条目。
- ActiveModel::ForbiddenAttributesError: 直接使用未经过滤的原始参数违反Rails安全机制,必须通过
booking_params获取允许的参数。 - 循环逻辑错误: 原create方法中
i = 0放在循环内部,导致每次循环重置索引,无法遍历所有乘客数据。
内容的提问来源于stack exchange,提问作者mareks

