Rails:销毁对象时按条件删除has_many关联对象问题
我来帮你捋捋这个问题哈~你现在的情况是:销毁@foo前想删掉所有type为test的关联bar,但加了.where(type: "test")就删不动,不加条件却能全删,大概率是这几个原因导致的:
1. 单表继承(STI)的type值写错了!
如果你的Bar模型用了单表继承(比如有个TestBar子类继承自Bar),那数据库里type字段存的是类名,比如"TestBar",不是小写的"test"。这时候你的条件根本匹配不到任何记录,自然删不掉。
解决办法:把条件改成对应类名的字符串就行:
@foo.bars.where(type: "TestBar").destroy_all
2. 先遍历更新拖了后腿
你先执行了@foo.bars.each{|b| b.update_attribute(:blub_id, nil)},这会把所有bar都加载到内存里。虽然之后的.where会重新发SQL查询,但这种先加载全量数据的操作不仅低效,还可能因为内存集合和数据库数据的差异导致查询异常。
优化一下:用批量更新代替遍历,既高效又不会提前加载全量数据:
# 批量更新符合条件的bar的blub_id为nil @foo.bars.where(type: "test").update_all(blub_id: nil) # 再销毁这些bar @foo.bars.where(type: "test").destroy_all
3. 关联的dependent设置可能在捣乱
检查下Foo模型里的关联定义,如果写了has_many :bars, dependent: :destroy,那调用@foo.destroy时Rails会自动删掉所有关联的bar。这时候你手动删部分bar的操作可能被后续的自动销毁覆盖,但你说不加条件能全删,可能没设置这个?不过还是确认下比较好:
# Foo模型里的关联,如果有dependent: :destroy会自动删所有bars has_many :bars, dependent: :destroy
如果确实有这个设置,而你只想删部分bar,要么把dependent改成:nullify(只把外键设为null,不销毁),要么就去掉这个设置,手动处理销毁逻辑。
最终修正后的代码
结合上面的点,修正后的destroy方法可以写成这样:
def destroy # 先批量更新符合条件的bar的blub_id(注意type值要和数据库存的一致) @foo.bars.where(type: "TestBar").update_all(blub_id: nil) # 销毁符合条件的bar @foo.bars.where(type: "TestBar").destroy_all # 销毁foo @foo.destroy respond_to do |format| format.html { redirect_to foo_url, notice: 'Foo was successfully destroyed.' } format.json { head :no_content } end end
要是想更符合Rails的约定,还可以把更新blub_id的逻辑放到Bar模型的回调里:
# Bar模型中添加回调 class Bar < ApplicationRecord before_destroy :nullify_blub_id private def nullify_blub_id update_attribute(:blub_id, nil) if type == "TestBar" end end
这样你的destroy方法就能简化成:
def destroy @foo.bars.where(type: "TestBar").destroy_all @foo.destroy respond_to do |format| format.html { redirect_to foo_url, notice: 'Foo was successfully destroyed.' } format.json { head :no_content } end end
内容的提问来源于stack exchange,提问作者larz

