Basic Reads

Here’s the full code for a JSONAPI endpoint that supports sorting, pagination, sparse fieldsets and a JSONAPI-compliant response:

# app/models/post.rb
class Post < ApplicationRecord
end
# config/routes.rb
scope path: '/api' do
  scope path: '/v1' do
    resources :posts, only: [:index]
  end
end
# app/serializers/serializable_post.rb
class SerializablePost < JSONAPI::Serializable::Resource
  type :posts

  attribute :title
  attribute :description
  attribute :body
end
# app/resources/post_resource.rb
class PostResource < ApplicationResource
  type :posts
  model Post
end
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  jsonapi resource: PostResource

  def index
    posts = Post.all
    render_jsonapi(posts)
  end
end

Let’s walk through each of these files:

  • app/models/post.rb
    • Our Model. As Martin Fowler puts it, “An object model of the domain that incorporates both behavior and data.”. In this case we’re using ActiveRecord, though other model patterns can be used. This is the M of MVC.
  • config/routes.rb
  • app/serializers/serializable_post.rb
    • Given a Model, how do we want to represent that model as JSON? We might want to avoid exposing certain attributes, normalize values, or compute something specific to the view. This is the V of MVC.
    • We use the excellent jsonapi-rb library for serialization. If you’re familiar with active_model_serializers, this code will look very familiar.
  • app/resources/post_resource.rb
  • app/controllers/posts_controller.rb
    • This is a typical Rails Controller, the C of MVC.
    • We’ve added jsonapi resource: PostResource to tell our controller to use query and persistence logic defined in our Resource.

All of this leads up to the all-important render_jsonapi method.

render_jsonapi

View the YARD documentation

This method does two things: builds and resolves the “base scope”, and passes relevant options to the serialization layer.

In other words, this lower-level code would be the equivalent:

scope = jsonapi_scope(Post.all) # build up the scope
posts = scope.resolve # fire the query
render json: posts,
  fields: params[:fields].split(','),
  bunch: 'of',
  other: 'options'
  • We’ve started with a base scope - Post.all - and passed it into our Resource, which will modify the scope based on incoming parameters.
  • We’ve passed a number of boilerplate options to the underlying jsonapi-rb serialization library.

There are times we want to manually build and resolve the scope prior to calling render_jsonapi. The show action is one example.

The #show action

Our #show action fetches one specific post by ID, rather than a list of posts. To accomodate this, we manually build and resolve the scope instead of applying the default logic in #render_jsonapi:

scope = jsonapi_scope(Post.where(id: params[:id]))
post = scope.resolve.first
render_jsonapi(post, scope: false)

Note the scope: false option - we’ve already resolved our models, so we tell render_jsonapi not to run the scoping logic again.

View the YARD documentation for #jsonapi_scope

It’s a common convention in Rails to return a 404 response code from the show action when a record is not found. Typically you’d raise and rescue ActiveRecord::RecordNotFound…but we want to be agnostic to the database. Instead:

raise JsonapiCompliable::Errors::RecordNotFound unless post
# app/controllers/application_controller.rb
rescue_exception JsonapiCompliable::Errors::RecordNotFound,
  status: 404

We’re throwing an exception, and using our error handling library to customize the status code when that particular error is thrown.