From 69dca0c87cf6a830c4dee1214af4cb512f9806cb Mon Sep 17 00:00:00 2001 From: Alan Date: Fri, 18 Sep 2015 17:47:30 -0400 Subject: [PATCH] [BUGFIX release] Disable polymorphic deserialization when a model expects a type attribute b7f7b7a introduced a bug where if there is a type key in a payload that is part of an array, it would be used for polymorphic deserialization even when the model expects an attribute that is named "type". Interestingly, for such payloads, `arrayHash` passed into `normalizeArray()` in rest-serializer.js contains Ember.Object instances as opposed to plain objects. This causes the code to throw, since `hash.type` would be a computed property in that case. This is probably because of extending from JSONAPISerializer. --- .../lib/serializers/rest-serializer.js | 9 +++-- .../serializers/rest-serializer-test.js | 35 +++++++++++++++++-- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/packages/ember-data/lib/serializers/rest-serializer.js b/packages/ember-data/lib/serializers/rest-serializer.js index 31070de71a5..60b2a58b888 100644 --- a/packages/ember-data/lib/serializers/rest-serializer.js +++ b/packages/ember-data/lib/serializers/rest-serializer.js @@ -8,6 +8,7 @@ import {singularize} from "ember-inflector/lib/system/string"; import coerceId from "ember-data/system/coerce-id"; var camelize = Ember.String.camelize; +var get = Ember.get; /** Normally, applications will use the `RESTSerializer` by implementing @@ -148,8 +149,10 @@ var RESTSerializer = JSONSerializer.extend({ let modelClass = store.modelFor(modelName); let serializer = store.serializerFor(modelName); + const primaryHasTypeAttribute = get(modelClass, 'attributes').get('type'); + /*jshint loopfunc:true*/ arrayHash.forEach((hash) => { - let { data, included } = this._normalizePolymorphicRecord(store, hash, prop, modelClass, serializer); + let { data, included } = this._normalizePolymorphicRecord(store, hash, prop, modelClass, serializer, primaryHasTypeAttribute); documentHash.data.push(data); if (included) { documentHash.included.push(...included); @@ -159,10 +162,10 @@ var RESTSerializer = JSONSerializer.extend({ return documentHash; }, - _normalizePolymorphicRecord(store, hash, prop, primaryModelClass, primarySerializer) { + _normalizePolymorphicRecord(store, hash, prop, primaryModelClass, primarySerializer, primaryHasTypeAttribute) { let serializer, modelClass; // Support polymorphic records in async relationships - if (hash.type && store._hasModelFor(this.modelNameFromPayloadKey(hash.type))) { + if (!primaryHasTypeAttribute && hash.type && store._hasModelFor(this.modelNameFromPayloadKey(hash.type))) { serializer = store.serializerFor(hash.type); modelClass = store.modelFor(hash.type); } else { diff --git a/packages/ember-data/tests/integration/serializers/rest-serializer-test.js b/packages/ember-data/tests/integration/serializers/rest-serializer-test.js index 30ffa0f7383..13fa5b56dfa 100644 --- a/packages/ember-data/tests/integration/serializers/rest-serializer-test.js +++ b/packages/ember-data/tests/integration/serializers/rest-serializer-test.js @@ -1,4 +1,4 @@ -var HomePlanet, league, SuperVillain, EvilMinion, YellowMinion, DoomsdayDevice, Comment, env; +var HomePlanet, league, SuperVillain, EvilMinion, YellowMinion, DoomsdayDevice, Comment, Basket, env; var run = Ember.run; module("integration/serializer/rest - RESTSerializer", { @@ -29,13 +29,18 @@ module("integration/serializer/rest - RESTSerializer", { root: DS.attr('boolean'), children: DS.hasMany('comment', { inverse: null, async: false }) }); + Basket = DS.Model.extend({ + type: DS.attr('string'), + size: DS.attr('number') + }); env = setupStore({ superVillain: SuperVillain, homePlanet: HomePlanet, evilMinion: EvilMinion, yellowMinion: YellowMinion, doomsdayDevice: DoomsdayDevice, - comment: Comment + comment: Comment, + basket: Basket }); env.store.modelFor('super-villain'); env.store.modelFor('home-planet'); @@ -43,6 +48,7 @@ module("integration/serializer/rest - RESTSerializer", { env.store.modelFor('yellow-minion'); env.store.modelFor('doomsday-device'); env.store.modelFor('comment'); + env.store.modelFor('basket'); }, teardown: function() { @@ -588,3 +594,28 @@ test("normalizeResponse can load secondary records of the same type without affe }] }); }); + +test("don't polymorphically deserialize base on the type key in payload when a type attribute exist", function() { + env.registry.register('serializer:application', DS.RESTSerializer.extend({ + isNewSerializerAPI: true + })); + + run(function() { + env.restSerializer.normalizeArrayResponse(env.store, Basket, { + basket: [ + env.store.createRecord('Basket', { type: 'bamboo', size: 10, id: '1' }), + env.store.createRecord('Basket', { type: 'yellowMinion', size: 10, id: '65536' }) + ] + }); + }); + + const normalRecord = env.store.peekRecord('basket', '1'); + ok(normalRecord, "payload with type that doesn't exist"); + strictEqual(normalRecord.get('type'), 'bamboo'); + strictEqual(normalRecord.get('size'), 10); + + const clashingRecord = env.store.peekRecord('basket', '65536'); + ok(clashingRecord, 'payload with type that matches another model name'); + strictEqual(clashingRecord.get('type'), 'yellowMinion'); + strictEqual(clashingRecord.get('size'), 10); +});