วิธีใช้ has_many: ผ่านความสัมพันธ์กับ Mongoid และ Mongodb?


96

การใช้ตัวอย่างที่แก้ไขนี้จากคู่มือ Railsโมเดลหนึ่งการเชื่อมโยง "has_many: through" เชิงสัมพันธ์โดยใช้ mongoid เป็นอย่างไร

ความท้าทายคือ mongoid ไม่รองรับ has_many: through เหมือนที่ 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

คำตอบ:


151

Mongoid ไม่มี has_many: ผ่านหรือคุณสมบัติที่เทียบเท่า มันจะไม่มีประโยชน์กับ 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 ในแง่ของการเข้าถึง 'ด้านอื่น ๆ ' แต่ในกรณีของคุณนั่นอาจไม่เหมาะสมเนื่องจากตารางการเข้าร่วมของคุณเป็นคลาสการนัดหมายซึ่งมีข้อมูลเพิ่มเติมไม่ใช่เฉพาะการเชื่อมโยง

วิธีที่คุณสร้างแบบจำลองนี้ขึ้นอยู่กับขอบเขตของแบบสอบถามที่คุณต้องเรียกใช้ แต่ดูเหมือนว่าคุณจะต้องเพิ่มรูปแบบการนัดหมายและกำหนดความสัมพันธ์กับผู้ป่วยและแพทย์ดังนี้:

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
ถู

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) เป็นแผนที่ {| การนัดหมาย | นัดหมาย. physician.id}
Steven Soroka

ฉันสงสัยว่าวิธีนี้ช่วยลดความยุ่งยากที่อาจเกิดขึ้นกับขีด จำกัด ขนาดเอกสาร 16MB หรือไม่เนื่องจากเอกสารไม่ได้ถูกฝังไว้ แต่จะเชื่อมโยงโดยใช้โมเดลภายนอกแทนหรือไม่ (ขออภัยหากนี่เป็นคำถาม noob!)
Attila Györffy

ดังที่ฟรานซิสอธิบายการใช้.pluck()sinstead of .mapนั้นเร็วกว่ามาก คุณสามารถอัปเดตคำตอบของคุณสำหรับผู้อ่านในอนาคตได้หรือไม่?
Cyril Duchon-Doris

ฉันกำลังจะได้รับundefined method 'pluck' for #<Array:...>
Wylliam Judd

7

วิธีแก้ปัญหาของ Steven Soroka นั้นยอดเยี่ยมจริงๆ! ฉันไม่มีชื่อเสียงในการแสดงความคิดเห็นคำตอบ (นั่นคือเหตุผลที่ฉันกำลังเพิ่มคำตอบใหม่: P) แต่ฉันคิดว่าการใช้แผนที่สำหรับความสัมพันธ์นั้นมีราคาแพง (โดยเฉพาะอย่างยิ่งถ้าความสัมพันธ์ has_many ของคุณมีจำนวนมาก | บันทึกหลายพันรายการ) เนื่องจากได้รับ ข้อมูลจากฐานข้อมูลสร้างแต่ละเร็กคอร์ดสร้างอาร์เรย์เดิมแล้ววนซ้ำบนอาร์เรย์เดิมเพื่อสร้างใหม่ด้วยค่าจากบล็อกที่กำหนด

การใช้ถอนจะเร็วกว่าและอาจเป็นตัวเลือกที่เร็วที่สุด

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 { 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: ผ่านมุมมอง

สมมติว่าเรามี CRM กับผู้ติดต่อ ผู้ติดต่อจะมีความสัมพันธ์กับผู้ติดต่ออื่น ๆ แต่แทนที่จะสร้างความสัมพันธ์ระหว่างโมเดลที่แตกต่างกันสองแบบเราจะสร้างความสัมพันธ์ระหว่างสองอินสแตนซ์ของโมเดลเดียวกัน ผู้ติดต่อสามารถมีเพื่อนมากมายและถูกผูกมิตรกับผู้ติดต่ออื่น ๆ อีกมากมายดังนั้นเราจะต้องสร้างความสัมพันธ์แบบกลุ่มต่อกลุ่ม

หากเราใช้ RDBMS และ ActiveRecord เราจะใช้ has_many: through ดังนั้นเราจะต้องสร้างรูปแบบการเข้าร่วมเช่นมิตรภาพ โมเดลนี้จะมีสองฟิลด์ 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

ตามเอกสารระบุ:

ความสัมพันธ์แบบมากถึงหลายความสัมพันธ์ที่เอกสารผกผันถูกเก็บไว้ในคอลเลกชันที่แยกต่างหากจากเอกสารพื้นฐานถูกกำหนดโดยใช้มาโคร has_and_belongs_to_many ของ Mongoid สิ่งนี้แสดงลักษณะการทำงานที่คล้ายกันกับ Active Record โดยมีข้อยกเว้นว่าไม่จำเป็นต้องมีการรวมคอลเลกชันรหัสคีย์ต่างประเทศจะถูกจัดเก็บเป็นอาร์เรย์ที่ด้านใดด้านหนึ่งของความสัมพันธ์

เมื่อกำหนดความสัมพันธ์ในลักษณะนี้เอกสารแต่ละชุดจะถูกเก็บไว้ในคอลเลคชันตามลำดับและเอกสารแต่ละชุดจะมีการอ้างอิง "คีย์ต่างประเทศ" ไปยังอีกชุดหนึ่งในรูปแบบของอาร์เรย์

# 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

อะไรคือความแตกต่างระหว่างผู้ติดต่อที่เกี่ยวข้องและผู้ติดต่อที่มีหลายคนและอยู่ในแนวทางปฏิบัติมากมาย? แตกต่างกันมาก! หนึ่งคือความสัมพันธ์ระหว่างสองเอนทิตี อื่น ๆ คือการอ้างอิงตัวเอง


เอกสารตัวอย่างน่าจะเหมือนกัน?
CyberMew
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.