如何避免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]
以这种方式分配关系时,会自动删除重复项。