ActiveRecord: избежать несогласованности в отношениях has_many - PullRequest
1 голос
/ 23 июня 2009

Предположим, у нас обычные отношения M-M между двумя таблицами, например:

пользователи --- <<strong> users_tags > --- * теги .

В этом посте меня интересует только отношение user_tags, теги: я бы хотел, чтобы связанные теги можно было удалить. Только теги, на которые нет ссылок, могут быть уничтожены.

Глупый способ сделать это будет:

class Tag
  def before_destroy  
    unless self.user_tags.empty?
      raise "error"
    end
  end
end

Но я думаю, что есть потенциальное состояние гонки между проверкой user_tags.empty? и фактическое удаление.

Второй подход может заключаться в блокировке всей таблицы user_tags перед проверкой, остались ли какие-либо ссылки.

И третий способ, который я могу придумать, связан с изменениями в коде, который создает фактическую ссылку:

Добавить ссылку на users_tags:

  1. Получить тег
  2. Блокировка (чтобы избежать одновременного уничтожения)
  3. Создать ссылку в users_tag
  4. Commit

Тогда обработчик before_destroy может:

  1. self.lock!
  2. Проверьте, есть ли ссылки
  3. уничтожить себя
  4. Commit

Есть ли лучшие способы сделать это? Какой из них надежный / лучший? Лично я склоняюсь ко второму, так как для него нужна только логика в контроллере before_destroy, но с затратами на блокировку всей таблицы.

Редактировать 1:

Экспериментируя с LOCK TABLE Я понял, что они играют против моих транзакций. При использовании innodb вы можете либо использовать транзакции (и их функции блокировки), либо использовать таблицу LOCK / UNLOCK, сочетание обоих миров делает ситуацию намного хуже (LOCK / UNLOCK вызывает неявные фиксации, я пропустил это предупреждение в документе). Но это только для протокола.

( Редактировать 2 (несколько недель спустя): Я снова решил эту проблему. Поэтому хочу еще раз подчеркнуть Не использовать LOCK TABLE )

Сейчас я стремлюсь использовать SHARE LOCK на родительском объекте (тег в примере)) при добавлении потомков и блокировку FOR UPDATE для удалений. Но мне все еще интересно, так ли это должно быть (заблокируйте Rang в дочерней таблице для обновления в родительской таблице).

Btw. Я также понимаю, что этот вопрос теперь полностью независим от рельсов:).

1 Ответ

3 голосов
/ 23 июня 2009

Один из способов избежать блокировки и проверки - просто создать внешние ключи. Попытка удалить что-то, на что есть ссылка в другой таблице, приведет к ошибке SQL.

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

Альтернатива - подойти к проблеме под другим углом. Например, в качестве одной транзакции стираются все неиспользуемые теги. Например:

DELETE FROM tags WHERE id NOT IN (SELECT DISTINCT(tag_id) FROM users_tags)

Это имеет недостаток в том, что не выполняет поведения типа before_destroy на уровне модели, но это может быть проблемой для вас.

...