Ruby on Rails API-only with Nested Resources
Tags: rails, ruby, rest, api
Published: 2020-08-06 09:35:28

As part of an interview coding challenge, I was asked to create a simple backend API that returned a list of doctors and their appointments. Before this challenge, I only knew in theory how to create an API-only app in Ruby on Rails and I believe I had followed through a tutorial to create a simple API. This post will cover what I learned from going through the process of reading the Rails documentation and understanding the requirements from the challenge given to me.

First and foremost is to create a Rails API-only app:

    $ bundle exec rails new doctors_api --api

The idea will be to create a Rails API with the following routes at the minimum:

    GET api/v1/doctors

    GET api/v1/doctors/1

    POST api/v1/doctors

    DELETE api/v1/doctors/1

    GET api/v1/doctors/1/appointments

    POST api/v1/doctors/1/appointments

    DELETE api/v1/doctors/1/appointments/1

Security and authentication protocols aside, this should allow any client to create, view or delete a doctor or appointment resource.

## Models

Once the application is ready, next step will be to create a data model and controller for Doctor and Appointment.

    $ bundle exec rails g scaffold Doctor first_name:string last_name:string

    $ bundle exec rails g scaffold Appointment first_name:string last_name:string visit:datetime

    $ bundle exec rails db:create

    $ bundle exec rails db:migrate

In app/models/doctor.rb

    class Doctor < ApplicationRecord
      has_many :appointments, dependent: :destroy
    end

In app/models/appointment.rb

    class Appointment < ApplicationRecord
      belongs_to :doctor
    end

## Controllers

It’s recommended to organize your API controllers, at best we will move them to their own directory. In this example we move the appointments and doctors controller ruby files to an API directory.

    mv app/controllers/*.rb app/controllers/api/v1

The controllers will be doing the brunt of the work of processing the requests using the index, show, create, update, and destroy methods.

## Seeding the database

In db/seeds.rb, we will seed sample data to ensure the REST endpoints work as designed.

    Doctor.create(first_name: 'John', last_name: 'Smith')
    doctor = Doctor.first
    Appointment.create(doctor_id: doctor.id, patient_first_name: 'Jane', patient_last_name: 'Doe', visit: 2.hours.ago)
    Appointment.create(doctor_id: doctor.id, patient_first_name: 'Jack', patient_last_name: 'Doe', visit: 1.day.ago)

bundle exec rails db:seed will insert the records into the database.

## Testing

In a web browser or a REST client, now you test the endpoints

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/i/rux3am116zdh7rfgqill.png" alt="GET api/v1/doctors/1" style="width:100%">
  <figcaption>GET api/v1/doctors/1</figcaption>
</figure>

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/i/zs3jsv2d7paad88tls3l.png" alt="GET api/v1/doctors/1/appointments" style="width:100%">
  <figcaption>GET api/v1/doctors/1/appointments</figcaption>
</figure> 

Creating new records for Doctor and Appointment will accept a JSON payload as a POST request
<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/i/p8escbp1vj0ftvorzynn.png" alt="POST api/v1/doctors" style="width:100%">
  <figcaption>POST api/v1/doctors</figcaption>
</figure> 

## Conclusion

In the end, we achieved our simple goal of a nested REST API. To improve on this setup, in the front-end we can sanitize the inputs before sending requests, insert client information in the HTTP headers to be authenticated in the backend. An example for such an effort is to ensure doctors can only view their own appointments.

Full discretion, while this post has largely been a learning exercise, a full scale REST API will have it’s own set of complexities and design principles that isn’t covered here. Stay tuned for more posts in the future as and when I encounter similar and complex problems in the backend software engineering space.