web-dev-qa-db-fra.com

Comment mettre en œuvre has_many: à travers les relations avec Mongoid et Mongodb?

En utilisant cet exemple modifié de les guides Rails , comment modélise-t-on une association relationnelle "has_many: through" utilisant mongoid?

Le défi est que mongoid ne prend pas en charge has_many: through comme 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
93
Mario Zigliotto

Mongoid n'a pas has_many: through ou une fonctionnalité équivalente. Cela ne serait pas très utile avec MongoDB car il ne prend pas en charge les requêtes de jointure. Même si vous pouviez référencer une collection associée via une autre, plusieurs requêtes seraient toujours nécessaires.

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

Normalement, si vous avez plusieurs relations dans un SGBDR, vous devez modéliser cela différemment dans MongoDB en utilisant un champ contenant un tableau de clés "étrangères" de chaque côté. Par exemple:

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 d'autres termes, vous élimineriez la table de jointure et cela aurait un effet similaire à has_many: through en termes d'accès à "l'autre côté". Mais dans votre cas, cela n’est probablement pas approprié car votre table de jointure est une classe de rendez-vous qui contient des informations supplémentaires, pas seulement l’association.

La manière dont vous modélisez cela dépend dans une certaine mesure des requêtes que vous devez exécuter, mais il semble que vous deviez ajouter le modèle de rendez-vous et définir des associations entre le patient et le médecin, par exemple:

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

Avec les relations dans MongoDB, vous devez toujours choisir entre les documents incorporés ou associés. Dans votre modèle, je suppose que MeetingNotes est un bon candidat pour une relation intégrée.

class Appointment
  include Mongoid::Document
  embeds_many :meeting_notes
end

class MeetingNote
  include Mongoid::Document
  embedded_in :appointment
end

Cela signifie que vous pouvez récupérer les notes avec un rendez-vous, alors que vous auriez besoin de plusieurs requêtes s'il s'agissait d'une association. Vous devez simplement garder à l’esprit la limite de taille de 16 Mo pour un document unique qui pourrait entrer en jeu si vous avez un très grand nombre de notes de réunion.

148
Steve

Juste pour développer, voici les modèles étendus avec des méthodes très similaires à has_many: via ActiveRecord en renvoyant un proxy de requête au lieu d'un tableau d'enregistrements:

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

La solution de Steven Soroka est vraiment géniale! Je n'ai pas la réputation de commenter une réponse (c'est pourquoi j'ajoute une nouvelle réponse: P), mais je pense que l'utilisation de la carte pour une relation coûte cher (surtout si votre relation has_many a des centaines | milliers d'enregistrements) les données de la base de données, construisent chaque enregistrement, génèrent le tableau d'origine, puis itèrent sur le tableau d'origine pour en créer un nouveau avec les valeurs du bloc donné.

Utiliser le plumage est plus rapide et peut-être l'option la plus rapide.

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

Voici quelques statistiques avec 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> 

J'utilise seulement 250 rendez-vous ..__ N'oubliez pas d'ajouter des index à: patient_id et: administrator_id dans le document Rendez-vous!

J'espère que ça aide, .__ Merci d'avoir lu!

6
franciscodelgadodev

Je veux répondre à cette question du point de vue de l'association d'auto-référencement, et pas seulement du has_many: à travers la perspective.

Disons que nous avons un CRM avec des contacts. Les contacts auront des relations avec d'autres contacts, mais au lieu de créer une relation entre deux modèles différents, nous allons créer une relation entre deux instances du même modèle. Un contact peut avoir de nombreux amis et être lié d'amitié par de nombreux autres contacts. Nous allons donc devoir créer une relation plusieurs à plusieurs.

Si nous utilisons un SGBDR et ActiveRecord, nous utiliserions has_many: through. Nous aurions donc besoin de créer un modèle de jointure, tel que l’amitié. Ce modèle aurait deux champs, un contact_id qui représente le contact actuel qui ajoute un ami et un ami_id qui représente l’utilisateur qui se lie d’amitié.

Mais nous utilisons MongoDB et Mongoid. Comme indiqué ci-dessus, Mongoid n'a pas has_many: through ou une fonctionnalité équivalente. Cela ne serait pas très utile avec MongoDB car cela ne supporte pas les requêtes de jointure. Par conséquent, pour modéliser une relation multiple dans une base de données non-SGBDR telle que MongoDB, vous utilisez un champ contenant un tableau de clés "étrangères" de chaque côté.

class Contact
  include Mongoid::Document
  has_and_belongs_to_many :practices
end

class Practice
  include Mongoid::Document
  has_and_belongs_to_many :contacts
end

Comme le dit la documentation:

Plusieurs à plusieurs relations dans lesquelles les documents inverses sont stockés dans un Les collections séparées du document de base sont définies à l’aide du fichier .__ de Mongoid. has_and_belongs_to_many macro. Cela présente un comportement similaire à Enregistrement actif avec l'exception qu'aucune collection de jointure n'est nécessaire, les identifiants de clé étrangère sont stockés sous forme de tableaux de part et d'autre du fichier relation.

Lors de la définition d'une relation de cette nature, chaque document est stocké dans sa collection respective et chaque document contient une «clé étrangère» référence à l'autre sous la forme d'un tableau.

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

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

Maintenant, pour une association d’auto-référencement dans MongoDB, vous avez quelques options. 

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

Quelle est la différence entre les contacts liés et les contacts ayant beaucoup et appartenant à plusieurs pratiques? Énorme différence! L'un est une relation entre deux entités. Autre est une référence personnelle.

0
Donato