Ruby

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.

Evan Glazer

Evan Glazer is a software engineer and self-starter at Edukate, where he uses Ember and Ruby on Rails and works with natural language processing and machine learning.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button