是否可以级联批量删除活动记录?

问题描述 投票:0回答:1

我有很多要删除的数据,因此,我通过级联外键(on_delete: :cascade)使用delete / delete_all而不是destroy。

我想删除一个父活动记录,该记录有几个带有许多行的“子表”。这些子表中的一些也有几个子表。因此,我在外键上添加了层叠,因此我只需要调用parent.delete即可触发删除所有父级的所有子级和大级子级。

我想将delete / delete_all与活动记录批处理https://api.rubyonrails.org/classes/ActiveRecord/Batches.html结合起来,但是由于只有一个单亲,我不确定如何以一种简洁的方式结合批处理和级联删除。

一种选择是显式批量删除子代和大子代,例如

parent.children_x.find_each do |child_x|
  child_x.grand_children_y.in_batches do |grand_child_y_batch|
    grand_child_y_batch.delete_all        
  end
  child_x.delete        
end
parent.children_z.in_batches do |child_batch|
  child_batch.delete_all
end
...etc...

但是,如果有一种更隐式的方式允许我仅在父级上调用delete并批量删除子级和大级子级,那是可取的,例如

parent.cascade_in_batches do |parent_batch|
  parent_batch.delete_all #This batch deletes all children and grand children
end

[我看到父级上没有in_batches,因为父级只是一个实体,所以看起来只有如我在上面的第一个示例中那样显式删除时才有可能吗?

谢谢,

-路易丝

ruby-on-rails rails-activerecord cascade
1个回答
1
投票

您实际上只需要设置外键进行级联,Postgres将负责删除所有行。由于这是在数据库层上实现的,因此无论如何触发从Rails删除都无关紧要。

class CreateCountries < ActiveRecord::Migration[6.0]
  def change
    create_table :countries do |t|
      t.string :name
      t.timestamps
    end
  end
end

class CreateStates < ActiveRecord::Migration[6.0]
  def change
    create_table :states do |t|
      t.string :name
      t.belongs_to :country, null: false, foreign_key: {on_delete: :cascade}
      t.timestamps
    end
  end
end

class CreateCities < ActiveRecord::Migration[6.0]
  def change
    create_table :cities do |t|
      t.string :name
      t.belongs_to :state, null: false, foreign_key: {on_delete: :cascade}
      t.timestamps
    end
  end
end

型号:

class Country < ApplicationRecord
  has_many :states
  has_many :cities, through: :states
end

class State < ApplicationRecord
  belongs_to :country
  has_many :cities
end

class City < ApplicationRecord
  belongs_to :state
  has_one :country, through: :state
end

通过规格:

require 'rails_helper'

RSpec.describe Country, type: :model do
  describe "cascading delete" do
    let!(:country){ Country.create }
    let!(:state){ country.states.create }
    let!(:city){ state.cities.create }

    it "deletes the states" do
      expect {
        country.delete
      }.to change(State, :count).from(1).to(0)
    end

    it "deletes the cities" do
      expect {
        Country.delete_all
      }.to change(City, :count).from(1).to(0)
    end
  end
end

如果您未使用.each_with_batches,则此处与此处无关。任何创建DELETE FROM countries查询的操作都会触发该数据库触发器。除非您真的需要评估是否应该在Rails中删除每个父级,否则您应该能够这样做:

Country.where(evil: true).delete_all

这将比.find_each效率高得多,因为您只在执行一个SQL查询。如果您遍历记录,则每行要进行一次DELETE FROM coutries WHERE id = ?查询,并且由于其阻塞,Rails必须等待往返数据库的时间。

© www.soinside.com 2019 - 2024. All rights reserved.