如何避免has_many:through关系中的重复?

我怎样才能实现以下目标? 我有两个模型(博客和读者)和一个JOIN表,它允许我在它们之间建立N:M关系:

class Blog  :destroy has_many :readers, :through => :blogs_readers end class Reader  :destroy has_many :blogs, :through => :blogs_readers end class BlogsReaders < ActiveRecord::Base belongs_to :blog belongs_to :reader end 

我现在想做的是将读者添加到不同的博客中。 但是,条件是我只能将博客添加到博客中。 因此, BlogsReaders表中不得有任何重复项(相同的readerID ,相同的blogID )。 我怎样才能做到这一点?

第二个问题是,如何获得读者尚未订阅的博客列表(例如,填写下拉选择列表,然后可以将读者添加到另一个博客)?

关于什么:

 Blog.find(:all, :conditions => ['id NOT IN (?)', the_reader.blog_ids]) 

Rails通过关联方法为我们收集ids! 🙂

http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

Rails中内置的更简单的解决方案:

  class Blog < ActiveRecord::Base has_many :blogs_readers, :dependent => :destroy has_many :readers, :through => :blogs_readers, :uniq => true end class Reader < ActiveRecord::Base has_many :blogs_readers, :dependent => :destroy has_many :blogs, :through => :blogs_readers, :uniq => true end class BlogsReaders < ActiveRecord::Base belongs_to :blog belongs_to :reader end 

注意在has_many调用中添加:uniq => true选项。

此外,您可能需要考虑Blog和Reader之间的has_and_belongs_to_many ,除非您在连接模型上有一些其他属性(当前没有)。 该方法还有一个:uniq opiton。

请注意,这不会阻止您在表中创建条目,但它确实在您查询集合时只能获得每个对象中的一个。

更新

在Rails 4中,实现它的方法是通过范围块。 以上更改为。

 class Blog < ActiveRecord::Base has_many :blogs_readers, dependent: :destroy has_many :readers, -> { uniq }, through: :blogs_readers end class Reader < ActiveRecord::Base has_many :blogs_readers, dependent: :destroy has_many :blogs, -> { uniq }, through: :blogs_readers end class BlogsReaders < ActiveRecord::Base belongs_to :blog belongs_to :reader end 

Rails更新5

在scope块中使用uniq将导致错误NoMethodError: undefined method 'extensions' for []:Array 。 使用distinct代替:

 class Blog < ActiveRecord::Base has_many :blogs_readers, dependent: :destroy has_many :readers, -> { distinct }, through: :blogs_readers end class Reader < ActiveRecord::Base has_many :blogs_readers, dependent: :destroy has_many :blogs, -> { distinct }, through: :blogs_readers end class BlogsReaders < ActiveRecord::Base belongs_to :blog belongs_to :reader end 

这应该照顾你的第一个问题:

 class BlogsReaders < ActiveRecord::Base belongs_to :blog belongs_to :reader validates_uniqueness_of :reader_id, :scope => :blog_id end 

Rails 5.1方式

 class Blog < ActiveRecord::Base has_many :blogs_readers, dependent: :destroy has_many :readers, -> { distinct }, through: :blogs_readers end class Reader < ActiveRecord::Base has_many :blogs_readers, dependent: :destroy has_many :blogs, -> { distinct }, through: :blogs_readers end class BlogsReaders < ActiveRecord::Base belongs_to :blog belongs_to :reader end 

此链接的答案显示如何覆盖“<<”方法以实现您正在寻找的内容,而不会引发异常或创建单独的方法: Rails成语以避免has_many中的重复:通过

我想有人会得到比这更好的答案。

 the_reader = Reader.find(:first, :include => :blogs) Blog.find(:all, :conditions => ['id NOT IN (?)', the_reader.blogs.map(&:id)]) 

[编辑]

请参阅下面的Josh的回答。 这是要走的路。 (我知道有更好的方法;)

目前最热门的答案是在proc中使用uniq

 class Blog < ActiveRecord::Base has_many :blogs_readers, dependent: :destroy has_many :readers, -> { uniq }, through: :blogs_readers end 

然而,这会将关系踢入数组并且可以破坏期望对关系执行操作的事物,而不是数组。

如果你使用distinct它会将它保持为关系:

 class Blog < ActiveRecord::Base has_many :blogs_readers, dependent: :destroy has_many :readers, -> { distinct }, through: :blogs_readers end 

最简单的方法是将关系序列化为数组:

 class Blog < ActiveRecord::Base has_many :blogs_readers, :dependent => :destroy has_many :readers, :through => :blogs_readers serialize :reader_ids, Array end 

然后在为读者分配值时,将其应用为

 blog.reader_ids = [1,2,3,4] 

以这种方式分配关系时,会自动删除重复项。

Interesting Posts