Marcel Degas
Marcel Degas

Reputation: 41

Add an array to an ActiveRecord object during api call

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

Answers (2)

Konstantin Strukov
Konstantin Strukov

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

Deepesh
Deepesh

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

Related Questions