Skip to content

Mapping JSON API spec / schema to AMS [ci skip] #1301

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 9, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions docs/jsonapi/schema.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
[![JSON API 1.0](https://img.shields.io/badge/JSON%20API-1.0-lightgrey.svg)](http://jsonapi.org/)

## JSON API Requests

- [Query Parameters Spec](http://jsonapi.org/format/#query-parameters)

Headers:

- Request: `Accept: application/vnd.api+json`
- Response: `Content-Type: application/vnd.api+json`

### [Fetching Data](http://jsonapi.org/format/#fetching)

A server MUST support fetching resource data for every URL provided as:

- a `self` link as part of the top-level links object
- a `self` link as part of a resource-level links object
- a `related` link as part of a relationship-level links object

Example supported requests

- Individual resource or collection
- GET /articles
- GET /articles/1
- GET /articles/1/author
- Relationships
- GET /articles/1/relationships/comments
- GET /articles/1/relationships/author
- Optional: [Inclusion of related resources](http://jsonapi.org/format/#fetching-includes) `ActiveModel::Serializer::IncludeTree`
- GET /articles/1?`include`=comments
- GET /articles/1?`include`=comments.author
- GET /articles/1?`include`=author,comments.author
- GET /articles/1/relationships/comments?`include`=comments.author
- Optional: [Sparse Fieldsets](http://jsonapi.org/format/#fetching-sparse-fieldsets) `ActiveModel::Serializer::Fieldset`
- GET /articles?`include`=author&`fields`[articles]=title,body&`fields`[people]=name
- Optional: [Sorting](http://jsonapi.org/format/#fetching-sorting)
- GET /people?`sort`=age
- GET /people?`sort`=age,author.name
- GET /articles?`sort`=-created,title
- Optional: [Pagination](http://jsonapi.org/format/#fetching-pagination)
- GET /articles?`page`[number]=3&`page`[size]=1
- Optional: [Filtering](http://jsonapi.org/format/#fetching-filtering)
- GET /comments?`filter`[post]=1
- GET /comments?`filter`[post]=1,2
- GET /comments?`filter`[post]=1,2

### [CRUD Actions](http://jsonapi.org/format/#crud)

### [Asynchronous Processing](http://jsonapi.org/recommendations/#asynchronous-processing)

### [Bulk Operations Extension](http://jsonapi.org/extensions/bulk/)

## JSON API Document Schema

| JSON API object | JSON API properties | Required | ActiveModelSerializers representation |
|-----------------------|----------------------------------------------------------------------------------------------------|----------|---------------------------------------|
| schema | oneOf (success, failure, info) | |
| success | data, included, meta, links, jsonapi | | AM::SerializableResource
| success.meta | meta | | AM::S::Adapter::Base#meta
| success.included | UniqueArray(resource) | | AM::S::Adapter::JsonApi#serializable_hash_for_collection
| success.data | data | |
| success.links | allOf (links, pagination) | | AM::S::Adapter::JsonApi#links_for
| success.jsonapi | jsonapi | |
| failure | errors, meta, jsonapi | errors |
| failure.errors | UniqueArray(error) | | #1004
| meta | Object | |
| data | oneOf (resource, UniqueArray(resource)) | | AM::S::Adapter::JsonApi#serializable_hash_for_collection,#serializable_hash_for_single_resource
| resource | String(type), String(id),<br>attributes, relationships,<br>links, meta | type, id | AM::S::Adapter::JsonApi#primary_data_for
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the JSON API being represented by ActiveModel::Serializer is one of my larger motivations for this PR. The naming of the library as ActiveModelSerializers and its primary namespace being ActiveModel::Serializer and its resource wrapper being a class of the same name causes no end of confusion.

I'd like to at the least move towards naming ActiveModel::Serializer as ActiveModelSerializers::Resource, so that its naming is less confusing, more precise, and a better namespace. I know it's a big historical change, so it would be backwards compatible, deprecated for now.

I generally also want to revisit what we mean to Serializer (Resource?) vs. Adapter and what interfaces we'd like for those.

Also, consider how versioned resources might look ref: #1290 (comment)

# lib/active_model_serializers/v08/serializer.rb
# lib/active_model_serializers/v08/collection_serializer.rb
# lib/active_model_serializers/v08/adapter.rb
module ActiveModelSerializers::V08
  # Serializer might not need anything, but good to have defined
  Serializer = ActiveModel::Serializer
  CollectionSerializer = ActiveModel::Serializer::CollectionSerializer # well, it's Array now, but hopefully we'll get that renaming pr soon
  class Adapter < ActiveModel::Serializer::Adapter::Base
    # whatever methods you need
  end
end

And test like the other serializers and adapters

adapter = ActiveModelSerializers::V08::Adapter
serializer = ActiveModelSerializers::V08::CollectionSerializer
each_serializer = ActiveModelSerializers::V08::Serializer
collection_resource = Post.all
expected_response = [{ something }]
assert ActiveModel::SerializableResource.new(collection_resource, serializer: serializer, each_serializer: each_serializer, adapter: adapter).as_json, expected_response

# and

adapter = ActiveModelSerializers::V08::Adapter
serializer = ActiveModelSerializers::V08::Serializer
resource = Post.first
expected_response = { something }
assert ActiveModel::SerializableResource.new(collection_resource, serializer: serializer, adapter: adapter).as_json, expected_response

refs:

Keeping a well-defined interface, e.g. grape support: #1258 and serialization_context #1289

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added as a comment in #1298 (comment)

| links | Uri(self), Link(related) | | #1028, #1246, #1282
| link | oneOf (linkString, linkObject) | |
| link.linkString | Uri | |
| link.linkObject | Uri(href), meta | href |
| attributes | patternProperties(<br>`"^(?!relationships$|links$)\\w[-\\w_]*$"`),<br>any valid JSON | | AM::Serializer#attributes, AM::S::Adapter::JsonApi#resource_object_for
| relationships | patternProperties(<br>`"^\\w[-\\w_]*$"`);<br>links, relationships.data, meta | | AM::S::Adapter::JsonApi#relationships_for
| relationships.data | oneOf (relationshipToOne, relationshipToMany) | | AM::S::Adapter::JsonApi#resource_identifier_for
| relationshipToOne | anyOf(empty, linkage) | |
| relationshipToMany | UniqueArray(linkage) | |
| empty | null | |
| linkage | String(type), String(id), meta | type, id | AM::S::Adapter::JsonApi#primary_data_for
| pagination | pageObject(first), pageObject(last),<br>pageObject(prev), pageObject(next) | | AM::S::Adapter::JsonApi::PaginationLinks#serializable_hash
| pagination.pageObject | oneOf(Uri, null) | |
| jsonapi | String(version), meta | | AM::S::Adapter::JsonApi::ApiObjects::JsonApi
| error | String(id), links, String(status),<br>String(code), String(title),<br>String(detail), error.source, meta | |
| error.source | String(pointer), String(parameter) | |
| pointer | [JSON Pointer RFC6901](https://tools.ietf.org/html/rfc6901) | |

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reposting #1298 (comment) here with a few changes, is some thoughts on why we might organize things.

let AMS refer to ActiveModelSerializers, AM::S to ActiveModel::Serializer, and AM::S::A to ActiveModel::Serializer::Adapter for brevity in the below.

Current Desired Notes
require 'active_model_serializers' require 'active_model_serializers'
ActiveModelSerializers ActiveModelSerializers
ActiveModel::SerializableResource ActiveModel::SerializableResource
namespace ActiveModel::Serializer AMS::Current,
ActiveModelSerializers::V08, etc
versioned
class AM::S AMS::Current::Resource is a class that defines the properties and behavior of the data that you present to the user
AMS::Model AMS::Model e.g. ActiveRecord object but a PORO. Maybe should be AMS::Record A record is an instance of a model that contains data loaded from a server. Your application can also create new records and save them back to the server.
AM::S::Lint AMS::Model::Lint
ActiveModel::Serialization AMS::ActionController::Serialization
AM::S::CollectionSerializer AMS::Current::ResourceCollection
namespace AM::S::Adapter AMS::Adapter adapter: knows how to talk to your server. knows how to translate requests from AMS into requests on your server. object that translates requests from AMS/Rails/Grape etc (such as "find the user with an ID of 123") into a requests to a server. let you completely change how your API is implemented without impacting your application code.
class AM::S::A::JsonApi AMS::Adapter::JsonApi,
AMS::Adapter::V08::Json
not versioned
class ActiveModel::Serializer, AM::S::A::Attributes AMS::Formatter maps keys and values to desired format, see https://github.com/orbitjs/orbit.js/blob/master/lib/orbit-common/jsonapi-serializer.js
N/A AMS::Current::Store store is the central repository of records in your application, use the store to retrieve records, as well to create new ones. The store will automatically cache records for you.
N/A AMS::Adapter::JsonApi::Exceptions
AM::S::A::JsonApi::ApiObject::JsonApi AMS::Adapter::JsonApi::ApiObject::
{JsonApi,Links,Link,Resource,Error, etc}? vs.
JsonApiFormat or even schema validation
ActiveModelSerializers.logger ActiveModelSerializers.logger

re: ActiveModelSerializers::Current is just a thought.. perhaps to generate an alternative :)

see


The [http://jsonapi.org/schema](schema/schema.json) makes a nice roadmap.

### Success Document
- [ ] success
- [ ] data: `"$ref": "#/definitions/data"`
- [ ] included: array of unique items of type `"$ref": "#/definitions/resource"`
- [ ] meta: `"$ref": "#/definitions/meta"`
- [ ] links:
- [ ] link: `"$ref": "#/definitions/links"`
- [ ] pagination: ` "$ref": "#/definitions/pagination"`
- [ ] jsonapi: ` "$ref": "#/definitions/jsonapi"`

### Failure Document

- [ ] failure
- [ ] errors: array of unique items of type ` "$ref": "#/definitions/error"`
- [ ] meta: `"$ref": "#/definitions/meta"`
- [ ] jsonapi: `"$ref": "#/definitions/jsonapi"`

### Info Document

- [ ] info
- [ ] meta: `"$ref": "#/definitions/meta"`
- [ ] links: `"$ref": "#/definitions/links"`
- [ ] jsonapi: ` "$ref": "#/definitions/jsonapi"`
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Info document isn't really part of the spec json-api/json-api#440 (comment)


### Definitions

- [ ] definitions:
- [ ] meta
- [ ] data: oneOf (resource, array of unique resources)
- [ ] resource
- [ ] attributes
- [ ] relationships
- [ ] relationshipToOne
- [ ] empty
- [ ] linkage
- [ ] meta
- [ ] relationshipToMany
- [ ] linkage
- [ ] meta
- [ ] links
- [ ] meta
- [ ] links
- [ ] link
- [ ] uri
- [ ] href, meta
- [ ] pagination
- [ ] jsonapi
- [ ] meta
- [ ] error: id, links, status, code, title: detail: source [{pointer, type}, {parameter: {description, type}], meta
Loading