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:
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: