如何实现has_many:通过与Mongoid和mongodb的关系?
Posted
技术标签:
【中文标题】如何实现has_many:通过与Mongoid和mongodb的关系?【英文标题】:How to implement has_many :through relationships with Mongoid and mongodb? 【发布时间】:2011-10-23 10:58:13 【问题描述】:使用the Rails guides 中的这个修改示例,如何使用 mongoid 对关系“has_many :through”关联进行建模?
挑战在于 mongoid 不像 ActiveRecord 那样支持 has_many :through。
# doctor checking out patient
class Physician < ActiveRecord::Base
has_many :appointments
has_many :patients, :through => :appointments
has_many :meeting_notes, :through => :appointments
end
# notes taken during the appointment
class MeetingNote < ActiveRecord::Base
has_many :appointments
has_many :patients, :through => :appointments
has_many :physicians, :through => :appointments
end
# the patient
class Patient < ActiveRecord::Base
has_many :appointments
has_many :physicians, :through => :appointments
has_many :meeting_notes, :through => :appointments
end
# the appointment
class Appointment < ActiveRecord::Base
belongs_to :physician
belongs_to :patient
belongs_to :meeting_note
# has timestamp attribute
end
【问题讨论】:
【参考方案1】:Mongoid 没有 has_many :through 或等效功能。它对 MongoDB 没有那么有用,因为它不支持连接查询,因此即使您可以通过另一个集合引用相关集合,它仍然需要多个查询。
https://github.com/mongoid/mongoid/issues/544
通常,如果您在 RDBMS 中存在多对多关系,您将在 MongoDB 中使用包含“外”键数组的字段以不同方式进行建模。例如:
class Physician
include Mongoid::Document
has_and_belongs_to_many :patients
end
class Patient
include Mongoid::Document
has_and_belongs_to_many :physicians
end
换句话说,您将消除连接表,它会产生与 has_many 类似的效果:在访问“另一边”方面。但在您的情况下,这可能不合适,因为您的联接表是一个 Appointment 类,其中包含一些额外信息,而不仅仅是关联。
如何对此进行建模在某种程度上取决于您需要运行的查询,但您似乎需要添加 Appointment 模型并定义与 Patient 和 Physician 的关联,如下所示:
class Physician
include Mongoid::Document
has_many :appointments
end
class Appointment
include Mongoid::Document
belongs_to :physician
belongs_to :patient
end
class Patient
include Mongoid::Document
has_many :appointments
end
对于 MongoDB 中的关系,您始终必须在嵌入文档或关联文档之间做出选择。在您的模型中,我猜 MeetingNotes 是嵌入关系的良好候选者。
class Appointment
include Mongoid::Document
embeds_many :meeting_notes
end
class MeetingNote
include Mongoid::Document
embedded_in :appointment
end
这意味着您可以一起检索笔记和约会,而如果这是一个关联,则需要多个查询。您只需要记住单个文档的 16MB 大小限制,如果您有大量会议记录,这可能会发挥作用。
【讨论】:
+1 非常好的答案,仅供参考,mongodb 大小限制已增加到 16 MB。 出于好奇(抱歉查询迟了),我也是 Mongoid 的新手,我想知道当它是使用单独的集合存储关联的 nn 关系时,您将如何查询数据,它和 ActiveRecord 一样吗?【参考方案2】:只是为了对此进行扩展,这里的模型扩展了与 has_many 非常相似的方法:从 ActiveRecord 通过返回查询代理而不是记录数组:
class Physician
include Mongoid::Document
has_many :appointments
def patients
Patient.in(id: appointments.pluck(:patient_id))
end
end
class Appointment
include Mongoid::Document
belongs_to :physician
belongs_to :patient
end
class Patient
include Mongoid::Document
has_many :appointments
def physicians
Physician.in(id: appointments.pluck(:physician_id))
end
end
【讨论】:
这肯定有助于导致我的检索方法返回一个搞乱分页的数组。 没有魔法。 @CyrilDD,你指的是什么? map(&:physician_id) 是 map|appointment| 的简写约会.physician.id 我想知道,考虑到文档不是嵌入的而是使用外部模型关联的,这种方法是否会减少对 16MB 文档大小限制的潜在挫败感? (对不起,如果这是一个菜鸟问题!) 正如弗朗西斯解释的那样,使用.pluck()
而不是.map
要快得多。你能为未来的读者更新你的答案吗?
我收到undefined method 'pluck' for #<Array:...>
【参考方案3】:
Steven Soroka 的解决方案真的很棒!我没有评论答案的声誉(这就是我添加新答案的原因:P)但我认为使用 map 来建立关系是昂贵的(特别是如果你的 has_many 关系有数百条|数千条记录),因为它得到从数据库中获取数据,构建每条记录,生成原始数组,然后迭代原始数组以使用给定块中的值构建一个新数组。
使用 pluck 更快,也许是最快的选择。
class Physician
include Mongoid::Document
has_many :appointments
def patients
Patient.in(id: appointments.pluck(:patient_id))
end
end
class Appointment
include Mongoid::Document
belongs_to :physician
belongs_to :patient
end
class Patient
include Mongoid::Document
has_many :appointments
def physicians
Physician.in(id: appointments.pluck(:physician_id))
end
end
这里有一些使用 Benchmark.measure 的统计数据:
> Benchmark.measure physician.appointments.map(&:patient_id)
=> #<Benchmark::Tms:0xb671654 @label="", @real=0.114643818, @cstime=0.0, @cutime=0.0, @stime=0.010000000000000009, @utime=0.06999999999999984, @total=0.07999999999999985>
> Benchmark.measure physician.appointments.pluck(:patient_id)
=> #<Benchmark::Tms:0xb6f4054 @label="", @real=0.033517774, @cstime=0.0, @cutime=0.0, @stime=0.0, @utime=0.0, @total=0.0>
我只使用了 250 个约会。 不要忘记在约会文档中为 :patient_id 和 :physician_id 添加索引!
希望对你有帮助 感谢阅读!
【讨论】:
我收到undefined method 'pluck' for #<Array:...>
【参考方案4】:
我想从自引用关联的角度来回答这个问题,而不仅仅是 has_many :through 的角度。
假设我们有一个包含联系人的 CRM。联系人将与其他联系人有关系,但不是在两个不同模型之间创建关系,而是在同一模型的两个实例之间创建关系。一个联系人可以有很多朋友,并且可以与很多其他联系人成为朋友,因此我们将不得不创建多对多的关系。
如果我们使用 RDBMS 和 ActiveRecord,我们将使用 has_many :through。因此我们需要创建一个连接模型,比如 Friendship。该模型将有两个字段,一个表示正在添加朋友的当前联系人的contact_id,一个表示正在成为好友的用户的friend_id。
但我们使用的是 MongoDB 和 Mongoid。如上所述,Mongoid 没有 has_many :through 或等效功能。它对 MongoDB 没有那么有用,因为它不支持连接查询。因此,为了在像 MongoDB 这样的非 RDBMS 数据库中建模多对多关系,您需要使用一个字段,该字段在任一侧都包含一个“外”键数组。
class Contact
include Mongoid::Document
has_and_belongs_to_many :practices
end
class Practice
include Mongoid::Document
has_and_belongs_to_many :contacts
end
如文档所述:
多对多关系,其中反向文档存储在一个 使用 Mongoid 定义与基础文档分开的集合 has_and_belongs_to_many 宏。这表现出与 Active Record,但不需要加入集合, 外键 ID 以数组形式存储在 关系。
在定义这种性质的关系时,每个文档都存储在 它各自的集合,每个文档都包含一个“外键” 以数组的形式引用另一个。
# the contact document
"_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
"practice_ids" : [ ObjectId("4d3ed089fb60ab534684b7f2") ]
# the practice document
"_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
"contact_ids" : [ ObjectId("4d3ed089fb60ab534684b7f2") ]
现在对于 MongoDB 中的自引用关联,您有几个选择。
has_many :related_contacts, :class_name => 'Contact', :inverse_of => :parent_contact
belongs_to :parent_contact, :class_name => 'Contact', :inverse_of => :related_contacts
相关联系人与联系人多且属于多实践有什么区别?巨大的差异!一种是两个实体之间的关系。其他是自引用。
【讨论】:
示例文档好像是一样的?以上是关于如何实现has_many:通过与Mongoid和mongodb的关系?的主要内容,如果未能解决你的问题,请参考以下文章
将 RABL 与 mongoid 一起使用时减少 DB 调用