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

时间:2008-11-24 22:56:26

标签: ruby-on-rails ruby duplicates has-many-through has-many

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

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

class Reader < ActiveRecord::Base
  has_many :blogs_readers, :dependent => :destroy
  has_many :blogs, :through => :blogs_readers
end

class BlogsReaders < ActiveRecord::Base
  belongs_to :blog
  belongs_to :reader
end

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

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

8 个答案:

答案 0 :(得分:77)

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

请注意将:uniq => true选项添加到has_many来电。

此外,您可能需要考虑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的更新

在范围块中使用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

答案 1 :(得分:36)

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

class BlogsReaders < ActiveRecord::Base
  belongs_to :blog
  belongs_to :reader

  validates_uniqueness_of :reader_id, :scope => :blog_id
end

答案 2 :(得分:17)

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

答案 3 :(得分:5)

怎么样:

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

Rails通过关联方法为我们收集id! :)

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

答案 4 :(得分:2)

此链接的答案显示了如何覆盖“&lt;&lt;”在不引发异常或创建单独方法的情况下实现您所需要的方法:Rails idiom to avoid duplicates in has_many :through

答案 5 :(得分:1)

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

the_reader = Reader.find(:first, :include => :blogs)

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

[编辑]

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

答案 6 :(得分:1)

目前最常见的答案是在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

答案 7 :(得分:-1)

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

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]

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