LET'S START SIMPLE $ rails new rubypets $ cd rubypets $ rails g scaffold pet name:string $ rails g scaffold toy name:string pet:references $ rake db:migrate # set a default route, define relationships, etc ...
LET'S START SIMPLE $ rails new rubypets $ cd rubypets $ rails g scaffold pet name:string $ rails g scaffold toy name:string pet:references $ rake db:migrate # set a default route, define relationships, etc ...
LET'S START SIMPLE $ rails new rubypets $ cd rubypets $ rails g scaffold pet name:string $ rails g scaffold toy name:string pet:references $ rake db:migrate # set a default route, define relationships, etc ...
LET'S START SIMPLE $ rails new rubypets $ cd rubypets $ rails g scaffold pet name:string $ rails g scaffold toy name:string pet:references $ rake db:migrate # set a default route, define relationships, etc ...
LET'S START SIMPLE $ rails new rubypets $ cd rubypets $ rails g scaffold pet name:string $ rails g scaffold toy name:string pet:references $ rake db:migrate # set a default route, define relationships, etc ...
LET'S START SIMPLE $ rails new rubypets $ cd rubypets $ rails g scaffold pet name:string $ rails g scaffold toy name:string pet:references $ rake db:migrate # set a default route, define relationships, etc ...
CONTROLLERS class PetsController < ApplicationController before_action :set_pet, only: [:show, :edit, :update, :destroy] # GET /pets # GET /pets.json def index @pets = Pet.all end # GET /pets/1 # GET /pets/1.json def show end ..... end
LET'S TRY IT OUT # Gemfile # gem 'jbuilder', '~> 1.2' gem 'active_model_serializers' $ bundle install $ rails g serializer pet $ rails g serializer toy
LET'S TRY IT OUT # Gemfile # gem 'jbuilder', '~> 1.2' gem 'active_model_serializers' $ bundle install $ rails g serializer pet $ rails g serializer toy
LET'S TRY IT OUT # Gemfile # gem 'jbuilder', '~> 1.2' gem 'active_model_serializers' $ bundle install $ rails g serializer pet $ rails g serializer toy
CONTROLLERS class PetsController < ApplicationController before_action :set_pet, only: [:show, :edit, :update, :destroy] # GET /pets # GET /pets.json def index render json: Pet.all end # GET /pets/1 # GET /pets/1.json def show render json: @pet end ..... end
CUSTOM FIELDS # app/serializers/base_serializer.rb class PetSerializer < ActiveModel::Serializer attributes :id, :created_at, :updated_at # allow custom inclusion of a single field def include_created_at? if @options.key?(:fields) return @options[:fields][:created_at] end true end end
CUSTOM FIELDS # app/serializers/base_serializer.rb class PetSerializer < ActiveModel::Serializer attributes :id, :created_at, :updated_at # allow custom inclusion of a single field def include_created_at? if @options.key?(:fields) return @options[:fields][:created_at] end true end end "THAT'S BETTER, BUT..."
BASE SERIALIZER # app/serializers/base_serializer.rb class BaseSerializer < ActiveModel::Serializer attributes :id, :created_at, :updated_at end # app/serializers/pet_serializer.rb class PetSerializer < BaseSerializer attributes :name end # app/serializers/toy_serializer.rb class ToySerializer < BaseSerializer attributes :name end
CUSTOM FIELDS + # app/serializers/base_serializer.rb class BaseSerializer < ActiveModel::Serializer attributes :id, :created_at, :updated_at # allow custom inclusion of ANY field # via options passed in from the controller def include?(field) if @options.key?(:fields) return @options[:fields][field] end super(field) end end
ONE-TO-MANY # /app/models/pet.rb class Pet < ActiveRecord::Base has_many :toys end # app/serializers/pet_serializer.rb class PetSerializer < BaseSerializer attributes :name has_many :toys end
ONE-TO-ONE # /app/models/pet.rb class Pet < ActiveRecord::Base has_many :toys belongs_to :person end # app/serializers/pet_serializer.rb class PetSerializer < BaseSerializer attributes :name has_many :toys has_one :person end
BASE SERIALIZER # app/serializers/base_serializer.rb class BaseSerializer < ActiveModel::Serializer # sideload related data by default embed :ids, include: true attributes :id, :created_at, :updated_at end
INCLUDING RELATIONSHIPS /pets?include=toys,people Best guess inclusions (e.g. always include toys with pets) ~ VS. ~ No inclusions by default. Custom inclusions as requested.
EMBEDDING RELATIONSHIPS Always embed any related data (i.e. JBuilder approach) ~ VS. ~ Favor "side-loading" and embed data very selectively (and only when it won't be shared)
SPARSE FIELDSETS Include all fields ~ VS. ~ Allow for custom inclusion of fields: /pets?fields=id,name,age /pets?include=toys,people& pet_fields=id,name,age& people_fields=id,name& toy_fields=name
PAGINATION No limits on resources ~ VS. ~ Provide a default page size, a max page size, and allow custom requests: /pets?page=2&per_page=100 Opinion! JSON API should separate the primary resource from its related resources when "side-loading" in order to clarify issues of primacy like pagination.
PAGINATION, CONT. As per RFC 5988, return a LINK header to allow for navigation: Link: ; rel="next", ; rel="last" Possible values for `rel`: first, last, next, prev
TL;DR A Hypermedia API makes full use of HTTP and a hypermedia content type to be as as navigable as the web itself. REST was a term originally invented by Roy Fielding, but since most "REST" APIs aren't in line with his definition, the term "Hypermedia API" was created. HYPERMEDIA APIS
HYPERMEDIA APIS "Hypermedia designs promote scalability, allow resilience towards future changes, and promote decoupling and encapsulation, with all the benefits those things bring. On the downside, it is not necessarily the most latency-tolerant design, and caches can get stale if you’re not careful. It may not be as efficient on an individual request level as other designs." Steve Klabnik Designing Hypermedia APIs designinghypermediaapis.com
*BASIC* SECURITY RECOMMENDATIONS • Restrict access to particular users (e.g. non-admins) as needed in your controller and serializer layers • HTTPS for all the things
*BASIC* SECURITY RECOMMENDATIONS • Restrict access to particular users (e.g. non-admins) as needed in your controller and serializer layers • HTTPS for all the things • Use token-based security
*BASIC* SECURITY RECOMMENDATIONS • Restrict access to particular users (e.g. non-admins) as needed in your controller and serializer layers • HTTPS for all the things • Use token-based security • If you want to build an OAuth provider, don't go it alone. Use a gem like James Coglan's oauth2-provider: https://github.com/songkick/oauth2-provider