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