Usage
Below are notes on using the ember-jsonapi-resources addon…
Installation
You will need to remove another dependency that injects a store service.
To consume this addon in an Ember CLI application:
ember install ember-jsonapi-resources
Remove dependency for Ember Data in your ember-cli app:
npm rm ember-data --save-dev
Remove ember-data from both bower.json and package.json then:
bower install
npm install
Resource Generator
Generate a resource (model with associated adapter, serializer and service):
ember generate jsonapi-resource entityName
Use the singular form of the name for your resource (entityName).
The blueprint for a jsonapi-resource does not generate a route.
You can generate a route using the same name or use a different name that
represents the user interface:
ember generate route entityName
The arguments are passed to the jsonapi-model blueprint so that attr,
has-one, and has-many computed properties can be generated.
ember generate jsonapi-resource article title:string version:number author:has-one:user
If you are using the pod file structure in your ember-cli project use the --pod
argument with your generator.
ember generate jsonapi-resource user name:string articles:has-many:articles --pod
For generated code examples, see the tests/dummy/app in this repo.
Store Service
A store service is injected into the routes. This is similar to how
Ember Data uses a store, but the resource
is referenced in the plural form (like your API endpoint).
This is the interface for the store which is a facade
for the service for a specific resource. Basically you call the store methods
and pass in the resource name, e.g. 'posts' which interacts with the service for
your resource.
An example route:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.find('post');
}
});The store object will pluralize the type to lookup the resources' service objects.
Resource Services
Each resource has an associated service that is used by the store.
A resource service is a combination of an adapter, serializer and cache
object. The service is also injected into the resource (model) objects.
The caching plan for a service is simply a mixin that can be easily customized.
To begin with, the resource (model) prototype and the service-cache mixin
work together to provide a basic plan.
The services are "evented" to facilitate close to real time updates.
An attr of the resource is a computed property to the actual attribute in an
attributes hash on the resource (model) instance. Using attr() supports
any type, and an optional type (String) argument can be used to enforce
setting and getting with a specific type. 'string', 'number', 'boolean',
'date', 'object', and 'array' are all valid types for attributes.
When an attr is set and the value has changed an attributeChanged event
is triggered on the resource's service object. By default, the adapter listens
to this event and handles it with a call to updateResource.
The resource adapter's updateResource method sends a PATCH request with
only the data for the changed attributes.
You might want to buffer changes on the resource that is passed into a component
using ember-state-services and ember-buffered-proxy, or you could just
re-define the resource's adapter prototype so that initEvents returns
a noop instead of listening for the attributeChanged event.
Resource (Model)
Here is the blueprint for a resource (model) prototype:
import Ember from 'ember';
import Resource from './resource';
import { attr, toOne, toMany } from 'ember-jsonapi-resources/models/resource';
export default Resource.extend({
type: '<%= resource %>',
service: Ember.inject.service('<%= resource %>'),
/* You can generate properties using arguments to the jsonapi-model blueprint…
title: attr('string'),
published: attr('date'),
tags: attr('array'),
footnotes: attr('object'),
revisions: attr()
version: attr('number'),
"is-approved": attr('boolean'),
author: toOne('author'),
comments: toMany('comments')
*/
});The commented out code includes an example of how to setup the relationships
and define your attributes. attr() can be used for any valid type, or you can
specify a type, e.g. attr('string') or attr('date'). An attribute that is
defined as a 'date' type has a built in transform method to serialize and
deserialize the date value. Typically the JSON value for a Date object is
communicated in ISO format, e.g. "2015-08-25T22:05:37.393Z". The application
serializer has methods for [de]serializing the date values between client and
server. You can add your own transform methods based on the type of the
attribute or based on the name of the attribute, the transform methods based on
the name of the attribute will be called instead of any transform methods based
on the type of the attribute.
The relationships are async using promise proxy objects. So when a template accesses
the resource's relationship a request is made for the relation.
Configuration
You may need configure some paths for calling your API server.
Example config settings: tests/dummy/config/environment.js
var ENV = {
// …
APP: {
API_HOST: '/',
API_HOST_PROXY: 'http://api.pixelhandler.com/',
API_PATH: 'api/v1',
},
contentSecurityPolicy: {
'connect-src': "'self' api.pixelhandler.com localhost:3000",
}
// …
};MODEL_FACTORY_INJECTIONS should be set to true in the app/app.js file.
Also, once you've generated a jsonapi-adapter you can customize the URL.
See this example: tests/dummy/app/adapters/post.js
import ApplicationAdapter from './application';
import config from '../config/environment';
export default ApplicationAdapter.extend({
type: 'post',
url: config.APP.API_PATH + '/posts',
fetchUrl: function(url) {
const proxy = config.APP.API_HOST_PROXY;
const host = config.APP.API_HOST;
if (proxy && host) {
url = url.replace(proxy, host);
}
return url;
}
});The example above also includes a customized method for the url. In the case where
your API server is running on it's own domain and you use a proxy with your nginx
server to access the API server on your same domain at /api then the JSON documents
may have it's own link references to the original server so you can replace the URL
as needed to act as if the API server is running on your same domain.
Authorization
By default credentials stored at localStorage['AuthorizationHeader'] will be used.
If you'd like to change this, for instance to make it work with ember-simple-auth,
there's a configurable mixin available.
Example JSON API 1.0 Document
{
"data": [{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON API paints my bikeshed!"
},
"links": {
"self": "http://example.com/articles/1"
},
"relationships": {
"author": {
"links": {
"self": "http://example.com/articles/1/relationships/author",
"related": "http://example.com/articles/1/author"
},
"data": { "type": "people", "id": "9" }
},
"comments": {
"links": {
"self": "http://example.com/articles/1/relationships/comments",
"related": "http://example.com/articles/1/comments"
},
"data": [
{ "type": "comments", "id": "5" },
{ "type": "comments", "id": "12" }
]
}
}
}]
}For more examples see my API's resources:
GET /api/v1/posts
curl -i -H "Accept: application/vnd.api+json" -H "Content-Type: application/vnd.api+json" -X GET http://api.pixelhandler.com/api/v1/postsGET /api/v1/comments
curl -i -H "Accept: application/vnd.api+json" -H "Content-Type: application/vnd.api+json" -X GET http://api.pixelhandler.com/api/v1/commentsGET /api/v1/authors
curl -i -H "Accept: application/vnd.api+json" -H "Content-Type: application/vnd.api+json" -X GET http://api.pixelhandler.com/api/v1/authorsGET /api/v1/posts with params for sort, fields, pagination
curl -i -H "Accept: application/vnd.api+json" -H "Content-Type: application/vnd.api+json" -X GET "http://api.pixelhandler.com/api/v1/posts?sort=-date&fields%5Bposts%5D=title,date&page%5Boffset%5D=0&page%5Blimit%5D=5"The api.pixelhandler.com server is running the JSONAPI::Resources gem. It follows the JSON API 1.0 spec.