如何通过与Mongoid和mongodb的关系来实现has_many:?


96

使用Rails指南中的修改示例,如何使用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

Answers:


151

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:through具有相似的效果。但是在您的情况下,那可能不合适,因为您的联接表是一个Appointment类,它携带一些额外的信息,而不仅仅是关联。

建模的方式在一定程度上取决于您需要运行的查询,但似乎您需要添加约会模型并为患者和医师定义关联,如下所示:

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大小限制,如果您有大量的会议记录,则可能会起作用。


7
+1非常好的答案,仅作参考,mongodb的大小限制已增加到16 MB。
rubish

1
出于好奇(很抱歉稍后提出询问),我对Mongoid还是陌生的,我想知道当它是nn关系时,使用单独的集合来存储关联,您将如何查询数据?与ActiveRecord?
innospark

38

只是为了对此进行扩展,这是模型的扩展,这些方法的行为与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

2
这肯定有助于导致我的检索方法返回了一个弄乱分页的数组。
prasad.surase

1
没魔术 @CyrilDD,您指的是什么?map(&:physician_id)是地图的简写{|约会| 任命
斯蒂芬·索罗卡

我想知道,考虑到文档不是嵌入的而是使用外部模型关联的,因此此方法是否可以减少16MBs文档大小限制的潜在麻烦?(很抱歉,如果这是一个
菜鸟

正如弗朗西斯(Francis)解释的那样,使用.pluck()sinstead的.map速度要快得多。您可以为将来的读者更新答案吗?
Cyril Duchon-Doris

我得到undefined method 'pluck' for #<Array:...>
Wylliam Judd

7

Steven Soroka的解决方案真的很棒!我没有评论的声誉(这就是为什么我要添加一个新的答案:P的原因),但是我认为使用map来建立关系是昂贵的(特别是如果has_many关系有hunder |数千条记录),因为它可以来自数据库的数据,构建每条记录,生成原始数组,然后遍历原始数组以使用给定块中的值构建一个新数组。

使用采摘更快,也许是最快的选择。

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:...>
Wylliam Judd

0

我想从自引用关联的角度回答这个问题,而不仅仅是has_many:through角度。

假设我们有一个带有联系人的CRM。联系人将与其他联系人具有关系,但是我们将在同一模型的两个实例之间创建关系,而不是在两个不同的模型之间创建关系。一个联系人可以有很多朋友,也可以与许多其他联系人成为朋友,因此我们将不得不建立多对多关系。

如果使用RDBMS和ActiveRecord,则将使用has_many:through。因此,我们需要创建一个联接模型,例如Friendship。该模型将具有两个字段,一个contact_id代表当前正在添加朋友的联系人,另一个friend_id代表正在成为好友的用户。

但是我们正在使用MongoDB和Mongoid。如上所述,Mongoid没有has_many:through或等效功能。对于MongoDB来说,它不是那么有用,因为它不支持联接查询。因此,为了在非RDBMS数据库(如MongoDB)中建立多对多关系模型,请使用一个在两边都包含“外国”键阵列的字段。

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

相关联系人和具有很多实践经验的联系人有什么区别?差异很大!一种是两个实体之间的关系。其他是自我参考。


例子文件好像一样吗?
Cyber​​Mew
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.