I have a ruby on rails api which has a many to many relationship between the following models:
class Course < ApplicationRecord
has_many :student_courses
has_many :students, through: :student_courses
end
class Student < ApplicationRecord
has_many :student_courses
has_many :courses, through: :student_courses
end
class StudentCourse < ApplicationRecord
belongs_to :student
belongs_to :courses
end
I want to serve json in the following format:
[
{
"course": "English",
"students": [
"John",
"Sarah"
]
},
{
"course": "Maths",
"students": [
"John",
"Ella",
"Lee"
]
},
{
"course": "Classics",
"students": [
"Una",
"Tom"
]
}
]
At the moment I'm doing this using a loop:
def index
@courses = Course.all
output = []
@courses.each do |course|
course_hash = {
course: course.name,
students: course.students.map { |student| student.name }
}
output << course_hash
end
render json: output.to_json
end
Is there a more efficient way to do this using active record object relational mapping?
In your example, iterating Course.all.each
and then calling course.students
within each iteration will lead to an N+1
problem. Which means there will be one database query to get all courses and the N additional database queries to load the students for each individual course in the list.
To avoid N+1 queries, Ruby on Rails allows to eager load students together with the courses in one or maximum two queries by using includes
Another optimization could be to reduce memory consumption by reusing the already existing array with Enumerable#map
instead of iterating the array with each
and coping the transformed data into a new array.
Putting it together:
def index
courses_with_students = Course.includes(:students).map do |course|
{ course: course.name, students: course.students.map(&:name) }
end
render json: courses_with_students.to_json
end