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
how to avoid duplicates in a has_many :through relationship?
提问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,相同blogID)BlogsReaders。我怎样才能做到这一点?
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.
以这种方式分配关系时,会自动删除重复项。

