Ruby on Rails Developer Series: Power of Strong APIs using JSON and Postgres Database
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
01 02 03 04 05 06 07 08 09 10 11 12 13 | 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.
1 2 3 4 | 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:
Let’s begin to generate the models:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 | 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:
01 02 03 04 05 06 07 08 09 10 11 12 | 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
:
01 02 03 04 05 06 07 08 09 10 | # 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
1 2 | 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:
1 2 3 4 | * 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
):
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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
) :
1 2 3 4 5 6 | 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:
1 2 3 4 5 6 7 | 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.
1 2 3 4 | 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:
Published on Web Code Geeks with permission by Evan Glazer, partner at our WCG program. See the original article here: Ruby on Rails Developer Series: Power of Strong APIs using JSON and Postgres Database Opinions expressed by Web Code Geeks contributors are their own. |