My Journey Building a Rails 5 API
Sat, Dec 30, 2017Notes & thoughts on building my first API with Rails 5.
What am I making?
This app will be used by developers who write scripts that automate repetitive tasks in video games.
Developers will be able to create a profile for their scripts, and use the API to record the runtime and type/number of tasks automated.
ex: logs chopped, monsters killed, trees cut, fish caught, etc.
The data recorded could be used for:
- Comparing profitability/runtime/number of tasks automated between scripts
- As an enduser:
- I can decide on a script based on these stats
- I can compare my usage stats to that of others
- I can decide on a script based on these stats
- Ex: As a scripter:
- I can use the stats for marketing and as a benchmark to compare to others
- I can use the stats for marketing and as a benchmark to compare to others
- As an enduser:
- Can act as a high-score system between individual users and scripts
Getting Started
Generate a Rails API only app using PostgreSQL since I’m hosting on Heroku.
$ rails new rsscriptstats --api --database=postgresqlSetup RSpec
Setup RSpec before models to utilize its auto-generated tests.
group :development, :test do
gem 'rspec-rails', '~> 3.6'
end$ bundle install
$ rails generate rspec:installSetup up Active Record
Models & Associations
Generate necessary models:
$ rails g devise_token_auth:install User auth
$ rails g scaffold Script name:string skill:string bot_for:string game_for:string
$ rails g scaffold Commit runtime:integer
$ rails g scaffold Stat task:string amount:floatNote: The User model was generated using devise_token_auth gem.
I learned:
You can set associations & delete scaffolds using the scaffold in command line.
rails d scaffold Stat task:string amount:floatSet model associations
The database is setup as such:

Here are some of the database associations and test code:
#/app/models/script.rb
class Script < ApplicationRecord
has_many :commits
end
#/app/models/commit.rb
class Commit < ApplicationRecord
belongs_to :script
end
#/db/migrate/20171231022832_create_commits.rb
class CreateCommits < ActiveRecord::Migration[5.1]
def change
create_table :commits do |t|
t.belongs_to :script, index: true #Add this line
t.string :task
t.float :amount
t.timestamps
end
end
endSeeding
test_script = Script.create(name: "YWoodcutter",
skill: "Woodcutting",
bot_for: "TRiBot",
game_for: "Oldschool Runescape 07")
test_commit_1 = Commit.new(task: "Logs Cut",
amount: "5")
test_commit_1 = Commit.new(task: "Oak Cut",
amount: "10")
test_script.commits << test_commit_1
test_script.commits << test_commit_2Versioning

I came across multiple ways to handle versioning, research to see which fits your need best.
Different strategies explained here:
Versioning a Rails API by Chris
I decided on URL namespaceing since it was covered the most thoroughly online, and was mentioned in the Infinifum Rails Handbook.
Namespacing & Routing
Organizing my filestructure to better accommodate for versioned API.

You can namespace controllers by using the module keyword:
How to Namespace Controllers in Rails
module Api::V1
class CommitsController < ApplicationController
def index
@commits = Commit.all
render json: @commits
end
...
end
endDefine new route:
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :commits
end
end
endWill also need to update the location parameter path in the controller to reflect the new route.
Explained in more detailed on this Stack Overflow answer
render json: @commit, status: :created, location: api_v1_commit_url(@commit)http://localhost:3000/api/v1/commits/ will now hit your API!
Note:
I had to make sure to permit necessary parameters in the controller before successfully cURL POSTing to the endpoint.
Adding Authentication
I chose to use the trusty devise_token_auth gem.
Add devise_token_auth to gemfile
gem 'devise_token_auth'Install dependencies
$ bundleGenerate a user model
$ rails g devise_token_auth:install User authIf it’s an API only app, disable Devise flash messages in config/initializers/devise.rb
Devise.setup do |config|
config.navigational_formats = [ :json ]
endMigrate
$ rails db:migrateAccepted Nested Attributes
#rsscriptstats/app/models/commit.rb
Accepted nested parameters in model.
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
class Commit < ApplicationRecord
has_many :stats
accepts_nested_attributes_for :stats
endAllow the nested resources’ params in controller.
def commit_params
params.require(:commit).permit(:task, :runtime, :script_id, :user_id, stats_attributes: [:task, :amount, :commit_id])
endAnd here’s where I stopped writing…
To be continued…
Listed below are a ton of amazing Rails API guides & resources that greatly assisted during this process.
- REST API Versioning - RailsCast
- Building the perfect Rails 5 API - Versioning Your API
- Pros/Cons of storing serialized hash vs. key/value database object in ActiveRecord?
- When Should You Store SQL Serialized Data in the Database?
- API Tutorial & Checklist
- Why foreign keys should always be indexed
- Build a simple Rails API server + Auth0 JWT authentication + React from scratch in 30 minutes (or less)
- Services I wish I Learned as a new Rails Developer
- JSONAPI-Rails Gem
- Friendly URLs for APIs:
- Database Design
- Authentication:
- Devise Token Auth
- http://www.dailysmarty.com/posts/how-to-use-jwt-authentication-in-a-rails-5-api-app
- https://sourcey.com/building-the-prefect-rails-5-api-only-app/#authenticating-your-api
- https://scotch.io/tutorials/build-a-restful-json-api-with-rails-5-part-two
- https://paweljw.github.io/2017/07/rails-5.1-api-with-vue.js-frontend-part-4-authentication-and-authorization/
- Devise Token Auth
- Jaavascript Web Tokens // Gems:
- Problems I ran into and their solutions:
- rails api “user”: [ “must exist” ]
Solution: Put user migration before others, change date or other method - How to create an association between devise user model and new model in rails?
- Passing Post request through postman for has_many association in rails
- Rails 4 upgrade JSON::ParseError for old sessions
- rails api “user”: [ “must exist” ]
- Factorygirl & RSpec:
- https://semaphoreci.com/community/tutorials/how-to-test-rails-models-with-rspec
- My Q: https://stackoverflow.com/questions/48499505/devise-factorybot-authentication-for-rspec-post-request
- https://blog.pardner.com/2012/10/how-to-specify-traits-for-model-associations-in-factorygirl/
- https://stackoverflow.com/questions/24570281/what-to-add-to-rspec-valid-attributes-when-it-is-validating-related-models
- https://devhints.io/factory_bot
- https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md
- https://semaphoreci.com/community/tutorials/how-to-test-rails-models-with-rspec
- API To Full Rails App:
- Removing Devise Token Authentication:
- Ruby: how to uninstall Devise?
rsscriptstats [remove-devise-token-auth] :> rails destroy devise_token_auth:install User Running via Spring preloader in process 76916 remove config/initializers/devise_token_auth.rb remove db/migrate/20171230080350_devise_token_auth_create_users.rb skipped Concern is already included in the application controller. skipped Routes already exist for User at auth
- Ruby: how to uninstall Devise?
- Testing/RSpec:
- How To: Test controllers with Rails (and RSpec)
- Rails Testing Antipatterns: Fixtures and Factories
- Authentication with Devise in Rspec tests
- RSpec Model Testing Template
- How to Test Rails Models with RSpec
- Testing, Covering and Documenting Ruby on Rails APIs
- Better Rails 5 API Controller Tests with RSpec Shared Examples
- How To: Test controllers with Rails (and RSpec)