Register Now

Ruby on Rails Developer Series: Power of Strong APIs using JSON and Postgres Database

Development

Reading Time: 4 minutes

Welcome to the second of four Ruby on Rails Developer Series. In this series, the goal is to outline how to strengthen your API with Postgres, how to dockerize your project and add security layers to mitigate attacks to your application. In this article, we’ll cycle through strengthening our API-based application that uses the JSON API from Active Model Serializer. Then we’ll take advantage of JSON API features.

Let’s Begin

The goal of the project has shifted and we’re now advancing our basic CRUD application into an application that allows for a user to have a to-do list. We need to be able to support showing todo cards in our iOS application – 10 cards at a time specifically. So in this project, we will use paging techniques to optimize the way we fetch JSON data.

Let’s start by creating an Exception Handler to include in our Application Base Controller.

This will help us take care of any record issues we may face and catch/output the response back to the application requesting data.

app/controllers/concerns/exception_handler.rb
module ExceptionHandler
  extend ActiveSupport::Concern

  included do
    rescue_from ActiveRecord::RecordNotFound do |e|
      render json: { message: e.message }, status: 404
    end

    rescue_from ActiveRecord::RecordInvalid do |e|
      render json: { message: e.message }, status: 422
    end
  end
end

Now we can remove: Line 2 - rescue_from ActiveRecord::RecordNotFound, with: :record_not_found and the method record_not_found from our Users Controller.

We will also want to include our Exception Handler Concern in our Base Controller.

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  include ExceptionHandler
end

Now that we have stronger exception handlers for our controllers, we can focus on the models we need to create in order to achieve our project requirements.

The way our logic is going to flow:

Relationship Diagram

Let’s begin to generate the models:

rails g model Todo name:string slug:string priority:string completed:boolean user_id:integer 
  invoke  active_record
  create    db/migrate/20190615221743_create_todos.rb
  create    app/models/todo.rb
  invoke    test_unit
  create      test/models/todo_test.rb
  create      test/fixtures/todos.yml

rails g model Item name:string slug:string priority:string position:integer completed:boolean todo_id:integer` 
  invoke  active_record
  create    db/migrate/20190615221812_create_items.rb
  create    app/models/item.rb
  invoke    test_unit
  create      test/models/item_test.rb
  create      test/fixtures/items.yml

Now let’s migrate our tables into our db.

rake db:migrate

And we need to add the associations to our Models:

class User < ActiveRecord::Base
  has_many :todos
end

class Todo < ActiveRecord::Base
  belongs_to :user
  has_many :items
end

class Item < ActiveRecord::Base
  belongs_to :todo
end

Ok, where are we at now?

Seed some data in our database from ../db/seeds.rb:

# Create our user
user = User.create(first_name: 'Evan', last_name: 'Glazer', email: 'evanowner@live.com')

# Each Todo has 10 Items associated with it
100.times.each do |i|
  todo = Todo.create(name: "Todo #{i}", slug: "todo-#{i}", priority: 'medium', completed: false, user_id: user.id)
  10.times.each do |k|
    Item.create("Item #{k}", slug: "Item-#{k}", priority: 'low', position: j, completed: false, todo_id: todo.id)
  end
end

Then let’s run rake db:seeds to get our data in the db.

Try It – We should be able to now access all our associations properly

user = User.first
user.todos.first.items

Things we will see added and changed for better practices and security measures in the next articles of this series:

* Devise - is a flexible authentication solution for Rails based on Warden.
* Validations to ensure the data we receive and create is consistent.
* FriendlyId is the "Swiss Army bulldozer" of slugging and permalink plugins for Active Record. It lets you create pretty URLs and work with human-friendly strings as if they were numeric ids.
* Controller Testing

Here we’re building our TodoController (\app\controllers\api\v1\todos_controller.rb):

class API::V1::TodosController < ApplicationController

  def index
    todos = Todo.where(user_id: params[:user_id])
    render json: todos, each_serializer: TodoSerializer, status: 200 
  end

  def show
    todo = Todo.find_by!(user_id: params[:user_id], id: params[:id])
    render json: todo, each_serializer: TodoSerializer, status: 200
  end

  def create
    todo = Todo.create!(todo_params)
    render json: todo, each_serializer: TodoSerializer, status: 200
  end

  def destroy
    todo = Todo.find_by!(user_id: params[:user_id, id: params[:id]]).destroy!
    render json: todo, each_serializer: TodoSerializer, status: 200
  end
end

Then theTodoSerializer(\app\serializers\todo_serializer.rb) :

class TodoSerializer < ActiveModelSerializers::Model
  type :todo
  attributes :first_name, :last_name, :email

  has_many :items
end

Time to turn the page! (Just kidding)

Next, let’s implement our paging technique to our controller for fetching 10 records at a time from our users’ todo lists:

Install this gem: https://github.com/mislav/will_paginate

Add to Gemfile `gem 'will_paginate', '~> 3.1.0'`

Then `bundle install`

Now we want to change our index method in our `TodosController` to paginate.

The iOS application will need to keep track of the pages its calling and increment as the user scrolls, etc. Below is setting our per page limit to show 10 Todo lists at a time when we perform a paginated query.

 def index
    todos = Todo.where(user_id: 1).paginate(page: 1, per_page: 10)
    render json: todos, each_serializer: TodoSerializer, status: 200 
  end

Finish Line

In this part of the series, we have implemented the ability for our API to show todo cards in our iOS application – 10 cards at a time, specifically. We strengthened our exception handlers to properly respond to the iOS application. And added paging techniques to work within our JSON API Serializers. Now you could have built something like this:

https://zappy.zapier.com/962219FF-6315-4A39-803F-A8AA28D37561.png)(https://zappy.zapier.com/962219FF-6315-4A39-803F-A8AA28D37561.png)

Subscribe via Email

Over 60,000 people from companies like Netflix, Apple, Spotify and O'Reilly are reading our articles.
Subscribe to receive a weekly newsletter with articles around Continuous Integration, Docker, and software development best practices.



We promise that we won't spam you. You can unsubscribe any time.

Join the Discussion

Leave us some comments on what you think about this topic or if you like to add something.