Ruby-on-rails 如何实现 has_many :通过与 Mongoid 和 mongodb 的关系?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7000605/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to implement has_many :through relationships with Mongoid and mongodb?
提问by Mario Zigliotto
Using this modified example from the Rails guides, how does one model a relational "has_many :through" association using mongoid?
使用Rails 指南中的这个修改后的示例,如何使用 mongoid 对关系“has_many :through”关联进行建模?
The challenge is that mongoid does not support has_many :through as ActiveRecord does.
挑战在于 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
回答by Steve
Mongoid doesn't have has_many :through or an equivalent feature. It would not be so useful with MongoDB because it does not support join queries so even if you could reference a related collection via another it would still require multiple queries.
Mongoid 没有 has_many :through 或等效功能。它对 MongoDB 不会那么有用,因为它不支持连接查询,因此即使您可以通过另一个引用相关集合,它仍然需要多个查询。
https://github.com/mongoid/mongoid/issues/544
https://github.com/mongoid/mongoid/issues/544
Normally if you have a many-many relationship in a RDBMS you would model that differently in MongoDB using a field containing an array of 'foreign' keys on either side. For example:
通常,如果您在 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
In other words you would eliminate the join table and it would have a similar effect to has_many :through in terms of access to the 'other side'. But in your case thats probably not appropriate because your join table is an Appointment class which carries some extra information, not just the association.
换句话说,您将消除连接表,并且在访问“另一端”方面,它与 has_many :through 具有类似的效果。但在您的情况下,这可能不合适,因为您的连接表是一个 Appointment 类,它带有一些额外的信息,而不仅仅是关联。
How you model this depends to some extent on the queries that you need to run but it seems as though you will need to add the Appointment model and define associations to Patient and Physician something like this:
您如何建模在某种程度上取决于您需要运行的查询,但似乎您需要添加约会模型并定义与患者和医生的关联,如下所示:
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
With relationships in MongoDB you always have to make a choice between embedded or associated documents. In your model I would guess that MeetingNotes are a good candidate for an embedded relationship.
使用 MongoDB 中的关系,您总是必须在嵌入或关联文档之间做出选择。在你的模型中,我猜 MeetingNotes 是嵌入关系的一个很好的候选者。
class Appointment
include Mongoid::Document
embeds_many :meeting_notes
end
class MeetingNote
include Mongoid::Document
embedded_in :appointment
end
This means that you can retrieve the notes together with an appointment all together, whereas you would need multiple queries if this was an association. You just have to bear in mind the 16MB size limit for a single document which might come into play if you have a very large number of meeting notes.
这意味着您可以一起检索笔记和约会,而如果这是关联,则需要多个查询。您只需要记住单个文档的 16MB 大小限制,如果您有大量会议记录,这可能会起作用。
回答by Steven Soroka
Just to expand on this, here's the models extended with methods that act very similar to the has_many :through from ActiveRecord by returning a query proxy instead of an array of records:
只是为了扩展这一点,这里的模型扩展了方法,这些方法与 ActiveRecord 中的 has_many :through 非常相似,通过返回查询代理而不是记录数组:
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
回答by franciscodelgadodev
Steven Soroka solution is really great! I don't have the reputation to comment an answer(That's why I'm adding a new answer :P) but I think using map for a relationship is expensive(specially if your has_many relationship have hunders|thousands of records) because it gets the data from database, build each record, generates the original array and then iterates over the original array to build a new one with the values from the given block.
Steven Soroka 的解决方案真的很棒!我没有评论答案的声誉(这就是我添加新答案的原因:P)但我认为使用 map 建立关系很昂贵(特别是如果您的 has_many 关系有成百上千条记录),因为它会得到来自数据库的数据,构建每条记录,生成原始数组,然后迭代原始数组以使用给定块中的值构建一个新数组。
Using pluck is faster and maybe the fastest option.
使用 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
Here some stats with Benchmark.measure:
这是 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>
I am using just 250 appointments. Don't forget to add indexes to :patient_id and :physician_id in Appointment document!
我只使用了 250 个约会。不要忘记在约会文档中为 :patient_id 和 :physician_id 添加索引!
I hope it helps, Thanks for reading!
希望能帮到你,感谢阅读!
回答by Donato
I want to answer this question from the self-referencing association perspective, not just the has_many :through perspective.
我想从自引用关联的角度来回答这个问题,而不仅仅是 has_many :through 的角度。
Let's say we have a CRM with contacts. Contacts will have relationships with other contacts, but instead of creating a relationship between two different models, we'll be creating a relationship between two instances of the same model. A contact can have many friends and be befriended by many other contacts so we're going to have to create a many-to-many relationship.
假设我们有一个带有联系人的 CRM。联系人将与其他联系人建立关系,但不是在两个不同模型之间创建关系,而是在同一模型的两个实例之间创建关系。一个联系人可以有很多朋友,并且可以与很多其他联系人成为朋友,因此我们将不得不创建多对多关系。
If we are using a RDBMS and ActiveRecord, we would use has_many :through. Thus we would need to create a join model, like Friendship. This model would have two fields, a contact_id that represents the current contact who's adding a friend and a friend_id that represents the user who's being befriended.
如果我们使用 RDBMS 和 ActiveRecord,我们将使用 has_many :through。因此我们需要创建一个连接模型,比如 Friendship。该模型将有两个字段,一个 contact_id 表示正在添加朋友的当前联系人,一个friend_id 表示正在加好友的用户。
But we are using MongoDB and Mongoid. As stated above, Mongoid doesn't have has_many :through or an equivalent feature. It would not be so useful with MongoDB because it does not support join queries. Therefore, in order to model a many-many relationship in a non-RDBMS database like MongoDB, you use a field containing an array of 'foreign' keys on either side.
但是我们使用的是 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
As the documentation states:
正如文档所述:
Many to many relationships where the inverse documents are stored in a separate collection from the base document are defined using Mongoid's has_and_belongs_to_many macro. This exhibits similar behavior to Active Record with the exception that no join collection is needed, the foreign key ids are stored as arrays on either side of the relation.
When defining a relation of this nature, each document is stored in its respective collection, and each document contains a “foreign key” reference to the other in the form of an array.
使用 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") ]
}
Now for a self-referencing Association in MongoDB, you have a few options.
现在对于 MongoDB 中的自引用关联,您有几个选择。
has_many :related_contacts, :class_name => 'Contact', :inverse_of => :parent_contact
belongs_to :parent_contact, :class_name => 'Contact', :inverse_of => :related_contacts
What is difference between related contacts and contacts having many and belonging to many practices? Huge difference! One is a relationship between two entities. Other is a self-reference.
相关联系人与具有许多和属于许多实践的联系人之间有什么区别?巨大的差异!一种是两个实体之间的关系。其他是自参考。

