Ассоциация ActiveRecord сохраняется перед записью в обновлении - PullRequest
0 голосов
/ 15 июля 2009

У меня есть модель Entry, у которой много Tag с. Tag s добавляются к записи, вводя их в текстовое поле в моей форме через виртуальный атрибут tag_names. Перед проверкой на модели Entry строка tag_names преобразуется в фактические объекты Tag с использованием find_or_create_by_name. Модель Tag также имеет проверку, чтобы убедиться, что имя тега соответствует регулярному выражению, которое запускается через ассоциацию.

Моя модель Entry выглядит так:

class Entry < ActiveRecord::Base
  has_many :entry_tags
  has_many :tags, :through => :entry_tags

  before_validation :update_tags

  attr_writer :tag_names

private
  def update_tags
    if @tag_names
      self.tags = @tag_names.split(",").uniq.map do |name|
        Tag.find_or_create_by_name(name.strip)
      end
    end
  end
end

Когда я создаю новый объект Entry и присваиваю ему теги, все работает правильно - теги не сохраняются, если на одном из Tag s произошла ошибка проверки и было возвращено сообщение об ошибке. Однако, если я пытаюсь обновить существующий объект Entry с недействительным тегом, вместо того, чтобы возвращать сообщение, мой вызов self.tags=update_tags выше) вызывает исключение с сообщением об ошибке проверки. Даже если я перезапишу find_or_create_by_name, чтобы просто вернуть новый объект вместо вызова create, я получу тот же результат.

Мне кажется (и документы , кажется, подтверждают), что вызов tags= фактически сохраняет мои Tag объекты до того, как основная запись будет сохранена, когда объект Entry уже существует. Могу ли я что-нибудь сделать, чтобы это сохранение не произошло, или чтобы оно не вызывало исключение и просто заставляло мое сохранение возвращать false?

Ответы [ 2 ]

2 голосов
/ 15 июля 2009

Я бы попробовал что-то вроде этого:

class Entry < ActiveRecord::Base
  has_many :entry_tags
  has_many :tags, :through => :entry_tags

  before_validation :update_tags

  attr_writer :tag_names
  validates_associated :tags

private
  def update_tags
    return unless @tag_names
    current_tag_names = tags.map(&:name)
    user_tag_names = @tag_names.split(",").uniq
    #add in new tags
    user_tag_names.each do |name|
      next if current_tag_names.include?(name)
      tags.build :name => name
    end
    #remove dropped tags
    ( current_tag_names - user_tag_names ).each do |name|
      removed_tag = tags.find_by_name(name)
      tags.delete(removed_tag)
    end
  end
end

Таким образом, вы только инициализируете связанные модели в своем действии update_tags и не будете выдавать ошибки проверки. Я также добавил в validates_associated :tags, чтобы об ошибках в этих связанных моделях можно было сообщать через стандартную форму ввода, используя error_messages_for :entry.

Обновление включает код для удаления удаленных тегов.

1 голос
/ 15 июля 2009

Вы можете перехватить возникшее исключение и вернуть false, в этом случае, из update_tags, что остановит сохранение на Entry.

В качестве альтернативы, если вы хотите избежать обработки этого исключения, вы можете создать новый экземпляр Tag, где он еще не существует, и проверить, является ли он действительным, прежде чем продолжить (new_tag.valid?), а если нет, вернуть false из update_tags.

...