Как реализовать has_many: через отношения с Mongoid и mongodb? - PullRequest
94 голосов
/ 09 августа 2011

Используя этот модифицированный пример из направляющих 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

Ответы [ 4 ]

150 голосов
/ 14 августа 2011

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: through с точки зрения доступа к «другой стороне».Но в вашем случае это, вероятно, не подходит, потому что ваша таблица соединений - это класс 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

Это означает, что вы можете извлекать заметки вместе с назначением вместе, тогда как вам потребуется несколько запросов, если это былоАссоциация.Вам просто нужно иметь в виду ограничение размера в 16 МБ для одного документа, который может вступить в игру, если у вас очень большое количество заметок о собрании.

36 голосов
/ 01 мая 2013

Просто чтобы расширить это, вот модели, дополненные методами, которые очень похожи на 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
6 голосов
/ 24 февраля 2015

Решение Стивена Сороки действительно великолепно!У меня нет репутации, чтобы комментировать ответ (вот почему я добавляю новый ответ: P), но я думаю, что использование map для отношений стоит дорого (особенно если в ваших отношениях has_many есть hunders | тысячи записей), потому что этоДанные из базы данных, построить каждую запись, генерирует исходный массив и затем выполняет итерации по исходному массиву для создания нового со значениями из данного блока.

Использование 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 в документе о встрече!

Надеюсь, это поможет, Спасибо за чтение!

0 голосов
/ 30 мая 2018

Я хочу ответить на этот вопрос с точки зрения ассоциации, ссылающейся на себя, а не только через has_many: через перспективу.

Допустим, у нас есть CRM с контактами. Контакты будут иметь отношения с другими контактами, но вместо того, чтобы создавать отношения между двумя разными моделями, мы будем создавать отношения между двумя экземплярами одной и той же модели. У контакта может быть много друзей, и он может быть подружен со многими другими контактами, поэтому нам придется создавать отношения многие-ко-многим.

Если мы используем RDBMS и ActiveRecord, мы бы использовали has_many: through. Таким образом, нам нужно будет создать модель соединения, такую ​​как Дружба. В этой модели будет два поля: contact_id, представляющий текущего контакта, добавляющего друга, и friend_id, представляющего пользователя, с которым происходит дружба.

Но мы используем MongoDB и Mongoid. Как указано выше, Mongoid не имеет has_many: through или эквивалентной функции. Это не было бы так полезно с MongoDB, потому что он не поддерживает запросы соединения. Поэтому, чтобы смоделировать отношение многие-многие в базе данных, не являющейся СУБД, такой как 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

В чем разница между связанными контактами и контактами, имеющими много и принадлежащими ко многим практикам? Огромная разница! Одним из них является отношения между двумя сущностями. Другое - это ссылка на себя.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...