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.

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.)

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

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.

If you're going to be following along, have the following on your system:

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.


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

First, the routes:

# config/routes.rb
Rails.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
private
def 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.

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:

Quotes. Data. Models.

alexa anderson

About the Author

Alexa Anderson is an engineer and evangelist with a penchant for making products geared for progress and achievement. When not writing software, Alexa spends her time curating content and sharing her discoveries.