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

时间:2011-08-09 17:57:48

标签: ruby-on-rails activerecord mongodb data-modeling mongoid

使用the Rails guides中的这个修改示例,如何使用mongoid建模关系“has_many:through”关联?

挑战是mongoid不支持has_many:通过ActiveRecord。

# 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

4 个答案:

答案 0 :(得分:150)

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类,它带有一些额外的信息,而不仅仅是关联。

如何对此进行建模在某种程度上取决于您需要运行的查询,但似乎您需要添加约会模型并定义患者和医生的关联,如下所示:

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 :(得分:36)

为了扩展这一点,这里的模型扩展了与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 :(得分:6)

Steven Soroka解决方案非常棒!我不具备评论答案的声誉(这就是为什么我要添加一个新的答案:P)但我认为使用地图建立关系是昂贵的(特别是如果你的伙伴关系有猎人的话) |成千上万的记录)因为它从数据库中获取数据,构建每条记录,生成原始数组,然后遍历原始数组,以使用给定块中的值构建一个新数组。

使用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!

我希望它有所帮助, 谢谢你的阅读!

答案 3 :(得分:0)

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

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

如果我们使用RDBMS和ActiveRecord,我们将使用has_many:through。因此,我们需要创建一个连接模型,如友谊。此模型将包含两个字段,一个contact_id表示正在添加朋友的当前联系人,另一个表示正在结识朋友的friend_id。

但我们正在使用MongoDB和Mongoid。如上所述,Mongoid没有has_many:through或等效功能。它对MongoDB没那么有用,因为它不支持连接查询。因此,为了在像MongoDB这样的非RDBMS数据库中建模多对多关系,您可以使用包含&#39; foreign&#39;的数组的字段。两侧的钥匙。

class Contact
  include Mongoid::Document
  has_and_belongs_to_many :practices
end

class Practice
  include Mongoid::Document
  has_and_belongs_to_many :contacts
end

正如文件所述:

  

反向文档存储在a中的多对多关系   使用Mongoid来定义基础文档中的单独集合   has_and_belongs_to_many宏。这表现出类似的行为   活动记录,但不需要加入集合,   外键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

相关联系人和拥有许多实践并且属于许多实践的联系人之间有什么区别?巨大的差异!一个是两个实体之间的关系。其他是自我参考。