Избавление от N + 1 запроса при уничтожении связанных объектов - PullRequest
0 голосов
/ 16 мая 2018

Я создаю приложение для блога, я пытаюсь написать метод destroy в Users Controller для обработки ситуации, когда User уничтожается. Я хочу, чтобы он запускал действие по удалению всех связанных объектов, но когда я указываю в моделях «зависимый:: уничтожить», я получаю N + 1 запрос (или N * M, если это возможно ...) - каждая отдельная статья удаляется один за другим, затем все комментарии, все теги, все теги (таблица для обработки отношений HABTM).

Я пытался написать служебный объект, чтобы избавиться от этого длинного запроса, но я не могу заставить его работать, потому что я "нарушаю ограничение внешнего ключа". Каковы возможности здесь? Я не хочу позволить nils в моей БД просто иметь возможность легко очищать БД, выполняя запрос, который ищет nils в БД (чтобы избежать странного ввода пользователями), поэтому зависимость:: nullify здесь не будет работать .. Также я бы предпочел не использовать Callbacks, если это возможно.

Что также беспокоит меня при выборе решения со Службами, когда я сначала жду удаления Пользователя, а затем вызываю Службу для очистки Orhpans ... но когда код выполняется неправильно, я получаю нулевое значение - следует ли использовать транзакции на этом?

Пользователь может быть автором статьи, которая отмечена тегом, его могут комментировать другие пользователи.

Вот основные классы:

Пользователь:

class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
        :recoverable, :rememberable, :trackable, :validatable

  has_many :articles, foreign_key: 'author_id'
  has_many :comments, foreign_key: 'author_id'
  validates :email, presence: true
end

Статья:

class Article < ApplicationRecord
  include ActiveModel::Validations
  belongs_to :author, class_name: 'User'
  has_many :taggings
  has_many :tags, through: :taggings
  has_many :comments
end

Комментарий:

class Comment < ApplicationRecord
  belongs_to :author, class_name: 'User'
  belongs_to :article
  belongs_to :parent, class_name: 'Comment', optional: true
  has_many :children, class_name: 'Comment', foreign_key: 'parent_id', dependent: :delete_all
  acts_as_tree order: 'created_at ASC'
end

Tag:

class Tag < ApplicationRecord
  has_many :taggings
  has_many :articles, through: :taggings
end

Пометка:

class Tagging < ApplicationRecord
  belongs_to :tag
  belongs_to :article
end

1 Ответ

0 голосов
/ 16 мая 2018

Это ожидаемое поведение. У вас есть несколько вариантов:

1) Вы можете создать сервисный объект, который выполняет все удаление вручную посредством запросов, начиная с ваших самых отдаленных отношений. например:

user = User.find(id_of_user_to_destroy)

user.articles.each do |article|
  article = Article.find(id_of_article_to_destroy)
  Tag.where(articles: [article]).delete_all #delete_all skips callbacks
  Tagging.where(article: article).delete_all
  Comment.where(...).delete_all
  article.destroy
end

user.destroy

Обычно это больше производительности, но сложнее поддерживать.

2) Пометить пользователя как «мусорную корзину» (логическое поле), а затем вызвать User.destroy в фоновом режиме. У вас по-прежнему будет большое количество запросов к базе данных, однако его будет проще поддерживать с помощью собственных атрибутов AR dependency.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...