2

Есть ли простой или, по крайней мере, элегантный способ предотвратить дублирование записей в полиморфных has_many через ассоциации?Рельсы, предотвращающие дубликаты в полиморфных has_many: через ассоциации

У меня есть две модели, истории и ссылки, которые можно пометить. Я принимаю сознательное решение не использовать плагин здесь. Я хочу на самом деле понять все, что происходит, и не зависеть от чужого кода, который я не полностью понимаю.

Чтобы увидеть, что мой вопрос клонит, если я запускаю следующее в консоли (предполагается, что существуют сюжетные и маркировать объекты в уже базе данных)

s = Story.find_by_id(1) 

t = Tag.find_by_id(1) 

s.tags << t 

s.tags << t 

Моих оснащений присоединиться к таблице будет две записей добавлены к нему, каждый с одинаковыми точными данными (tag_id = 1, taggable_id = 1, taggable_type = "Story"). Это мне кажется не очень правильным. Таким образом, в попытке предотвратить это, я добавил следующее к моей мечения модели:

before_validation :validate_uniqueness 

def validate_uniqueness 
    taggings = Tagging.find(:all, :conditions => { :tag_id => self.tag_id, :taggable_id => self.taggable_id, :taggable_type => self.taggable_type }) 

    if !taggings.empty? 
     return false 
    end 

    return true 
end 

И это работает почти как задумано, но если я пытаюсь добавить дубликат тег в истории или ссылке, которую я получаю ActiveRecord :: RecordInvalid: исключение с ошибкой проверки. Кажется, что когда вы добавляете ассоциацию в список, она вызывает сохранение! (а не сохранять sans!) метод, который вызывает исключения, если что-то идет не так, а просто возвращает false. Это не совсем то, что я хочу. Я полагаю, что я могу объединить любые попытки добавить новые теги с помощью try/catch, но это противоречит идее, что вы не должны ожидать своих исключений, и это то, чего я полностью ожидаю.

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

ответ

1

Вы могли бы сделать это несколькими способами.

Определите пользовательский метод add_tags, который загружает все существующие теги, а затем проверяет и только добавляет новые.

Пример:

def add_tags *new_tags 
    new_tags = new_tags.first if tags[0].kind_of? Enumerable #deal with Array as first argument 
    new_tags.delete_if do |new_tag| 
    self.tags.any? {|tag| tag.name == new_tag.name} 
    end 
    self.tags += new_tags 
end 

Вы также можете использовать before_save фильтр, чтобы убедиться, что список тегов не имеет дубликатов. Это повлечет за собой немного больше накладных расходов, потому что это произойдет при КАЖДОМ спасении.

+0

Это в значительной степени то, что я хочу. Однако я не уверен, как вы собираетесь перегружать метод тэгов <<. Я попытался скопировать метод add_tags и просто переименовать его в «теги <<», но он дает мне NoMethodError: undefined method 'tags 'для # всякий раз, когда я пытаюсь его вызвать. Мне бы очень хотелось, чтобы метод << был перегружен, так как кажется более естественным использовать это. – seaneshbaugh

+0

О да .. Я забыл, вам нужно определить метод меток, который возвращает объект с методом '<<'. НО, self.tags << new_tags не будет работать, так как это просто попытается добавить массив тегов в качестве элемента в коллекции тегов. Вы бы хотели использовать 'self.tags + = new_tags' для этой строки. Ответ обновлен. –

1

Вы можете установить параметр uniq при определении отношения has_many. Rails API Docs говорит:

:uniq

If true, duplicates will be omitted from the collection. Useful in conjunction with :through.

(взято из: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#M001833 в разделе "Поддерживаемые опции" подзаголовок)

+0

Опция uniq фактически не предотвращает внесение каких-либо вещей в базу данных. Фактически, основываясь на моих тестах, похоже, что он вступает в игру только тогда, когда создается экземпляр объекта, который, по-видимому, вызывает uniq! в списке ассоциаций объектов, который просто удаляет их в памяти в этот момент. Если добавляются дублирующие ассоциации, они все равно отображаются как в списке объектов, так и в базе данных. Это прекрасный пример того, почему документация на рейлы ужасна и нуждается в уточнении и/или пересмотре. – seaneshbaugh

+3

': uniq' не имеет ничего общего с предотвращением дублирования записей в базе данных ... –

0

Я считаю, что это работает ...

class Tagging < ActiveRecord::Base 
    validate :validate_uniqueness 

    def validate_uniqueness 
     taggings = Tagging.find(:all, :conditions => { 
     :tag_id => self.tag_id, 
     :taggable_id => self.taggable_id, 
     :taggable_type => self.taggable_type }) 

     errors.add_to_base("Your error message") unless taggings.empty? 
    end 
end 

Позвольте мне знать, если вы получаете какие-либо ошибки или что-то с этим:]