Як реалізувати has_many: через відносини з Mongoid та mongodb?


96

Використовуючи цей модифікований приклад із довідників Rails , як можна моделювати реляційну асоціацію "has_many: through", використовуючи mongoid?

Проблема полягає в тому, що 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

Відповіді:


151

Mongoid не має has_many: через або еквівалентну функцію. Це не було б настільки корисним для MongoDB, оскільки він не підтримує запити на приєднання, тому навіть якщо ви можете посилатися на пов'язану колекцію через іншу, це все одно вимагатиме декількох запитів.

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

Зазвичай, якщо у вас є взаємозв'язок багато-багато в СУБД, ви б моделювали це по-різному в 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: через з точки зору доступу до "іншої сторони". Але у вашому випадку це, мабуть, не підходить, оскільки ваша таблиця приєднання - це клас призначення, який містить деяку додаткову інформацію, а не лише асоціацію.

Як ви це моделюєте, певною мірою залежить від запитів, які вам потрібно виконати, але, здається, вам потрібно буде додати модель призначення та визначити асоціації для пацієнта та лікаря приблизно так:

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

Це означає, що ви можете отримувати нотатки разом із зустріччю всі разом, тоді як вам потрібно буде кілька запитів, якщо це буде асоціація. Потрібно лише пам’ятати про обмеження розміру 16 МБ для окремого документа, який може увійти в дію, якщо у вас дуже велика кількість приміток до наради.


7
+1 дуже гарна відповідь, лише для інформації, обмеження розміру mongodb збільшено до 16 МБ.
сміття

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}
Стівен Сорока 02

Цікаво, чи зменшує цей підхід потенційне розчарування обмеженням розміру документа в 16 МБ, враховуючи те, що документи не вбудовані, а натомість пов’язані за допомогою зовнішньої моделі? (вибачте, якщо це питання
нубу

Як пояснює Френсіс, використання .pluck()sinstead .mapнабагато швидше. Чи можете ви оновити свою відповідь для майбутніх читачів?
Кирило Дюшон-Доріс

Я отримуюundefined method 'pluck' for #<Array:...>
Вільям Джадд

7

Рішення Стівена Сороки - справді чудове! У мене немає репутації, щоб коментувати відповідь (Ось чому я додаю нову відповідь: 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:

> 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:...>
Вільям Джадд

0

Я хочу відповісти на це питання з точки зору асоціації, що посилається на себе, а не лише з has_many: через перспективу.

Скажімо, у нас є CRM з контактами. Контакти матимуть зв’язки з іншими контактами, але замість того, щоб створювати зв’язок між двома різними моделями, ми створимо зв’язок між двома екземплярами однієї моделі. У контакту може бути багато друзів і дружити з багатьма іншими контактами, тому нам доведеться створити стосунки багато-до-багатьох.

Якщо ми використовуємо СУБД та 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

Як зазначено в документації:

Багато-багато взаємозв'язків, де зворотні документи зберігаються в окремій колекції від базового документа, визначаються за допомогою макросу Mongoid has_and_belongs_to_many. Це демонструє подібну поведінку до 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.