Many-to-Many Association


A common question with Rails is how exactly to create a many-to-many relationship. For example, say our application is some sort of online-course management system. Every student is enrolled in multiple courses, and every course has many students enrolled in it. This is a many-to-many relationship.

How do we model this in Rails? We might try something like this:

class Student < ActiveRecord::Base
has_and_belongs_to_many :courses
#...
end

class Course < ActiveRecord::Base
has_and_belongs_to_many :courses
end

Student- and course-specific data aside, all you need is a join table called courses_students that has a course_id and a student_id column (both, together, form the composite key); Rails does the rest. Assuming you have sufficient data populated, you can write code like:

ahmed = Student.find(:first, :first_name => "Ahmed")
<%= #{ahmed.first_name} #{ahmed.last_name} is enrolled in #{ahmed.courses.count} courses." %>

Similarly, you can also write code like:
databases = Course.find(:first, :name => "Introduction to Databases")
<%= "Students enrolled in #{databases.name}:" %>
database.students.each do |s|
<%= s.first_name + " " + s.last_name %>
end

This, of course, prints out all the students in the databases course (by name). Rails knows the class names, and assumes you’ve made the table name courses_students (notice the names are alphabetical). It connects the dots, and voila!

But what if you wanted a little more information about the relationship? You want to keep track of students paying for their courses, and the date of their enrollments.

At this point, the current structure becomes insufficient; what we really have is a third class of Enrollments that bridge these two classes. This is covered in our Many-to-Many Recursive Relationship article; but we’ll skim over it quickly.

You need to create your new model class called Enrollment. In the migration/table, add any fields you like, such as payment_date and enrollment_date. In this class, you specify the relationship of courses to students, like so:

class Enrollment < ActiveRecord::Base
belongs_to :student, :class_name => "Student"
belongs_to :course, :class_name => "Course"
end

This code specifies that the Enrollment table has a student_id and course_id composite primary key (the same as above).

The final two steps are to add to your Course class:

has_many :students, :class_name => "Student"

And to your Student class:

has_many :courses, :class_name => "Course"

That’s it! You can manipulate the students and courses the same way as with the previous method; but in addition, you can query Enrollment objects for additional information about when students enrolled in classes and if they paid.

How do you enroll a student in a course? Like so:
course = Course.find(:first, ...)
student = Student.find(:first, ...)
e = Enrollment.new
e.course = course
e.student = student

What about interesting queries, like all students who haven’t paid one or more courses?

e = Enrollment.find(:all, :conditions => "payment_date = NULL")
e.each do |enrollment|
enrollment.students.each do |s|
puts #{s.first_name} #{s.last_name} hasn't paid!"
end
end

And that’s really all there is to it! If there are any questions/comments, post them in the comments, or on the forums.

Tags: , ,     Posted in Development

Rate this article:
1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Further Reading

Leave a Reply