Introduction to Object Relationships in Ruby
By: Ben Swanson
Tags: One To Many, Many To Many, Object Oriented, Ruby, Object Relationships

I’m not proud of what I’ve done, but it had to be done. This thoroughly commented diagram demonstrates a fully functional Many-to-Many relationship between Doctors and Patients. Notice how it looks suspiciously like two One-to-Many relationships (if it looks that way, that’s because it is).

Remember when we decided each attribute for a particular instance of a model should only contain a single value? Since Doctors have many Patients, and Patients can have many Doctors, neither one of them should store data on the other. A Many-to-Many relationship must have a join model, in this case Appointment, where each Appointment belongs to one Doctor and one Patient.

It is always the model belonging to a single instance of a related model (or models) that stores the related instances.

Re-read that until your eye-holes hurt. Alternatively, when you draw your relationship diagrams (and you should ALWAYS draw your relationship diagrams) you can just remember that you store related instances in the side receiving the forky end. I like to use app.diagrams.net (use the ‘Entity Relation’ objects).

I included some basic methods demonstrating typical functionality found in Many-to-Many relationships which also show how you have to ‘go through’ the join model to find all the instances of one side of the relationship that relate to a particular instance from the other side.

This is the end of the blog post. Take care of yourself.

And as promised, copy-paste-able code blocks if you want to play with this yourself:

Doctor

class Doctor attr_accessor :name, :office, :specialty #before we work with databases to store each instance of the #Doctor class, it is convenient to store each Doctor in a #class variable array called @@all @@all = [] def initialize(name, office, specialty) @name = name @office = office @specialty = specialty #this adds each instance of the Doctor class to @@all upon #initialization @@all << self end #inside a class when we start a method definition with 'self' it #means this is a class method (as opposed to an instance method). #That means we call .all on the Doctor class to return the array #of all instances def self.all @@all end #returns an array of all appointments relating to a #particular Doctor def appts Appointment.all.select { |appt| appt.doctor == self } end #returns an array of all appointment times relating #to a particular Doctor def appt_times self.appts.map{ |appt| appt.appt_time } end #returns an array of all patients relating to #a particular Doctor. See comment on the 'doctors' #method in the Patient class to learn why we #need to 'go through' Appointments def patients self.appts.map{ |appt| appt.patient.name } end end

Appointment

class Appointment attr_accessor :appt_time #here we have appt_time in an attr_accessor because we may #need to reschedule, but Doctor and Patient are in an #attr_reader, because they shouldn't change after an #Appointment instance is created. attr_reader :doctor, :patient @@all = [] #IMPORTANT - Appointment is the join model for the #Many-to-Many relationship between Doctors and #Patients. You can think of each side of the Many-to-Many #as having a One-to-Many with the join model. #It is always the model that 'has one' of the other #model where you store that model's information. Each #Appointment has one Doctor and one Patient, so we store #Doctor and Patient here. Doctors and Patients can have #many Appointments, so it is much easier to use #Appointment as the Single Source of Truth def initialize(appt_time, doctor, patient) @appt_time = appt_time @doctor = doctor @patient = patient @@all << self end def self.all @@all end end

Patient