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/posts
GET /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/comments
GET /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/authors
GET /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.