Можно ли каскадно удалять активные записи партиями? - PullRequest
0 голосов
/ 03 февраля 2020

У меня много данных, которые я хочу удалить, и по этой причине я использую delete / delete_all вместо уничтожения через каскад на внешних ключах (on_delete: :cascade).

Я хочу удалить один родительская активная запись, которая имеет несколько «дочерних таблиц» со многими строками. Некоторые из этих дочерних таблиц также имеют пару дочерних таблиц. По этой причине я добавил каскад для внешних ключей, так что мне нужно только вызвать 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

Я вижу, что на родительском элементе нет in_batches, поскольку родительским объектом является только одна сущность, поэтому похоже, что это возможно только в том случае, если я явно удаляю пакетами, как в первом примере выше?

Спасибо,

- Луиза

1 Ответ

1 голос
/ 03 февраля 2020

Вам просто нужно настроить внешние ключи для каскадирования, а 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

Передача spe c:

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 должен ждать обратного пути к БД.

...