38

我怎样才能实现以下目标?我有两个模型(博客和读者)和一个 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,相同blogIDBlogsReaders。我怎样才能做到这一点?

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

4

9 回答 9

93

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调用中。

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

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

更新

在 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
于 2008-11-25T17:13:19.373 回答
39

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

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

  validates_uniqueness_of :reader_id, :scope => :blog_id
end
于 2008-11-24T23:08:27.787 回答
20

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
于 2016-08-09T08:12:08.087 回答
7

关于什么:

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

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

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

于 2008-11-25T14:39:59.840 回答
2

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

于 2011-01-03T15:05:58.490 回答
2

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

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
于 2017-08-04T16:01:17.650 回答
1

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

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

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

[编辑]

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

于 2008-11-25T00:42:04.603 回答
1

我为 Rails 6 执行以下操作

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

  validates :blog_id, uniqueness: { scope: :reader_id }
end

不要忘记创建数据库约束以防止违反唯一性。

于 2021-08-30T19:45:18.883 回答
-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]

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

于 2016-02-02T22:32:42.163 回答