2

是否有一种简单或至少优雅的方式来防止多态has_many通过关联重复项?Rails预防重复多态has_many:通过关联

我有两个模型,故事和可以标记的链接。我正在有意识地决定不要在这里使用插件。我想真正理解所发生的一切,而不是依赖于别人的代码,我没有完全掌握。

看看我的问题是在得到,如果我跑在控制台下(假设的故事和标签对象已经存在于数据库中)

s = Story.find_by_id(1) 

t = Tag.find_by_id(1) 

s.tags << t 

s.tags << t 

我引用的Tagging加入将增加表中的两个条目到它,每个具有相同的确切数据(tag_id = 1,taggable_id = 1,taggable_type =“故事”)。这对我来说似乎不太合适。因此,在试图防止这种情况发生,我添加以下到我的标签模型:

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:验证失败的异常。看起来,当你添加一个关联到列表时,它会调用save! (而不是保存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方法,并将其重命名为“tags <<”,但每当我尝试调用它时,它都会给我带来NoMethodError:未定义的方法'标签',用于#。我真的很喜欢<<方法被重载,因为使用它似乎更自然。 – seaneshbaugh 2010-05-22 20:26:11

+0

噢,我忘了,你必须定义一个标签方法,它返回一个具有'<<'方法的对象。但是,self.tags << new_tags无论如何都不起作用,因为它只会尝试在标签集合中添加一组标签作为元素。你会想为该行使用'self.tags + = new_tags'。答案已更新。 – 2010-05-23 07:10:23

1

定义has_many关系时,可以设置uniq选项。 Rails的API文档说:

: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!在该对象的关联列表上,该关联列表在那一刻只是在内存中删除它们。如果添加了任何重复的关联,它们仍然显示在对象的列表和数据库中。这是一个很好的例子,说明为什么rails文档很糟糕,并且急需澄清和/或修订。 – seaneshbaugh 2010-05-22 12:43:11

+3

':uniq'与防止数据库中的重复项无关... – 2010-05-22 14:47:36

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 

让你得到任何错误,我知道或者与之相关的东西]: