UNPKG

5.86 kBJavaScriptView Raw
1/**
2 @module @ember-data/adapter
3*/
4import { dasherize } from '@ember/string';
5
6import { pluralize } from 'ember-inflector';
7
8import { serializeIntoHash } from './-private';
9import RESTAdapter from './rest';
10
11/**
12 The `JSONAPIAdapter` is the default adapter used by Ember Data. It
13 is responsible for transforming the store's requests into HTTP
14 requests that follow the [JSON API](http://jsonapi.org/format/)
15 format.
16
17 ## JSON API Conventions
18
19 The JSONAPIAdapter uses JSON API conventions for building the URL
20 for a record and selecting the HTTP verb to use with a request. The
21 actions you can take on a record map onto the following URLs in the
22 JSON API adapter:
23
24<table>
25 <tr>
26 <th>
27 Action
28 </th>
29 <th>
30 HTTP Verb
31 </th>
32 <th>
33 URL
34 </th>
35 </tr>
36 <tr>
37 <th>
38 `store.findRecord('post', 123)`
39 </th>
40 <td>
41 GET
42 </td>
43 <td>
44 /posts/123
45 </td>
46 </tr>
47 <tr>
48 <th>
49 `store.findAll('post')`
50 </th>
51 <td>
52 GET
53 </td>
54 <td>
55 /posts
56 </td>
57 </tr>
58 <tr>
59 <th>
60 Update `postRecord.save()`
61 </th>
62 <td>
63 PATCH
64 </td>
65 <td>
66 /posts/123
67 </td>
68 </tr>
69 <tr>
70 <th>
71 Create `store.createRecord('post').save()`
72 </th>
73 <td>
74 POST
75 </td>
76 <td>
77 /posts
78 </td>
79 </tr>
80 <tr>
81 <th>
82 Delete `postRecord.destroyRecord()`
83 </th>
84 <td>
85 DELETE
86 </td>
87 <td>
88 /posts/123
89 </td>
90 </tr>
91</table>
92
93 ## Success and failure
94
95 The JSONAPIAdapter will consider a success any response with a
96 status code of the 2xx family ("Success"), as well as 304 ("Not
97 Modified"). Any other status code will be considered a failure.
98
99 On success, the request promise will be resolved with the full
100 response payload.
101
102 Failed responses with status code 422 ("Unprocessable Entity") will
103 be considered "invalid". The response will be discarded, except for
104 the `errors` key. The request promise will be rejected with a
105 `InvalidError`. This error object will encapsulate the saved
106 `errors` value.
107
108 Any other status codes will be treated as an adapter error. The
109 request promise will be rejected, similarly to the invalid case,
110 but with an instance of `AdapterError` instead.
111
112 ### Endpoint path customization
113
114 Endpoint paths can be prefixed with a `namespace` by setting the
115 namespace property on the adapter:
116
117 ```app/adapters/application.js
118 import JSONAPIAdapter from '@ember-data/adapter/json-api';
119
120 export default JSONAPIAdapter.extend({
121 namespace: 'api/1'
122 });
123 ```
124 Requests for the `person` model would now target `/api/1/people/1`.
125
126 ### Host customization
127
128 An adapter can target other hosts by setting the `host` property.
129
130 ```app/adapters/application.js
131 import JSONAPIAdapter from '@ember-data/adapter/json-api';
132
133 export default JSONAPIAdapter.extend({
134 host: 'https://api.example.com'
135 });
136 ```
137
138 Requests for the `person` model would now target
139 `https://api.example.com/people/1`.
140
141 @since 1.13.0
142 @class JSONAPIAdapter
143 @constructor
144 @extends RESTAdapter
145*/
146const JSONAPIAdapter = RESTAdapter.extend({
147 defaultSerializer: '-json-api',
148
149 _defaultContentType: 'application/vnd.api+json',
150
151 /**
152 @method ajaxOptions
153 @private
154 @param {String} url
155 @param {String} type The request type GET, POST, PUT, DELETE etc.
156 @param {Object} options
157 @return {Object}
158 */
159 ajaxOptions(url, type, options = {}) {
160 let hash = this._super(url, type, options);
161
162 hash.headers['Accept'] = hash.headers['Accept'] || 'application/vnd.api+json';
163
164 return hash;
165 },
166
167 /**
168 By default the JSONAPIAdapter will send each find request coming from a `store.find`
169 or from accessing a relationship separately to the server. If your server supports passing
170 ids as a query string, you can set coalesceFindRequests to true to coalesce all find requests
171 within a single runloop.
172
173 For example, if you have an initial payload of:
174
175 ```javascript
176 {
177 data: {
178 id: 1,
179 type: 'post',
180 relationship: {
181 comments: {
182 data: [
183 { id: 1, type: 'comment' },
184 { id: 2, type: 'comment' }
185 ]
186 }
187 }
188 }
189 }
190 ```
191
192 By default calling `post.get('comments')` will trigger the following requests(assuming the
193 comments haven't been loaded before):
194
195 ```
196 GET /comments/1
197 GET /comments/2
198 ```
199
200 If you set coalesceFindRequests to `true` it will instead trigger the following request:
201
202 ```
203 GET /comments?filter[id]=1,2
204 ```
205
206 Setting coalesceFindRequests to `true` also works for `store.find` requests and `belongsTo`
207 relationships accessed within the same runloop. If you set `coalesceFindRequests: true`
208
209 ```javascript
210 store.findRecord('comment', 1);
211 store.findRecord('comment', 2);
212 ```
213
214 will also send a request to: `GET /comments?filter[id]=1,2`
215
216 Note: Requests coalescing rely on URL building strategy. So if you override `buildURL` in your app
217 `groupRecordsForFindMany` more likely should be overridden as well in order for coalescing to work.
218
219 @property coalesceFindRequests
220 @type {boolean}
221 */
222 coalesceFindRequests: false,
223
224 findMany(store, type, ids, snapshots) {
225 let url = this.buildURL(type.modelName, ids, snapshots, 'findMany');
226 return this.ajax(url, 'GET', { data: { filter: { id: ids.join(',') } } });
227 },
228
229 pathForType(modelName) {
230 let dasherized = dasherize(modelName);
231 return pluralize(dasherized);
232 },
233
234 updateRecord(store, type, snapshot) {
235 const data = serializeIntoHash(store, type, snapshot);
236
237 let url = this.buildURL(type.modelName, snapshot.id, snapshot, 'updateRecord');
238
239 return this.ajax(url, 'PATCH', { data: data });
240 },
241});
242
243export default JSONAPIAdapter;