UNPKG

4.84 kBPlain TextView Raw
1import RESTAdapter from '@ember-data/adapter/rest';
2import AdapterError, {
3 InvalidError,
4 errorsHashToArray,
5} from '@ember-data/adapter/error';
6import { pluralize } from 'ember-inflector';
7import { AnyObject } from 'active-model-adapter';
8import { decamelize, underscore } from '@ember/string';
9// eslint-disable-next-line ember/use-ember-data-rfc-395-imports
10import ModelRegistry from 'ember-data/types/registries/model';
11
12interface ActiveModelPayload {
13 errors: AnyObject;
14}
15
16/**
17 @module ember-data
18*/
19
20/**
21 * The ActiveModelAdapter is a subclass of the RESTAdapter designed to integrate
22 * with a JSON API that uses an underscored naming convention instead of camelCasing.
23 * It has been designed to work out of the box with the
24 * [active\_model\_serializers](http://github.com/rails-api/active_model_serializers)
25 * Ruby gem. This Adapter expects specific settings using ActiveModel::Serializers,
26 * `embed :ids, embed_in_root: true` which sideloads the records.
27 *
28 * This adapter extends the DS.RESTAdapter by making consistent use of the camelization,
29 * decamelization and pluralization methods to normalize the serialized JSON into a
30 * format that is compatible with a conventional Rails backend and Ember Data.
31 *
32 * ## JSON Structure
33 *
34 * The ActiveModelAdapter expects the JSON returned from your server to follow
35 * the REST adapter conventions substituting underscored keys for camelcased ones.
36 *
37 * Unlike the DS.RESTAdapter, async relationship keys must be the singular form
38 * of the relationship name, followed by "_id" for DS.belongsTo relationships,
39 * or "_ids" for DS.hasMany relationships.
40 *
41 * ### Conventional Names
42 *
43 * Attribute names in your JSON payload should be the underscored versions of
44 * the attributes in your Ember.js models.
45 *
46 * For example, if you have a `Person` model:
47 *
48 * ```javascript
49 * export default class FamousPerson extends Model {
50 * @attr() firstName;
51 * @attr() lastName;
52 * @attr() occupation;
53 * }
54 * ```
55 *
56 * The JSON returned should look like this:
57 *
58 * ```json
59 * {
60 * "famous_person": {
61 * "id": 1,
62 * "first_name": "Barack",
63 * "last_name": "Obama",
64 * "occupation": "President"
65 * }
66 * }
67 * ```
68 *
69 * Let's imagine that `Occupation` is just another model:
70 *
71 * ```javascript
72 * export default class Person extends Model {
73 * @attr() firstName;
74 * @attr() lastName;
75 * @belongsTo('occupation') occupation;
76 * }
77 *
78 * export default class Occupation extends Model {
79 * @attr() name;
80 * @attr('number') salary;
81 * @hasMany('person') people;
82 * }
83 * ```
84 *
85 * The JSON needed to avoid extra server calls, should look like this:
86 *
87 * ```json
88 * {
89 * "people": [{
90 * "id": 1,
91 * "first_name": "Barack",
92 * "last_name": "Obama",
93 * "occupation_id": 1
94 * }],
95 *
96 * "occupations": [{
97 * "id": 1,
98 * "name": "President",
99 * "salary": 100000,
100 * "person_ids": [1]
101 * }]
102 * }
103 * ```
104 *
105 * @class ActiveModelAdapter
106 * @constructor
107 * @namespace DS
108 * @extends DS.RESTAdapter
109 */
110export default class ActiveModelAdapter extends RESTAdapter {
111 defaultSerializer = '-active-model';
112 /**
113 * The ActiveModelAdapter overrides the `pathForType` method to build
114 * underscored URLs by decamelizing and pluralizing the object type name.
115 *
116 * ```js
117 * this.pathForType("famousPerson");
118 * //=> "famous_people"
119 * ```
120 *
121 * @method pathForType
122 * @param {String} modelName
123 * @return String
124 */
125 pathForType<K extends keyof ModelRegistry>(modelName: K): string {
126 const decamelized = decamelize(modelName as string);
127 const underscored = underscore(decamelized);
128 return pluralize(underscored);
129 }
130
131 /**
132 * The ActiveModelAdapter overrides the `handleResponse` method
133 * to format errors passed to a DS.InvalidError for all
134 * 422 Unprocessable Entity responses.
135 *
136 * A 422 HTTP response from the server generally implies that the request
137 * was well formed but the API was unable to process it because the
138 * content was not semantically correct or meaningful per the API.
139 *
140 * For more information on 422 HTTP Error code see 11.2 WebDAV RFC 4918
141 * https://tools.ietf.org/html/rfc4918#section-11.2
142 *
143 * @method handleResponse
144 * @param {Number} status
145 * @param {Object} headers
146 * @param {Object} payload
147 * @return {Object | AdapterError} response
148 */
149 handleResponse(
150 status: number,
151 headers: AnyObject,
152 payload: ActiveModelPayload,
153 requestData: AnyObject | AdapterError
154 // eslint-disable-next-line @typescript-eslint/no-explicit-any
155 ): any {
156 if (this.isInvalid(status, headers, payload)) {
157 const errors = errorsHashToArray(payload.errors);
158
159 return new InvalidError(errors);
160 } else {
161 return super.handleResponse(status, headers, payload, requestData);
162 }
163 }
164}