【问题标题】:Create parent and child entry from same nested forms in Rails在 Rails 中从相同的嵌套表单创建父项和子项
【发布时间】:2026-01-19 10:55:02
【问题描述】:

编辑 3:澄清一下,目标和问题是从相同的形式创建 2 个新记录,其中一个是父级,一个是子级。子级需要父级 ID,但父级是从与子级相同的表单创建的。

编辑 2:我想我越来越近了。最后查看日志文件,交易已成功保存,看起来客户端条目开始提交但未保存。代码在下面更新以进行更改。

我遵循Railscast #196 的嵌套表单,只要记录已经创建,我就可以成功地编辑、添加和删除嵌套表单。现在我正在尝试使用嵌套表单来创建新记录。我想我已经完成了 99% 的工作,但我错过了一些我再也看不到的东西。我只需要弄清楚如何将父母的id 传递给孩子。

除了 Railscast 之外,我还使用 this answer 设置 inverse_of 关系并调用保存顺序(尽管使用 create 而不是 save)。我很确定问题出在表单或控制器上(但下面也列出了模型)

嵌套表单(我尝试简化以使其更易于阅读)

编辑 2:删除隐藏字段

<%= form_for(@deal) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>

  <div class="deal-<%= @deal.id %>" >
    <div class="form-group">
      <%= f.label :headline %>
      <%= f.text_field :headline, required: true, placeholder: "Headline" %>
    </div>

    <div class="form-group" id="clients">
      <%= f.fields_for :clients do |client_form| %>
        <div class="field">
          <%= client_form.label :client %><br />
          <%= client_form.text_field :name, placeholder: "Client name" %>
        </div>
      <% end %>
      <%= link_to_add_fields "Add client", f, :clients %>
    </div>  

    <div class="form-group">
      <%= f.label :matter %>
      <%= f.text_field :matter, placeholder: "Matter", rows: "4" %>
    </div>

    <div class="form-group">
      <%= f.label :summary %>
      <%= f.text_area :summary, placeholder: "Deal summary", rows: "4" %>
    </div>

    <div class="form-group">
      <div class="action-area">
        <%= f.submit "Add deal" %>
      </div>
    </div>

  </div>
<% end %>

控制器

编辑 2:包括 deal_id 参数和更改保存调用

class DealsController < ApplicationController
  before_action :require_login

  def new
    @deal = Deal.new
    @client = @deal.clients
  end

  def create
    @deal = current_user.deals.create(deal_params)
    if @deal.save
      flash[:success] = "Your deal was created!"
      redirect_to root_url
    else
      render 'deals/new'
    end
  end

  private

  def deal_params
    params.require(:deal).permit(:headline, :matter, :summary, clients_attributes: [:id, :deal_id, :name, :_destroy])
  end
end

编辑 2:不再在浏览器中产生错误并触发成功 Flash 消息

编辑2:这是提交时的控制台输出(记录已保存并可查看它只是没有客户端)

Started POST "/deals" for ::1 at 2017-04-26 00:13:08 +0200
Processing by DealsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"hbvpS6KsZOorR3u4LgNoG5WHgerok6j3yYzO+dFUHs9thsxRi+rbUkm88nb7A5WvlmWZEcvaDvCKywufP3340w==", "deal"=>{"headline"=>"headline", "client"=>{"name"=>"aaa"}, "matter"=>"", "summary"=>""}, "commit"=>"Add deal"}
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ?  [["id", 1], ["LIMIT", 1]]
Unpermitted parameter: client
   (0.1ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "deals" ("headline", "matter", "summary", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?)  [["headline", "headline"], ["matter", ""], ["summary", ""], ["user_id", 1], ["created_at", 2017-04-25 22:13:08 UTC], ["updated_at", 2017-04-25 22:13:08 UTC]]
   (3.8ms)  commit transaction
   (0.1ms)  begin transaction
   (0.1ms)  commit transaction
Redirected to http://localhost:3000/
Completed 302 Found in 16ms (ActiveRecord: 4.6ms)
Models for reference

用户

class User < ApplicationRecord
  has_many :deals
end

交易

class Deal < ApplicationRecord
  belongs_to :user
  has_many :clients, inverse_of: :deal
  validates :headline, presence: true
  accepts_nested_attributes_for :clients, allow_destroy: true
end

客户

class Client < ApplicationRecord
  belongs_to :deal, inverse_of: :clients
  validates :name, presence: true
  validates :deal_id, presence: true
end

【问题讨论】:

    标签: ruby-on-rails forms nested-forms


    【解决方案1】:

    您缺少参数上的 deal_id

      def deal_params
        params.require(:deal).permit(:headline, :matter, :summary, clients_attributes: [:id, :deal_id, :name, :_destroy])
      end
    

    在创建交易时,你可以做这样的事情

     def create
        @deal = Deal.new(deal_params)
        if @deal.save
        ...
    

    只需在表单上为 user_id 参数添加一个隐藏字段。

    【讨论】:

    • 如果我将 :deal_id 添加到 params 并将 hidden_field 添加到表单中,它确实会显示在哈希中,但没有值 "clients_attributes"=&gt;{"0"=&gt;{"name"=&gt;"aaaa", "deal_id"=&gt;""}}。如果我将 create 方法保留为 @deal = current_user.deals.create(deal_params)
    • 我更改了问题中的代码以反映我尝试的更改
    • 正如我经常发生的那样,再次阅读本文时,我发现您的评论会将 user_id 添加到 HTML,但这已经成功地从关联中构建。这是必需的交易 ID(从同一表单创建)。我再编辑一下,看看能不能说清楚
    • 是的,我知道这部分工作正常,这只是一个建议,以更通用的方式,如果您以后想从不是当前用户的另一个用户创建交易,您不要不需要其他操作,创建将是通用的。在您编辑的 tve 更改 ir tve 控制器中,您更改了新的,您需要更改创建。新的可以和以前一样。并将创建更改为我发送的方式,只是为了尝试,一切似乎都是正确的,我看到的唯一奇怪的事情是客户端参数是单数的,它需要是复数的,这种方式没有被保存,这是一个未经许可的范围。试试这个
    【解决方案2】:

    问题在于客户端模型中的验证。删除验证将允许从嵌套表单中正确保存记录。

    1. 在客户端模型中删除 name 和 deal_id 验证器

      class Client < ApplicationRecord
        belongs_to :deal, inverse_of: :clients
      end
      
    2. 在控制器中将build 添加到new 操作中,这样对用户更友好:

    3. def new
        @deal = Deal.new
        @client = @deal.clients.build
      end
      

    .build 不是绝对必要的,因为从 Rails Cast 创建的辅助方法将根据需要创建新的客户端条目,但用户会看到占位符第一个条目,如果为空白,则将被忽略。

    1. 为了防止保存空客户记录,我在交易模型中添加了 reject_if: proc

      class Deal < ApplicationRecord
        belongs_to :user
        has_many :clients, inverse_of: :deal
        validates :headline, presence: true
        accepts_nested_attributes_for :clients, allow_destroy: true, reject_if: proc { |attributes| attributes['name'].blank? }
      end
      

    如果有人可以更好地解释我的解决方案为何有效以及是否有更好的方法,我将保持开放/未回答。

    【讨论】: