¿Cómo implementar has_many: a través de las relaciones con Mongoid y mongodb?

Usando este ejemplo modificado de las guías de Rails , ¿cómo se modela una asociación relacional “has_many: through” usando mongoid?

El desafío es que mongoid no es compatible con has_many: como lo hace ActiveRecord.

# doctor checking out patient class Physician  :appointments has_many :meeting_notes, :through => :appointments end # notes taken during the appointment class MeetingNote  :appointments has_many :physicians, :through => :appointments end # the patient class Patient  :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 

Mongoid no tiene has_many: through o una función equivalente. No sería tan útil con MongoDB porque no es compatible con las consultas de combinación, por lo que incluso si pudiera hacer referencia a una colección relacionada a través de otra, aún requeriría múltiples consultas.

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

Normalmente, si tiene una relación de muchos y muchos en un RDBMS, debería modelarlo de forma diferente en MongoDB utilizando un campo que contenga un conjunto de claves “extranjeras” en cada lado. Por ejemplo:

 class Physician include Mongoid::Document has_and_belongs_to_many :patients end class Patient include Mongoid::Document has_and_belongs_to_many :physicians end 

En otras palabras, eliminaría la tabla de unión y tendría un efecto similar a has_many: a través del acceso al “otro lado”. Pero en su caso eso probablemente no sea apropiado porque su tabla de unión es una clase de Cita que lleva información adicional, no solo la asociación.

La forma de modelar esto depende, hasta cierto punto, de las consultas que necesita ejecutar, pero parece que tendrá que agregar el modelo de Citas y definir asociaciones para el paciente y el médico, algo como esto:

 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 

Con las relaciones en MongoDB siempre tiene que elegir entre documentos incrustados o asociados. En su modelo, supongo que MeetingNotes es un buen candidato para una relación incrustada.

 class Appointment include Mongoid::Document embeds_many :meeting_notes end class MeetingNote include Mongoid::Document embedded_in :appointment end 

Esto significa que puede recuperar las notas junto con una cita juntas, mientras que necesitaría múltiples consultas si se tratara de una asociación. Solo debe tener en cuenta el límite de tamaño de 16 MB para un solo documento que podría entrar en juego si tiene una gran cantidad de notas de reunión.

Solo para ampliar esto, aquí están los modelos extendidos con métodos que actúan de forma muy similar al has_many: a través de ActiveRecord al devolver un proxy de consulta en lugar de una matriz de registros:

 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 

¡La solución de Steven Soroka es realmente genial! No tengo la reputación de comentar una respuesta (es por eso que estoy agregando una nueva respuesta: P) pero creo que usar el mapa para una relación es costoso (especialmente si tu relación has_many tiene miles de registros) porque se vuelve los datos de la base de datos, crean cada registro, generan la matriz original y luego iteran sobre la matriz original para construir una nueva con los valores del bloque dado.

El uso de desplumado es más rápido y tal vez la opción más rápida.

 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 

Aquí algunas estadísticas con Benchmark.measure:

 > Benchmark.measure { physician.appointments.map(&:patient_id) } => # > Benchmark.measure { physician.appointments.pluck(:patient_id) } => # 

Estoy usando solo 250 citas. ¡No olvide agregar índices a: patient_id y: physician_id en el documento de cita!

Espero que ayude, ¡gracias por leer!

Quiero responder a esta pregunta desde la perspectiva de la asociación autorreferencial, no solo desde la perspectiva has_many: through.

Digamos que tenemos un CRM con contactos. Los contactos tendrán relaciones con otros contactos, pero en lugar de crear una relación entre dos modelos diferentes, crearemos una relación entre dos instancias del mismo modelo. Un contacto puede tener muchos amigos y ser amigo de muchos otros contactos, así que vamos a tener que crear una relación de muchos a muchos.

Si estamos utilizando un RDBMS y ActiveRecord, usaríamos has_many: through. Por lo tanto, necesitaríamos crear un modelo de unión, como Friendship. Este modelo tendría dos campos, un contact_id que representa el contacto actual que está agregando un amigo y un friend_id que representa al usuario con quien se hace amistad.

Pero estamos usando MongoDB y Mongoid. Como se indicó anteriormente, Mongoid no tiene has_many: through o una función equivalente. No sería tan útil con MongoDB porque no admite consultas de unión. Por lo tanto, para modelar una relación muchos-muchos en una base de datos no RDBMS como MongoDB, utiliza un campo que contiene una matriz de claves ‘extranjeras’ en cada lado.

 class Contact include Mongoid::Document has_and_belongs_to_many :practices end class Practice include Mongoid::Document has_and_belongs_to_many :contacts end 

Como dice la documentación:

Las relaciones de muchos a muchos donde los documentos inversos se almacenan en una colección separada del documento base se definen usando la macro has_and_belongs_to_many de Mongoid. Esto muestra un comportamiento similar a Active Record con la excepción de que no se necesita una colección de unión, los ID de clave externa se almacenan como matrices en cualquier lado de la relación.

Al definir una relación de esta naturaleza, cada documento se almacena en su colección respectiva, y cada documento contiene una referencia de “clave externa” a la otra en forma de una matriz.

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

Ahora, para una Asociación autorreferenciada en MongoDB, tiene algunas opciones.

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

¿Cuál es la diferencia entre contactos y contactos relacionados que tienen muchos y que pertenecen a muchas prácticas? ¡Gran diferencia! Una es una relación entre dos entidades. Otro es una autorreferencia.