Rails Associations In Plain English : What I Wish I Had Known Before I Started
Ok, ok, I can’t hide. I am a huge Ruby on Rails fan. It’s been a couple of years that i knew about RoR existence, but i never actually got to learn more about it, never engaged myself to its internals. For years, i’ve been coding A LOT using PHP, mySQL, Smarty or other template engines, Pear and the likes. As much as i liked all these, the truth is ugly; they are too damn slow for a single programmer to work with.
RoR came as a shock to me. In the first days, i could not even believe that it would automatically do all these things for me. Convention over configuration and a steep learning curve for me, BUT, it comes with a prize. Fewer code, less time spend, more things done. And as in every business, we need to maximize profit using the less resources possible, right ?
Yes, RoR is Difficult to Learn but ..
..it is heavily worth it. My problem when learning Rails was that of conventions (surprise !). Even today, i still meet some new challenges in that area. However, after reading more and more about Rails, it feels more natural and easier to grasp. Literally every new Rails programmer struggles with conventions. Single name for models, plural name for database tables, find_by, the list seems endless.
For me, one of the most difficult things to handle was Rails Associations. Before telling you a few things about them, please never underestimate the power of the official resource on Rails associations. I would like to explain associations to you as i understand them, not with many technical stuff. All technical things are available online easily, but getting a good grasp on the topic means that you understand the mechanics; and i feel that this is much more important than enumerating things for you.
What is an Association ?
If you have been using a database management system (mySQL, Oracle etc.), you already know of database associations. As i promised, i will not go too technical, and only stress the important things. In short, there are just 3 types :
1. one to one -> A child has one mother
2. one to many -> A mother has many children
3. many to many -> Many students have many teachers
1. A child can only have one mother (in this planet). In Rails words, you would have a Mother model and a Child Model. It is important to remember that belongs_to refers to the model where the foreign key is set. This example would create the database tables : children(irregular plural form) and mothers. A child has_one :mother and a mother belongs_to :child. This means that the attributes of the two tables are like :
children(id, name, whatever ..)
mothers(id, child_id, name, whatever ..)
2. For the second example, everything, in terms of database tables(models) is exactly the same. The difference in that declaration is that a mother can have many children and not just one. Therefore, mother.children would be availabe, instead of just child.mother of the previous example.
3. A many to many relationship is a whole new different thing. To hold many to many information, you always need an intermediate table. The reason is the DRY (don’t repeat yourself) principle. If we just used a students and a teachers table, we would need to repeat a student id as well as a teacher id all the time. That leads to very bad database design. Instead, we need an intermediate table, say teachers_students, so that we now need to create :
students(id, name, .. )
teachers(id, name, .. )
This way, you clearly see that a student and a teacher are just two static models, in the sense that they do not define whether a student has a certain teacher and vice versa. This information is hold by teachers_students. Now, in Rails, there are two ways to declare this association. The has_and_belongs_to_many declaration and the has_many => through declaration. But, what are really the differences ?
Has_and_belongs_to_many or has_many => through ?
If you ask me, i would always prefer the second method, mainly because of habit. However, it would be a good idea to know the differences, in case you are not sure of which one to use. Has_and_belongs_to_many creates a many to many association for you automatically, as long as you create the intermediate model. It’s important to notice that the intermediate model has to have the name of the two join tables in alphabetical order. That is, if you have students and teachers, the intermediate table has to be students_teachers (or you can specify the name explicitly). This is, once more, a convention used by Rails.
Has_many => through was the first Rails way to do a many to many association and i like it better due to the fact that more information is revealed through such a declaration. What you would like to say in this example is that a student can have many teachers and a teacher can have many students. Thus, how about :
teachers have_many :students
students have_many :teachers
After all, this is what we want to do. However, there is a problem here. Rails is not able to find the information needed to understand what the students of a teacher are, and vice versa. The reason is that this information is contained in the intermediate table. Therefore, as in has_and_belongs_to_many, we need to specify a third table. Since there is no convention here, let’s name this table classroom_members, instead of teachers_students (the attributes remain the same).
Our declarations now take their final form :
teachers have_many :students, :through => classroom_members
students have_many :teachers, :through => classroom_members
Now, Rails has everything. If we do a “teacher.students”, Rails will search the intermediate table, find the teacher students and return a related object.
Messing With :Through
I understand that through can be a bit confusing. Sometimes, you think : “Do I really need through here” ? The general golden to rule to make out whether you need through or not, is to think of what information you need, in order to execute an association. Let’s think of an example to illustrate that.
We need to create a new model, say Classroom, which defines some information about the classroom where teachers and students exist. If we needed to get all the students in a classroom, that a certain teacher handles, we would like to be able to do something like :
classroom.students — the classroom model is like classroom(id, teacher_id, … )
But the classroom mode only has information about teachers and not students. Luckily, this information that binds a teacher to a student is present to the classroom_members table. This is where through applies smoothly here :
classroom has_many :students, :through => classroom_members
Do you see the convenience here ? The teacher_id that is present in the classroom, is able to get us student information through another table, classroom_members. Hope that makes it a bit more clear to you.