Ruby-on-rails 如何避免 has_many :through 关系中的重复?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/315792/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 20:48:00  来源:igfitidea点击:

how to avoid duplicates in a has_many :through relationship?

ruby-on-railsrubyduplicateshas-many-throughhas-many

提问by Sebastian

How can I achieve the following? I have two models (blogs and readers) and a JOIN table that will allow me to have an N:M relationship between them:

我怎样才能实现以下目标?我有两个模型(博客和读者)和一个 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

What I want to do now, is add readers to different blogs. The condition, though, is that I can only add a reader to a blog ONCE. So there mustn't be any duplicates (same readerID, same blogID) in the BlogsReaderstable. How can I achieve this?

我现在想做的是将读者添加到不同的博客。但是,条件是我只能将读者添加到博客一次。所以表中不能有任何重复(相同readerID,相同blogIDBlogsReaders。我怎样才能做到这一点?

The second question is, how do I get a list of blog that the readers isn't subscribed to already (e.g. to fill a drop-down select list, which can then be used to add the reader to another blog)?

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

采纳答案by Josh Delsman

What about:

关于什么:

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

Rails takes care of the collection of ids for us with association methods! :)

Rails 使用关联方法为我们处理 id 的收集!:)

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

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

回答by Otto

Simpler solution that's built into Rails:

内置于 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

Note adding the :uniq => trueoption to the has_manycall.

请注意将:uniq => true选项添加到has_many呼叫中。

Also you might want to consider has_and_belongs_to_manybetween Blog and Reader, unless you have some other attributes you'd like to have on the join model (which you don't, currently). That method also has a :uniqopiton.

此外,您可能还想has_and_belongs_to_many在博客和阅读器之间考虑,除非您希望在连接模型上拥有一些其他属性(目前您没有)。该方法也有一个:uniq选项。

Note that this doesn't prevent you from creating the entries in the table, but it does ensure that when you query the collection you get only one of each object.

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

Update

更新

In Rails 4 the way to do it is via a scope block. The Above changes to.

在 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

Update for Rails 5

Rails 5 的更新

The use of uniqin the scope block will cause an error NoMethodError: undefined method 'extensions' for []:Array. Use distinctinstead :

使用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

回答by Mike Breen

This should take care of your first question:

这应该解决你的第一个问题:

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

  validates_uniqueness_of :reader_id, :scope => :blog_id
end

回答by pastullo

The Rails 5.1 way

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

回答by Hollownest

The answer at this link shows how to override the "<<" method to achieve what you are looking for without raising exceptions or creating a separate method: Rails idiom to avoid duplicates in has_many :through

此链接上的答案显示了如何覆盖“<<”方法以实现您正在寻找的内容,而不会引发异常或创建单独的方法:Rails idiom to avoid duplicates in has_many :through

回答by JD Isaacks

The top answer currently says to use uniqin the proc:

目前最重要的答案uniq是在 proc 中使用:

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

This however kicks the relation into an array and can break things that are expecting to perform operations on a relation, not an array.

然而,这会将关系踢到一个数组中,并可能破坏期望对关系而不是数组执行操作的事情。

If you use distinctit keeps it as a relation:

如果你使用distinct它保持它作为一个关系:

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

回答by Mike Breen

I'm thinking someone will come along with a better answer than this.

我想有人会提出比这更好的答案。

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

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

[edit]

[编辑]

Please see Josh's answer below. It's the way to go. (I knew there was a better way out there ;)

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

回答by Christos C

Easiest way is to serialize the relationship into an array:

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

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

Then when assigning values to readers, you apply them as

然后在为读者分配值时,您将它们应用为

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

When assigning relationships this way, duplicates are automatically removed.

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