On Positivity and Rails API

I’ve been programming in Rails for years now, but have yet to make a publicly consumable API. Especially not one using the built in Rails API functionality. Today that ends. Read ahead as I document building a pretty simple API to be consumed by some projects I’ve had in mind.

Twitter has its place in the world, but truth be told I’m not a big Tweeter.

A few weeks ago, I looked into making a Twitter bot to disclose my thoughts on a regular basis. The bot would posts a tweet daily at 5pm if I hadn’t already. Everybody would win. My followers would no longer be bored by my unending silence while I could go about my everyday life, not concerned with character limits.

Image for post
Image for post
who am I kidding… I have no followers

I made the bot to read pre-saved tweets from a file, but the thought occurred to me: “What happens when that file runs out of tweets?” The file would require a repeated refreshing of tweets to keep up with the demand of time. Projecting my current rate of tweeting, 0 tweets/day, running out of tweets was guaranteed.

In lieu of a personalized message, I would like to be able to pull from a preconditioned list of things that I could put my personal brand behind. For me, personal growth and confidence is important, so I chose to have the backup tweets be filled with positive quotations.

Little did I know that the positive quotation API world is in shambles. The best one that exists is behind a paywall. The other feasible option required emailing the maintainer for a secret key and waiting a week to get it. (Go ahead. Google it yourself.)

Image for post
Image for post

So, that’s the motivation for the following material. I went out into the Real World (*gasp*), and bought this book:

Image for post
Image for post
Does National Bestseller even mean anything these days?

I’m going to spend a decent amount of time inputing chosen quotes into digital form, but as far as I’m concerned, that’s time better spent than potentially scrolling through the never ending flame war that is Twitter.

The API. It’s Rails.

Ruby 2.3+ — Rails 5+ — Postgres 10+

I have a few requirements for this API. I want to be able to GET a specific quotation. I want to be able to easily PUT a new quotation (I’ll be using POST, though.) I also want the ability to edit the quote if I notice I fat fingered the content or attribution. And as always, the code should be maintainable and extensible. But, that’s it. No bulk GETs. No DELETEs.

Let’s rev it up.

$ rails new --api --database=postgresql quotes

That makes a new rails API project in a directory called quotes. I chose Postgres because it’s familiar; if you’re following along, choose whatever DB you wish.

Let’s make the main event: the quote resource. The book that I will be using to source the quotes provides attribution to a person. I’ll populate my quotes with the same information.

$ rails g model quote content:text attribution:string

I’ll go in and add null: false to both attribute fields in the migration file that was generated by this command (db/migrate/xxx_create_quotes.rb), then run

$ rake db:create && rake db:migrate

Now that resource is storable in a database, let’s set our model to reflect the database restrictions:

# app/models/quote.rb
class Quote < ApplicationRecord
validates :content, :attribution, presence: true
end

Nice.

Image for post
Image for post

Time set up how a user can access these quotes from the outside.

First, the routes:

# config/routes.rbRails.application.routes.draw do
scope :v1 do
resources :quotes, only: [:show, :create]
end
end

For most projects I make, I do an additional scoping to the :api path. Because all foreseeable routes will be api routes, that convention seems a bit redundant here.

Running $ rake routes generates the following:

Prefix Verb URI Pattern                  Controller#Action
quotes POST /api/v1/quotes(.:format) quotes#create
quote GET /api/v1/quotes/:id(.:format) quotes#show

Great. One route to make a new quote and one to retrieve a quote. Now to the controller to code up what happens when a user hits the routes. I want all of my controllers to handle JSON requests and their responses to be in JSON format. Rails API actually strips away the functionality to respond to JSON, so first I add the respondersgem and bundle it in.

# Gemfile
...
gem 'responders'
...
$ bundle install

Next, I make the file, app/controllers/quotes_controller.rb. For now, I’m going to do the basic CRUD implementations of the create and show actions.

# app/controllers/quotes_controller.rb
class QuotesController < ApplicationController
respond_to :json
def show
quote = Quote.find_by(id: params[:id])
if quote
render json: quote
else
render json: { error: 'No quote found' }, status: 404
end
end
def create
quote = Quote.new(quote_params)
if quote.save
render json: quote
else
render json: { error: quote.errors }
end
end
privatedef quote_params
params.require(:quote).permit(
:id,
:content,
:attribution
)
end
end

Start up the server to test out the actions: rails s

I hit the show error path, first: $ curl localhost:3000/v1/quotes/1. This returns a 404 status code with a payload of { error: 'No quote found' }.

Great.

Next the create error path. I use the Postman app for this. I send a POST request without a quote[content] argument. This returns:

error: {
"content": [
"can't be blank"
]
}

Great. Now test the happy path; I provide all of the necessary attributes. This returns:

{
"id": 1,
"content": "test content",
"attribution": "test person",
"created_at": "2017-12-11T02:50:20.761Z",
"updated_at": "2017-12-11T02:50:20.761Z"
}

Great. As expected. Now let’s try to GET the newly created quote; I curl localhost:3000/v1/quotes/1 and this is returned:

{
"id": 1,
"content": "test content",
"attribution": "test person",
"created_at": "2017-12-11T02:50:20.761Z",
"updated_at": "2017-12-11T02:50:20.761Z"
}

Great.

Image for post
Image for post
It’s at least aiiiiiight

Next step is doing a bit of data modeling to ensure the code is maintainable.

To see this project in action, be sure to follow me — @pop_demand — on Twitter.

Stay tuned.

Edit: Next article is fresh off the presses! Check it out:

Written by

Fullstack Developer and Code Yogi

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store