如何避免提交经jQuery remove()删除字段的参数?
解决AJAX移除嵌套属性字段后提交触发RecordNotFound错误的问题
我明白你遇到的困扰了:用AJAX删除嵌套的song字段后,DOM元素已经消失,数据库里的记录也删掉了,但提交表单时还是会把已删除的song参数发过去,导致Rails找不到对应的song而抛出错误。这是因为表单里其实还残留着该song的相关字段(比如隐藏的id输入框),或者你提前删除了数据库记录,但表单提交时Rails仍尝试更新这条已不存在的记录。
下面给你几个可行的解决方案,按推荐程度排序:
方案一:利用Rails嵌套属性的_destroy机制(最优雅)
Rails的嵌套属性本来就支持通过_destroy参数标记删除关联记录,不需要提前AJAX删除数据库,推荐用这种方式:
- 配置模型允许销毁:
在Artist模型里添加allow_destroy: true,开启嵌套属性的销毁功能:
class Artist < ApplicationRecord has_many :songs, dependent: :destroy accepts_nested_attributes_for :songs, allow_destroy: true end
- 修改删除链接和JS逻辑:
把原来的删除链接改成不直接请求后端,而是通过JS标记该song为待删除:
<%= form_for @artist do |f| %> <%= f.label :name %> <%= f.text_field :name %><br/> <%= f.label :style %> <%= f.text_field :style %><br/> <%= f.fields_for :songs do |song_builder|%> <div id='song_<%= song_builder.object.id %>_div' data-index='<%= song_builder.index %>'> <%= song_builder.label :title %> <%= song_builder.text_field :title %><br/> <%= song_builder.label :lyrics %> <%= song_builder.text_area :lyrics %><br/> <%= link_to 'Remove song', '#', class: 'remove-song', data: { song_id: song_builder.object.id } %> </div> <% end %> <%= f.submit 'Save' %> <% end %>
添加JS处理点击事件(可以放在app/assets/javascripts/artists.js里):
$(document).on('click', '.remove-song', function(e) { e.preventDefault(); const songDiv = $(this).closest('[data-index]'); const index = songDiv.data('index'); // 添加隐藏的_destroy字段,标记该song需要被删除 $('<input>', { type: 'hidden', name: `artist[songs_attributes][${index}][_destroy]`, value: '1' }).appendTo(songDiv); // 隐藏该song的区域(也可以直接remove,不过hide方便撤销操作) songDiv.hide(); });
这样,当你点击删除按钮时,只是在表单里添加了一个标记字段,提交表单时Rails会自动删除所有标记了_destroy: 1的song,不会出现找不到记录的问题,也不需要额外的AJAX请求。
方案二:提前AJAX删除后,彻底移除表单字段
如果你坚持要提前AJAX删除数据库记录,那需要确保删除DOM的同时,把对应的表单字段也彻底移除:
- 修正controller里的拼写错误:
你的delete_songaction里有几处拼写错误,先修正:
def delete_song @song_id = params[:s_id] # 这里要加冒号获取参数 artist = Artist.find(params[:a_id]) # 修正aritst拼写错误,参数也要加冒号 song = artist.songs.find(@song_id) song.delete respond_to do |format| format.js {render 'delete_song.js.erb'} end end
- 更新delete_song.js.erb:
除了移除显示的div,还要找到所有和该song相关的表单字段并删除:
// 移除显示的song区域 $('#song_<%= @song_id %>_div').remove(); // 移除所有对应song id的隐藏字段(比如id输入框) $('input[name*="songs_attributes"][value="<%= @song_id %>"]').remove();
这样就能确保表单提交时不会携带已删除song的参数。
方案三:在update action里过滤无效的song id
如果上面两种方式都不想改,可以在参数过滤的时候,自动去掉已不存在的song记录:
def artist_params params.require(:artist).permit(:name, :style, songs_attributes: [:id, :title, :lyrics]).tap do |permitted| # 过滤掉已不存在的song id if permitted[:songs_attributes].present? permitted[:songs_attributes].keep_if do |song_attr| # 新的song(没有id)保留,已存在的song要确认数据库里还有 song_attr[:id].blank? || Song.exists?(song_attr[:id]) end end end end
这样即使表单里带了已删除的song id,也会被过滤掉,不会触发查找错误。
内容的提问来源于stack exchange,提问作者youssef
相关产品推荐
相关产品推荐

