Rails 7 Hotwire动态表单无法更新值,新增重复记录求助
问题描述
使用Hotwire遵循GoRails动态嵌套表单教程开发,ProgramPart与RichContent为has_many/belongs_to关联,已配置accepts_nested_attributes_for。编辑ProgramPart时,更新已有RichContent的值后提交表单,旧记录会保留,同时新增一条包含更新后值的新记录,而非直接更新原记录。提交参数显示rich_contents_attributes的键是一个非现有记录ID的数值。
相关代码
模型代码
class ProgramPart < ApplicationRecord has_many :rich_contents, dependent: :destroy accepts_nested_attributes_for :rich_contents, reject_if: :all_blank, allow_destroy: true end class RichContent < ApplicationRecord belongs_to :program_part has_rich_text :content end
表单相关代码
<div id="block_elements" class="col-span-6 space-y-6"> <% @program_part.rich_contents.each do |content| %> <%= render(RichContent::RichContentFormComponent.new(rich_content: content)) %> <% end %> </div> <div class="col-span-6"> <span> <%= link_to "Text and media", new_company_program_program_part_rich_content_path(@company, @program, @program_part.persisted? ? @program_part.id : Time.now.to_i), data: { turbo_stream: true } %> </span> </div> <div class="bg-gray-50"> <%= part_form.submit class: "#{submit_classes}" %> </div>
Turbo Stream文件
<%= turbo_stream.append "block_elements" do %> <%= render(RichContent::RichContentFormComponent.new(rich_content: @rich_content)) %> <% end %> # 控制器中 @rich_content = RichContent.new
ViewComponent代码
<%= fields_for "program_part[rich_contents_attributes][#{@rich_content.persisted? ? @rich_content.id : Time.now.to_i }]", @rich_content do |rich_content_form| %> <%= render(Forms::InputLabelComponent.new(label: "Text and media", form_builder: rich_content_form, model_attribute: :content)) do %> <%= rich_content_form.rich_text_area :content %> <% end %> <% end %>
提交参数示例
Processing by ProgramPartsController#update as TURBO_STREAM 14:28:07 web.1 | Parameters: {"authenticity_token"=>"[FILTERED]", "program_part"=>{"title"=>"Test", "description"=>"", "rich_contents_attributes"=>{"152"=>{"content"=>"<div>2</div>"}}}, "commit"=>"Update Program part", "company_id"=>"1", "program_id"=>"9", "id"=>"27"}
问题原因
核心问题出在ViewComponent的fields_for手动构造逻辑上:
- 对于已持久化的RichContent记录,代码用
Time.now.to_i生成的临时值作为rich_contents_attributes的键,且没有在嵌套字段中传入记录的id。 - Rails的
accepts_nested_attributes_for识别更新操作的关键是:嵌套属性中必须包含对应子记录的id,这样Rails才能匹配到要更新的旧记录。 - 提交参数里的
"152"是临时生成的时间戳,和现有RichContent的ID完全不匹配,且参数中没有id字段,所以Rails会将这个请求判定为创建新记录,而非更新原有记录。
修复方案
方案1:使用父表单构建器生成嵌套字段(推荐)
不要手动构造字段名,借助父表单的part_form来生成嵌套字段,Rails会自动处理ID和属性键的匹配:
- 修改父表单的渲染逻辑,传递表单构建器给组件:
<div id="block_elements" class="col-span-6 space-y-6"> <%= part_form.fields_for :rich_contents do |rich_content_form| %> <%= render(RichContent::RichContentFormComponent.new(form: rich_content_form)) %> <% end %> </div>
- 更新ViewComponent,直接使用传入的表单构建器:
<%= render(Forms::InputLabelComponent.new(label: "Text and media", form_builder: @form, model_attribute: :content)) do %> <%= @form.rich_text_area :content %> <% end %>
方案2:手动添加ID字段(不推荐,易出错)
如果一定要手动构造字段名,必须在嵌套属性中添加隐藏的id字段,让Rails识别出要更新的记录:
<%= fields_for "program_part[rich_contents_attributes][#{@rich_content.persisted? ? @rich_content.id : Time.now.to_i }]", @rich_content do |rich_content_form| %> <% if @rich_content.persisted? %> <%= rich_content_form.hidden_field :id %> <% end %> <%= render(Forms::InputLabelComponent.new(label: "Text and media", form_builder: rich_content_form, model_attribute: :content)) do %> <%= rich_content_form.rich_text_area :content %> <% end %> <% end %>
新增记录的Turbo Stream适配
对于通过Turbo Stream新增的RichContent,控制器创建空记录后,同样建议用父表单构建器的fields_for来渲染组件,确保字段符合Rails嵌套属性的规范。
内容的提问来源于stack exchange,提问作者Anders
相关产品推荐
相关产品推荐

