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

Is it possible to cascade active record deletion in batches?

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

我想删除一个父活动记录,它有一对 "child tables" 多行。其中一些子表还有几个子表。为此,我在外键上添加了级联,这样我只需要调用 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...

但如果有一种更隐式的方式允许我只对父级调用删除并批量删除子级和孙级,那将是更可取的,例如

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

我看到 parent 上没有 in_batches,因为 parent 只是一个实体,所以看起来只有像上面第一个例子那样显式地批量删除才有可能吗?

谢谢,

-路易丝

您真的只需要将外键设置为级联,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 查询的内容都会触发该数据库触发器。除非你真的需要评估是否每个 parent 应该在 Rails 中删除你应该能够做到:

Country.where(evil: true).delete_all

这将比 .find_each 更有效,因为您只需要执行一个 SQL 查询。如果您遍历记录,您正在对每行执行一个 DELETE FROM coutries WHERE id = ? 查询,并且由于它的阻塞 Rails 必须等待到数据库的往返。