Как реализовать has_many: через отношения с Mongoid и mongodb?
Используя этот измененный пример из руководства Rails, как моделировать реляционную ассоциацию "has_many: через", используя 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
Ответы
Ответ 1
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: через доступ к "другой стороне". Но в вашем случае это, вероятно, не подходит, потому что ваша таблица соединений - это класс 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 МБ для одного документа, который может вступить в игру, если у вас очень большое количество заметок.
Ответ 2
Просто чтобы расширить это, здесь модели расширены с помощью методов, которые очень похожи на 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
Ответ 3
Решение Стивена Сороки действительно здорово! У меня нет репутации, чтобы прокомментировать ответ (вот почему я добавляю новый ответ: P), но я думаю, что использование карты для отношений дорого (особенно, если у вас есть отношения 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 в документе Назначения!
Надеюсь, это поможет,
Спасибо за чтение!