Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUGFIX release] backport ActiveModelAdapter changes #3564

Merged
merged 1 commit into from
Jul 20, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions packages/activemodel-adapter/lib/system/active-model-adapter.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import {RESTAdapter} from "ember-data/adapters";
import {pluralize} from "ember-inflector";
import Ember from 'ember';
import {
InvalidError,
errorsHashToArray
} from "ember-data/adapters/errors";
} from 'ember-data/adapters/errors';
import RESTAdapter from 'ember-data/adapters/rest-adapter';

import { pluralize } from 'ember-inflector';

const {
decamelize,
underscore
} = Ember.String;

/**
@module ember-data
*/

var decamelize = Ember.String.decamelize;
var underscore = Ember.String.underscore;

/**
The ActiveModelAdapter is a subclass of the RESTAdapter designed to integrate
with a JSON API that uses an underscored naming convention instead of camelCasing.
Expand Down Expand Up @@ -103,7 +107,7 @@ var underscore = Ember.String.underscore;
@extends DS.RESTAdapter
**/

var ActiveModelAdapter = RESTAdapter.extend({
const ActiveModelAdapter = RESTAdapter.extend({
defaultSerializer: '-active-model',
/**
The ActiveModelAdapter overrides the `pathForType` method to build
Expand Down
86 changes: 67 additions & 19 deletions packages/activemodel-adapter/lib/system/active-model-serializer.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { singularize } from "ember-inflector";
import RESTSerializer from "ember-data/serializers/rest-serializer";
import normalizeModelName from "ember-data/system/normalize-model-name";
import Ember from 'ember';
import RESTSerializer from 'ember-data/serializers/rest-serializer';
import normalizeModelName from 'ember-data/system/normalize-model-name';

/**
@module ember-data
*/
*/

var forEach = Ember.ArrayPolyfills.forEach;
var camelize = Ember.String.camelize;
var classify = Ember.String.classify;
var decamelize = Ember.String.decamelize;
var underscore = Ember.String.underscore;
const {
singularize,
classify,
decamelize,
camelize,
underscore
} = Ember.String;

/**
The ActiveModelSerializer is a subclass of the RESTSerializer designed to integrate
Expand Down Expand Up @@ -177,9 +180,7 @@ var ActiveModelSerializer = RESTSerializer.extend({
if (Ember.isNone(belongsTo)) {
json[jsonKey] = null;
} else {
json[jsonKey] = classify(belongsTo.modelName).replace(/(\/)([a-z])/g, function(match, separator, chr) {
return match.toUpperCase();
}).replace('/', '::');
json[jsonKey] = classify(belongsTo.modelName).replace('/', '::');
}
},

Expand Down Expand Up @@ -279,10 +280,7 @@ var ActiveModelSerializer = RESTSerializer.extend({
if (payload && payload.type) {
payload.type = this.modelNameFromPayloadKey(payload.type);
} else if (payload && relationship.kind === "hasMany") {
var self = this;
forEach.call(payload, function(single) {
single.type = self.modelNameFromPayloadKey(single.type);
});
payload.forEach((single) => single.type = this.modelNameFromPayloadKey(single.type));
}
} else {
payloadKey = this.keyForRelationship(key, relationship.kind, "deserialize");
Expand All @@ -298,12 +296,62 @@ var ActiveModelSerializer = RESTSerializer.extend({
}, this);
}
},

extractRelationships: function(modelClass, resourceHash) {
modelClass.eachRelationship(function (key, relationshipMeta) {
var relationshipKey = this.keyForRelationship(key, relationshipMeta.kind, "deserialize");

// prefer the format the AMS gem expects, e.g.:
// relationship: {id: id, type: type}
if (relationshipMeta.options.polymorphic) {
extractPolymorphicRelationships(key, relationshipMeta, resourceHash, relationshipKey);
}
// If the preferred format is not found, use {relationship_name_id, relationship_name_type}
if (resourceHash.hasOwnProperty(relationshipKey) && typeof resourceHash[relationshipKey] !== 'object') {
var polymorphicTypeKey = this.keyForRelationship(key) + '_type';
if (resourceHash[polymorphicTypeKey] && relationshipMeta.options.polymorphic) {
let id = resourceHash[relationshipKey];
let type = resourceHash[polymorphicTypeKey];
delete resourceHash[polymorphicTypeKey];
delete resourceHash[relationshipKey];
resourceHash[relationshipKey] = { id: id, type: type };
}
}
}, this);
return this._super.apply(this, arguments);
},

modelNameFromPayloadKey: function(key) {
var convertedFromRubyModule = camelize(singularize(key)).replace(/(^|\:)([A-Z])/g, function(match, separator, chr) {
return match.toLowerCase();
}).replace('::', '/');
var convertedFromRubyModule = singularize(key.replace('::', '/'));
return normalizeModelName(convertedFromRubyModule);
}
});

function extractPolymorphicRelationships(key, relationshipMeta, resourceHash, relationshipKey) {
let polymorphicKey = decamelize(key);
if (polymorphicKey in resourceHash && typeof resourceHash[polymorphicKey] === 'object') {
if (relationshipMeta.kind === 'belongsTo') {
let hash = resourceHash[polymorphicKey];
let {id, type} = hash;
resourceHash[relationshipKey] = { id, type };
// otherwise hasMany
} else {
let hashes = resourceHash[polymorphicKey];

if (!hashes) {
return;
}

// TODO: replace this with map when ActiveModelAdapter branches for Ember Data 2.0
var array = [];
for (let i = 0, length = hashes.length; i < length; i++) {
let hash = hashes[i];
let {id, type} = hash;
array.push({ id, type });
}
resourceHash[relationshipKey] = array;
}
}
}

export default ActiveModelSerializer;
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
var env, store, adapter, User;

var originalAjax;
const {ActiveModelAdapter} = DS;

module("integration/active_model_adapter_serializer - AMS Adapter and Serializer", {
setup: function() {
Expand All @@ -11,7 +13,7 @@ module("integration/active_model_adapter_serializer - AMS Adapter and Serializer

env = setupStore({
user: User,
adapter: DS.ActiveModelAdapter
adapter: ActiveModelAdapter
});

store = env.store;
Expand All @@ -25,7 +27,7 @@ module("integration/active_model_adapter_serializer - AMS Adapter and Serializer
}
});

test('errors are camelCased and are expected under the `errors` property of the payload', function() {
test('errors are camelCased and are expected under the `errors` property of the payload', function(assert) {
var jqXHR = {
status: 422,
getAllResponseHeaders: function() { return ''; },
Expand All @@ -48,8 +50,8 @@ test('errors are camelCased and are expected under the `errors` property of the
Ember.run(function() {
user.save().then(null, function() {
var errors = user.get('errors');
ok(errors.has('firstName'), "there are errors for the firstName attribute");
deepEqual(errors.errorsFor('firstName').getEach('message'), ['firstName error']);
assert.ok(errors.has('firstName'), "there are errors for the firstName attribute");
assert.deepEqual(errors.errorsFor('firstName').getEach('message'), ['firstName error']);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const {ActiveModelAdapter} = DS;

var env, store, adapter, SuperUser;
var passedUrl, passedVerb, passedHash;
module("integration/active_model_adapter - AMS Adapter", {
Expand All @@ -6,7 +8,7 @@ module("integration/active_model_adapter - AMS Adapter", {

env = setupStore({
superUser: SuperUser,
adapter: DS.ActiveModelAdapter
adapter: ActiveModelAdapter
});

store = env.store;
Expand All @@ -16,11 +18,11 @@ module("integration/active_model_adapter - AMS Adapter", {
}
});

test('buildURL - decamelizes names', function() {
equal(adapter.buildURL('superUser', 1), "/super_users/1");
test('buildURL - decamelizes names', function(assert) {
assert.equal(adapter.buildURL('superUser', 1), "/super_users/1");
});

test('handleResponse - returns invalid error if 422 response', function() {
test('handleResponse - returns invalid error if 422 response', function(assert) {

var jqXHR = {
status: 422,
Expand All @@ -31,17 +33,17 @@ test('handleResponse - returns invalid error if 422 response', function() {

var error = adapter.handleResponse(jqXHR.status, {}, json).errors[0];

equal(error.detail, "can't be blank");
equal(error.source.pointer, "data/attributes/name");
assert.equal(error.detail, "can't be blank");
assert.equal(error.source.pointer, "data/attributes/name");
});

test('handleResponse - returns ajax response if not 422 response', function() {
test('handleResponse - returns ajax response if not 422 response', function(assert) {
var jqXHR = {
status: 500,
responseText: "Something went wrong"
};

var json = adapter.parseErrorResponse(jqXHR.responseText);

ok(adapter.handleResponse(jqXHR.status, {}, json) instanceof DS.AdapterError, 'must be a DS.AdapterError');
assert.ok(adapter.handleResponse(jqXHR.status, {}, json) instanceof DS.AdapterError, 'must be a DS.AdapterError');
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const {ActiveModelSerializer} = DS;

var SuperVillain, EvilMinion, YellowMinion, DoomsdayDevice, MediocreVillain, TestSerializer, env;
var run = Ember.run;

Expand All @@ -22,7 +24,7 @@ module("integration/active_model - AMS-namespaced-model-names (new API)", {
name: DS.attr('string'),
evilMinions: DS.hasMany('evilMinion', { polymorphic: true })
});
TestSerializer = DS.ActiveModelSerializer.extend({
TestSerializer = ActiveModelSerializer.extend({
isNewSerializerAPI: true
});
env = setupStore({
Expand All @@ -49,74 +51,78 @@ module("integration/active_model - AMS-namespaced-model-names (new API)", {
}
});

test("extractPolymorphic hasMany", function() {
var json_hash = {
mediocre_villain: { id: 1, name: "Dr Horrible", evil_minion_ids: [{ type: "EvilMinions::YellowMinion", id: 12 }] },
"evil-minions/yellow-minion": [{ id: 12, name: "Alex", doomsday_device_ids: [1] }]
};
var json;
if (Ember.FEATURES.isEnabled('ds-new-serializer-api')) {

run(function() {
json = env.amsSerializer.normalizeResponse(env.store, MediocreVillain, json_hash, '1', 'findRecord');
});
test("extractPolymorphic hasMany", function(assert) {
var json_hash = {
mediocre_villain: { id: 1, name: "Dr Horrible", evil_minion_ids: [{ type: "EvilMinions::YellowMinion", id: 12 }] },
"evil-minions/yellow-minion": [{ id: 12, name: "Alex", doomsday_device_ids: [1] }]
};
var json;

deepEqual(json, {
"data": {
"id": "1",
"type": "mediocre-villain",
"attributes": {
"name": "Dr Horrible"
},
"relationships": {
"evilMinions": {
"data": [
{ "id": "12", "type": "evil-minions/yellow-minion" }
]
run(function() {
json = env.amsSerializer.normalizeResponse(env.store, MediocreVillain, json_hash, '1', 'find');
});

assert.deepEqual(json, {
"data": {
"id": "1",
"type": "mediocre-villain",
"attributes": {
"name": "Dr Horrible"
},
"relationships": {
"evilMinions": {
"data": [
{ "id": "12", "type": "evil-minions/yellow-minion" }
]
}
}
}
},
"included": [{
"id": "12",
"type": "evil-minions/yellow-minion",
"attributes": {
"name": "Alex"
},
"relationships": {}
}]
"included": [{
"id": "12",
"type": "evil-minions/yellow-minion",
"attributes": {
"name": "Alex"
},
"relationships": {}
}]
});
});
});

test("extractPolymorphic belongsTo", function() {
var json_hash = {
doomsday_device: { id: 1, name: "DeathRay", evil_minion_id: { type: "EvilMinions::YellowMinion", id: 12 } },
"evil-minions/yellow-minion": [{ id: 12, name: "Alex", doomsday_device_ids: [1] }]
};
var json;
test("extractPolymorphic belongsTo", function(assert) {
var json_hash = {
doomsday_device: { id: 1, name: "DeathRay", evil_minion_id: { type: "EvilMinions::YellowMinion", id: 12 } },
"evil-minions/yellow-minion": [{ id: 12, name: "Alex", doomsday_device_ids: [1] }]
};
var json;

run(function() {
json = env.amsSerializer.normalizeResponse(env.store, DoomsdayDevice, json_hash, '1', 'findRecord');
});
run(function() {
json = env.amsSerializer.normalizeResponse(env.store, DoomsdayDevice, json_hash, '1', 'find');
});

deepEqual(json, {
"data": {
"id": "1",
"type": "doomsday-device",
"attributes": {
"name": "DeathRay"
},
"relationships": {
"evilMinion": {
"data": { "id": "12", "type": "evil-minions/yellow-minion" }
assert.deepEqual(json, {
"data": {
"id": "1",
"type": "doomsday-device",
"attributes": {
"name": "DeathRay"
},
"relationships": {
"evilMinion": {
"data": { "id": "12", "type": "evil-minions/yellow-minion" }
}
}
}
},
"included": [{
"id": "12",
"type": "evil-minions/yellow-minion",
"attributes": {
"name": "Alex"
},
"relationships": {}
}]
"included": [{
"id": "12",
"type": "evil-minions/yellow-minion",
"attributes": {
"name": "Alex"
},
"relationships": {}
}]
});
});
});

}
Loading