You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 12:59:52