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


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

El reto es que mongoid no soporta has_many :through como lo hace 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
Author: Mario Zigliotto, 2011-08-09

4 answers

Mongoid no tiene has_many :through o una característica equivalente. No sería tan útil con MongoDB porque no admite consultas de unió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 muchos-muchos en un RDBMS, modelaría eso de manera diferente en MongoDB usando un campo que contiene una matriz de claves 'foráneas' en cualquiera de los dos 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 join y tendría un efecto similar a has_many :through en términos de acceso al 'otro lado'. Pero en su caso, eso probablemente no es apropiado porque su tabla de unirse es una clase de cita que lleva algo de información adicional, no solo la asociación.

Cómo modelar esto depende en cierta medida de las consultas que necesita ejecutar, pero parece que necesitará agregar el modelo de cita 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 hay que elegir entre documentos incrustados o asociados. En tu modelo supongo que MeetingNotes son 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 todos juntos, mientras que necesitaría múltiples consultas si se tratara de una asociación. Solo hay que tener en cuenta la Límite de tamaño de 16 MB para un solo documento que podría entrar en juego si tiene un gran número de notas de reunión.

 146
Author: Steve,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-13 20:31:24

Solo para ampliar esto, aquí están los modelos extendidos con métodos que actúan muy similares a has_many: a través de ActiveRecord devolviendo 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
 36
Author: Steven Soroka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-04 17:32:23

Steven Soroka solución es realmente genial! No tengo la reputación de comentar una respuesta(por eso estoy agregando una nueva respuesta :P) pero creo que usar map para una relación es costoso (especialmente si su relación has_many tiene cientos|miles de registros) porque obtiene los datos de la base de datos, construye cada registro, genera la matriz original y luego itera sobre la matriz original para construir una nueva con los valores del bloque dado.

Usando pluck es más rápido y tal vez el 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.medida:

> 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> 

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!

 6
Author: franciscodelgadodev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-24 16:27:16

Quiero responder a esta pregunta desde la perspectiva de la asociación autorreferenciante, 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, estaremos creando 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 un relación de muchos a muchos.

Si estamos usando un RDBMS y ActiveRecord, usaríamos has_many :through. Por lo tanto, necesitaríamos crear un modelo de unión, como la Amistad. Este modelo tendría dos campos, un contact_id que representa al contacto actual que está agregando un amigo y un friend_id que representa al usuario que está siendo amigo.

Pero estamos usando MongoDB y Mongoid. Como se indicó anteriormente, Mongoid no tiene has_many :through o una característica equivalente. No sería tan útil con MongoDB porque no admite consultas de combinación. Por lo tanto, para modelar una relación muchos-muchos en una base de datos no RDBMS como MongoDB, se utiliza un campo que contiene una matriz de claves 'foráneas' a 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:

Muchas a muchas relaciones donde los documentos inversos se almacenan en un colección separada del documento base se definen utilizando Mongoid has_and_belongs_to_many macro. Esto exhibe un comportamiento similar a Active Record con la excepción de que no se necesita ninguna colección de unión, los id de clave foránea se almacenan como matrices a cada lado de la relaci.

Al definir una relación de esta naturaleza, cada documento se almacena en su colección respectiva, y cada documento contiene una " clave foránea" referencia al otro en forma de 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 de autorreferenciación 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 relacionados y contactos que tienen muchos y pertenecen a muchas prácticas? Gran diferencia! Una es una relación entre dos entidades. Otro es una auto-referencia.

 0
Author: Donato,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-29 21:34:51