Skip to content

Commit

Permalink
Merge pull request #3564 from fivetanley/backport-am-changes
Browse files Browse the repository at this point in the history
[BUGFIX release] backport ActiveModelAdapter changes
  • Loading branch information
fivetanley committed Jul 20, 2015
2 parents 35c7cab + 623bd2b commit 88d0456
Show file tree
Hide file tree
Showing 8 changed files with 845 additions and 477 deletions.
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

0 comments on commit 88d0456

Please sign in to comment.