In this walkthrough I will be showing you how to set up a 'has_many :through' relationship. There's a good chance that your Ruby application will utilize multiple classes and it will be necessary to have them connected in particular way to access information from foreign tables. In order for your tables to be aware of each other they will need to be connected properly.
Before starting you'll want to carefully think about how the relationships will be set up. When creating a has_many through relationship you'll need a join class. The join class, as the name suggest, joins the other two models together. For instance, if we were working with three models, "Doctor", "Patient" and "Appointment", it would make sense for the "Appointment" model to be the joiner. Doctor can have many patients and patients can have many doctors, for the sake of this example "Appointment" will be the joiner.
The joiner will have the belongs_to methods in its model. And because the joiner has the belongs_to methods, it will also need the foreign keys for both Doctor and Patient in its migration file. It's the responsibility of the model with the belongs_to to also carry the foreign key.
When creating your Models, Views and Controllers you might be inclined to do so manually. I prefer to use generators, they're much easier, faster and there's a lower chance of encountering bugs further down the road.
There are different generators available to you. You'll want to choose one based on the needs of you application. If you don't have any of the MVC files (Model, View, Controller), the best generator would probably be rails generate resource.
rails g resource Appointment doctor:references patient:references
You can also add flags to the end of your generators. A "no test framework" flag is particularly useful if you don't want the additional tests that come with this generator, although it is completely optional.
rails g resource Appointment doctor:references patient:references --no-test-framework
You may already have some of the MVC files in your application, in that case you probably wouldn't want to use that particular generator. Having repeat and unnecessary files will bloat your application and complicate things. There are other generators available for only creating specific resources. If you only need to create the model you could use the model generator.
rails g model Appointment doctor:references patient:references
In the above examples "references" provides the foreign key to the Appointment model. If you look at your migration files in the db directory of your app you'll see that you now have columns for both doctor and patient foreign keys. This is crucial for establishing the relationship.
When generating the models for Doctor and Patient they won't require you to add references. These models, being parent classes, do not bare the responsibility of holding the foreign keys. If our Doctor class only had an attribute of name, we would only need to account for that, not references.
rails g resource Doctor name:string
Here we've created full MVC for the Doctor class with an attribute of name, which is a string. We would then do the same for the Patient class.
rails g resource Patient name:string age:integer
Very similar to Doctor, but in this case we've added a second attribute for patient, age. The name of the attribute is specified on the left side of the colon and the datatype is specified on the right side. There are more than a few datatypes that you can use, these are just two. Choose the datatype that is appropriate for your attribute. If you make a mistake while generating your models, or if for any reason you want to get rid of everything you've just created, you can delete all of the files you've created by running 'rails d resource'.
rails d resource Patient
After inspecting your migration files to confirm that the attributes are represented as they should be, and that the foreign keys are present on the table of the joining class, it's time move on to our Models.
As stated earlier, the joining model will have belongs_to method, which should have been generated for us by running the commands described above. It will look like this.
class Subject < ApplicationRecord
belongs_to :doctor
belongs_to :patient
end
Syntax is important here. A misplaced comma or colon could really throw a wrench into the mix. Also it's important that doctor and patient are singularized. An instance of appointment belongs to one doctor and one patient.
class Doctor < ApplicationRecord
has_many :appointments
has_many :patients, through: :appointments
end
The parent classes however, require the owned classes to be pluralized in the model. An instance of Doctor has many appointments and many patients through appointments, pluralized, just as you would say out loud. It's also important to list the join table before the has_many through for the relationship to work as planned. The program needs to made aware that the join table exists to be able to make sense of relationship created through it. We then do the same for the Patient model, but with the appropriate classes listed.
class Patient < ApplicationRecord
has_many :appointments
has_many :doctors, through: :appointments
end
Now after we've created the migration files with the appropriate generators, checked them to make sure they have the correct columns and corresponding datatypes, made sure the foreign keys are on the join table columns, and confirmed that our has_many's and belong_to's are properly laid out, it's time to migrate.
rails db:migrate
Run 'rails db:migrate' in the terminal. You should notice a new file in your db directory called 'schema'. Your schema should have the correct attributes in the columns of the corresponding tables with the foreign keys in the columns of the join class's table. If all of the previous steps were executed correctly your has many through relationship should be ready to go!
I hope this post was informative and helpful. I'm still new to Ruby, and I am by no means an expert. Please reach out if if you notice anything that needs to be corrected. Thank you for reading!
Top comments (0)