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