To go over rendering JSON in a Rails API, I will use an example.
Example:
Models: doctor, patient, appointment
Relationships:
- Doctor has many patients
- Doctor has many appointments, through patients
- Patient has many doctors
- Patient has many appointments, through doctors
- Appointment belongs to a doctor and belongs to a patient
# app/controllers/appointments_controller.rb
def index
appointments = Appointment.all
render json: appointments.to_json
end
def show
appointment = Appointment.find_by(id: params[:id])
render json: appointment.to_json
end
In this example, we have all of Appointment
model's instances / objects assigned to the local variable, appointments. By using render json:
, we are converting all the model instances into JSON. to_json
method can add tacked on, but it is optional, as it will be called implicitly, thanks to Rails doing work behind the scenes.
# config/routes.rb
Rails.application.routes.draw do
get '/appointments' => 'appointments#index'
get '/appointments' => 'appointments#index'
end
Once you have the routes, controller, and models set up, you can start the Rails server to view your appointments.
# '/appointments/'
[
{
id: 1,
doctor_id: 1,
patient_id: 1
},
{
id: 2,
doctor_id: 2,
patient_id: 2
}
]
Adding relationships
So far we can only see the appointments information, which is a bunch of ids. What if we wanted to see an appointment's doctor and patient information? We can add options, such as include
.
# app/controllers/appointments_controller.rb
def index
appointments = Appointment.all
render json: appointments.to_json(include: [:doctor, :patient])
end
def show
appointment = Appointment.find_by(id: params[:id])
render json: appointment.to_json(include: [:doctor, :patient])
end
[{
id: 1,
doctor_id: 1,
patient_id: 1
doctor: {
id: 1,
name: "Bob",
created_at: "2020-04-26T21:56:50.874Z",
updated_at: "2020-04-26T21:56:50.874Z"
},
patient: {
id: 1,
name: "Fred",
created_at: "2020-04-26T21:56:50.924Z",
updated_at: "2020-04-26T21:56:50.924Z"
}
}]
We now have both the doctor's and patient's information, but what if we don't want to show certain attributes, like created_at
and updated_at
?
We could use the option, only
:
# app/controllers/appointments_controller.rb
def show
appointment = Appointment.find_by(id: params[:id])
render json: appointment.to_json(
:include => {
:doctor => {only: [:name]},
:patient => {only: [:name]}
})
end
However, it is best practice to try to keep most of our logic out of our controllers. So, we can move this logic of arranging our JSON data the way we want it to a serializer class.
What is serialization?
Serialization is the process of translating data structures or object state into a format that can be stored or transmitted and reconstructed later (Wikipedia).
What is JSON serialization?
JSON is a data format that encodes objects in a string. JSON serialization is the process of transforming an object, in our case, a hash because we are writing Ruby, into that JSON string.
Generate serializer classes using Fast JSON API
Fast JSON API is a gem you can install in your project. It is one of many gems that you can use. Fast JSON API gives us a new rails generator, serializer
, which allows you to quickly create a serializer class. You will need a serializer class for each model, for which you have data you want to serialize.
Running rails g serializer Appointment
will create a serializers folder within app folder and then create a file called appointment_serializer.rb. You will need to add the relationships to AppointmentSerializer
, just like you would in the models files.
# app/serializers/appointment_serializer.rb
class AppointmentSerializer
include FastJsonapi::ObjectSerializer
belongs_to :doctor
belongs_to :patient
end
Do the same thing for doctor and patient serializer. We need a serializer for these models as well because we want to specify the attributes we want to show.
# app/serializers/doctor_serializer.rb
class DoctorSerializer
include FastJsonapi::ObjectSerializer
attributes :name
end
# app/serializers/patient_serializer.rb
class PatientSerializer
include FastJsonapi::ObjectSerializer
attributes :name
end
Then, in the controller:
def show
appointment = Appointment.find(params[:id])
options = {
include: [:doctor, :patient]
}
render json: AppointmentSerializer.new(appointment, options)
end
Notice AppointmentSerializer.new()
. In order to start using the serializer, we have to create a new instance of the AppointmentSerializer
class and then pass in the variable to turn it into JSON. We will also pass in the options parameter to the serializer to include the doctor and patient objects and their attributes. We used this for the show action, but this can be used in any of the controller actions.
# RESULT:
{
"data": {
"id": "2",
"type": "appointment"
},
"relationships": {
"doctor": {
"data": {
"id": "2",
"type": "doctor"
}
},
"patient": {
"data": {
"id": "2",
"type": "patient"
}
}
}
},
"included": [{
"id": "2",
"type": "doctor",
"attributes": {
"name": "Bob"
}
},
{
"id": "2",
"type": "patient",
"attributes": {
"name": "Fred"
}
}
]
}
This is the way Fast JSON API structures the data. Other serializer gems will structure data differently.
Thanks for reading!
Top comments (0)