Làm thế nào để triển khai has_many: thông qua các mối quan hệ với Mongoid và mongodb?


96

Sử dụng ví dụ đã sửa đổi này từ hướng dẫn Rails , làm thế nào để mô hình hóa một liên kết quan hệ "has_many: through" bằng cách sử dụng mongoid?

Thách thức là mongoid không hỗ trợ has_many: through như 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

Câu trả lời:


151

Mongoid không có has_many: through hoặc một tính năng tương đương. Nó sẽ không quá hữu ích với MongoDB vì nó không hỗ trợ các truy vấn nối nên ngay cả khi bạn có thể tham chiếu một bộ sưu tập có liên quan qua một bộ sưu tập khác, nó vẫn sẽ yêu cầu nhiều truy vấn.

https://github.com/mongoid/mongoid/issues/544

Thông thường, nếu bạn có mối quan hệ nhiều-nhiều trong RDBMS, bạn sẽ lập mô hình khác nhau trong MongoDB bằng cách sử dụng trường chứa một mảng các khóa 'ngoại' ở hai bên. Ví dụ:

class Physician
  include Mongoid::Document
  has_and_belongs_to_many :patients
end

class Patient
  include Mongoid::Document
  has_and_belongs_to_many :physicians
end

Nói cách khác, bạn sẽ loại bỏ bảng tham gia và nó sẽ có tác dụng tương tự với has_many: thông qua việc truy cập vào 'phía bên kia'. Nhưng trong trường hợp của bạn, điều đó có thể không phù hợp vì bảng tham gia của bạn là một lớp Cuộc hẹn mang một số thông tin bổ sung, không chỉ liên kết.

Cách bạn mô hình hóa điều này ở một mức độ nào đó phụ thuộc vào các truy vấn mà bạn cần chạy nhưng có vẻ như bạn sẽ cần thêm mô hình Cuộc hẹn và xác định các liên kết với Bệnh nhân và Bác sĩ như sau:

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

Với các mối quan hệ trong MongoDB, bạn luôn phải lựa chọn giữa các tài liệu được nhúng hoặc liên kết. Trong mô hình của bạn, tôi đoán rằng MeetingNotes là một ứng cử viên sáng giá cho một mối quan hệ nhúng.

class Appointment
  include Mongoid::Document
  embeds_many :meeting_notes
end

class MeetingNote
  include Mongoid::Document
  embedded_in :appointment
end

Điều này có nghĩa là bạn có thể truy xuất các ghi chú cùng với một cuộc hẹn, trong khi bạn sẽ cần nhiều truy vấn nếu đây là một liên kết. Bạn chỉ cần lưu ý giới hạn kích thước 16MB cho một tài liệu duy nhất có thể phát huy tác dụng nếu bạn có một số lượng rất lớn ghi chú cuộc họp.


7
+1 câu trả lời rất hay, chỉ để biết thông tin, giới hạn kích thước mongodb đã được tăng lên 16 MB.
rác

1
Vì tò mò (xin lỗi vì yêu cầu muộn), tôi cũng mới sử dụng Mongoid và tôi đã tự hỏi bạn sẽ truy vấn dữ liệu như thế nào khi nó là một mối quan hệ nn bằng cách sử dụng một bộ sưu tập riêng để lưu trữ liên kết, nó có giống với nó không với ActiveRecord?
innospark

38

Chỉ để mở rộng điều này, đây là các mô hình được mở rộng với các phương thức hoạt động rất giống với has_many: thông qua từ ActiveRecord bằng cách trả về proxy truy vấn thay vì một mảng bản ghi:

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
điều này chắc chắn đã giúp ích vì phương thức của tôi để truy xuất đang trả về một mảng làm rối loạn phân trang.
prasad.surase

1
Không có phép thuật. @CyrilDD, bạn đang đề cập đến điều gì vậy? map (&: doctor_id) là viết tắt của bản đồ {| cuộc hẹn | hẹn.physician.id}
Steven Soroka

Tôi tự hỏi, liệu cách tiếp cận này có làm giảm sự thất vọng tiềm ẩn với giới hạn kích thước tài liệu 16MB, vì tài liệu không được nhúng mà thay vào đó được liên kết bằng cách sử dụng mô hình bên ngoài? (xin lỗi nếu điều này là một câu hỏi noob!)
Attila Györffy

Như Francis giải thích, sử dụng .pluck()sinstead of .mapnhanh hơn RẤT NHIỀU. Bạn có thể cập nhật câu trả lời của bạn cho những độc giả trong tương lai?
Cyril Duchon-Doris

Tôi đang nhận đượcundefined method 'pluck' for #<Array:...>
Wylliam Judd

7

Giải pháp Steven Soroka thực sự tuyệt vời! Tôi không có danh tiếng để bình luận một câu trả lời (Đó là lý do tại sao tôi thêm một câu trả lời mới: P) nhưng tôi nghĩ rằng việc sử dụng bản đồ cho một mối quan hệ là tốn kém (đặc biệt nếu mối quan hệ has_many của bạn có hàng nghìn bản ghi) vì nó được dữ liệu từ cơ sở dữ liệu, xây dựng từng bản ghi, tạo mảng ban đầu và sau đó lặp lại mảng ban đầu để tạo một mảng mới với các giá trị từ khối đã cho.

Sử dụng pluck nhanh hơn và có thể là lựa chọn nhanh nhất.

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

Dưới đây là một số thống kê với 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> 

Tôi chỉ sử dụng 250 cuộc hẹn. Đừng quên thêm các chỉ mục vào: bệnh nhân_id và: bác sĩ_id trong tài liệu Cuộc hẹn!

Tôi hy vọng nó sẽ giúp, Cảm ơn đã đọc!


Tôi đang nhậnundefined method 'pluck' for #<Array:...>
Wylliam Judd

0

Tôi muốn trả lời câu hỏi này từ quan điểm liên kết tự tham chiếu, không chỉ theo quan điểm has_many: thông qua.

Giả sử chúng tôi có một CRM với các địa chỉ liên hệ. Địa chỉ liên hệ sẽ có mối quan hệ với các địa chỉ liên hệ khác, nhưng thay vì tạo mối quan hệ giữa hai mô hình khác nhau, chúng tôi sẽ tạo mối quan hệ giữa hai phiên bản của cùng một mô hình. Một liên hệ có thể có nhiều bạn bè và kết bạn với nhiều liên hệ khác, vì vậy chúng ta sẽ phải tạo ra một mối quan hệ nhiều-nhiều.

Nếu chúng ta đang sử dụng RDBMS và ActiveRecord, chúng ta sẽ sử dụng has_many: through. Vì vậy, chúng ta sẽ cần tạo một mô hình tham gia, như Friendship. Mô hình này sẽ có hai trường, contact_id đại diện cho người liên hệ hiện tại đang thêm bạn và friend_id đại diện cho người dùng đang được kết bạn.

Nhưng chúng tôi đang sử dụng MongoDB và Mongoid. Như đã nêu ở trên, Mongoid không có has_many: through hoặc một tính năng tương đương. Nó sẽ không hữu ích với MongoDB vì nó không hỗ trợ các truy vấn nối. Do đó, để mô hình hóa mối quan hệ nhiều-nhiều trong cơ sở dữ liệu không phải RDBMS như MongoDB, bạn sử dụng một trường chứa một mảng các khóa 'ngoại' ở hai bên.

class Contact
  include Mongoid::Document
  has_and_belongs_to_many :practices
end

class Practice
  include Mongoid::Document
  has_and_belongs_to_many :contacts
end

Như tài liệu nói:

Mối quan hệ nhiều đến nhiều trong đó các tài liệu nghịch đảo được lưu trữ trong một bộ sưu tập riêng biệt từ tài liệu cơ sở được xác định bằng cách sử dụng macro has_and_belongs_to_many của Mongoid. Điều này thể hiện hành vi tương tự như Active Record với ngoại lệ là không cần tập hợp nối, id khóa ngoại được lưu trữ dưới dạng mảng ở hai bên của quan hệ.

Khi xác định một quan hệ có tính chất này, mỗi tài liệu được lưu trữ trong bộ sưu tập tương ứng của nó và mỗi tài liệu chứa một tham chiếu “khóa ngoại” đến tài liệu kia dưới dạng một mảng.

# the contact document
{
  "_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
  "practice_ids" : [ ObjectId("4d3ed089fb60ab534684b7f2") ]
}

# the practice document
{
  "_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
  "contact_ids" : [ ObjectId("4d3ed089fb60ab534684b7f2") ]
}

Bây giờ đối với một Hiệp hội tự tham chiếu trong MongoDB, bạn có một vài lựa chọn.

has_many :related_contacts, :class_name => 'Contact', :inverse_of => :parent_contact
belongs_to :parent_contact, :class_name => 'Contact', :inverse_of => :related_contacts

Sự khác biệt giữa các liên hệ có liên quan và liên hệ có nhiều và thuộc nhiều thực hành là gì? Sự khác biệt lớn! Một là mối quan hệ giữa hai thực thể. Khác là tự tham khảo.


Các tài liệu ví dụ có vẻ giống nhau?
CyberMew
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.