Reputation: 41
I have two classes called classrooms and students. They have a many-to-many relationship because classrooms can have many students, but also students can belong to many classrooms. When I make an api call to get a partular classroom, I would also like to grab all the students belonging to that classroom (through the StudentClassroom join table) and add them to the Classroom ActiveRecord object that I am returing from the api.
If I pause execution with a binding.pry I before returning the response in the controller, @Classroom.students will return an array of student records as expected. However, when @Classroom gets sent to the front end it gets stripped of the students array. What am I doing wrong?
Classroom class
class Classroom < ApplicationRecord
has_many :student_classrooms
has_many :students, through: :student_classrooms
def get_classroom_students(classroom_id)
StudentClassroom.where(classroom_id: classroom_id)
end
end
Controller
def show
students = @Classroom.get_classroom_students(@Classroom.id)
# somehow add students to @Classroom ActiveRecord object
render json: @Classroom
end
Upvotes: 0
Views: 114
Reputation: 3019
When you call render
no magic happens: before being sent in the response body your classroom object gets serialized.
Default json serialization for the AR models is roughly speaking a hash of attributes - no associations, no instance methods etc. You can workaround this behavior via redefining as_json
for the model as another answer suggests, but this is generally a bad idea - you might (and most probably will) need different serialization for different endpoints and/or scenarios, so having just one hardcoded in the model will force you to add more and more hacking.
The way better idea is to decouple serialization from the business logic - this is where serializers come into play. Take a look at https://github.com/jsonapi-serializer/jsonapi-serializer for example - this is what you need to solve the task in a clean and maintainable way.
Just define a serializer
class ClassroomSerializer
include JSONAPI::Serializer
attributes <attributes you would like to expose>
has_many :students
end
(just a draft - refer to the documentation to unleash all the power :)) and you're good to go with something as simple as just:
def show
render json: ClassroomSerializer.new(@classroom).serializable_hash
end
Upvotes: 0
Reputation: 6418
This would never return students to the front end. When you add binding.pry
and test that on the controller you still have the ActiveRecord
object which has the students
method defined but when you pass it to the front end you convert it to JSON:
render json: @Classroom
So that JSON doesn't have the student array. To fix this you could try this on the show action:
def show
render json: @Classroom.as_json(include: students)
end
This will include students
in the JSON you are passing to the front end.
In case you want to make this behavior default, i.e., whenever the Classroom
object is converted to JSON then include the students
you can add as_json
method on Classroom
class like this:
def as_json(options = {})
super({ include: :students }.merge(options))
end
Now, whenever the Classroom
object is converted to JSON it would include the students. Examples:
# this will automatically include the students in your controller action
render json: @Classroom
# this will also have the students included
Classroom.last.to_json
Upvotes: 0