Skip to content

Commit

Permalink
[BUGFIX beta] extract polymorphic belongsTo in RESTSerializer
Browse files Browse the repository at this point in the history
This change adds the correct extraction of a polymorphic belongsTo
specified in the payload in the following form:

``` js
{
  id: 123,
  // ...
  message: 1,
  messageType: 'post'
}
```

where the model is specified as:

``` js
DS.Model.extend({
  message: DS.belongsTo('message', { polymorphic: true })
})
```

---

This commit introduces a new hook `keyForPolymorphicType` which can be
overwritten to customize the key which holds the polymorphic type.
  • Loading branch information
pangratz committed Oct 20, 2015
1 parent 921e8c9 commit 1d85e5c
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 1 deletion.
33 changes: 32 additions & 1 deletion packages/ember-data/lib/serializers/json-serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,29 @@ export default Serializer.extend({
return { id: coerceId(relationshipHash), type: relationshipModelName };
},

/**
Returns a polymorphic relationship formatted as a JSON-API "relationship object".
http://jsonapi.org/format/#document-resource-object-relationships
`relationshipOptions` is a hash which contains more information about the
polymorphic relationship which should be extracted:
- `resourceHash` complete hash of the resource the relationship should be
extracted from
- `relationshipKey` key under which the value for the relationship is
extracted from the resourceHash
- `relationshipMeta` meta information about the relationship
@method extractPolymorphicRelationship
@param {Object} relationshipModelName
@param {Object} relationshipHash
@param {Object} relationshipOptions
@return {Object}
*/
extractPolymorphicRelationship: function(relationshipModelName, relationshipHash, relationshipOptions) {
return this.extractRelationship(relationshipModelName, relationshipHash);
},

/**
Returns the resource's relationships formatted as a JSON-API "relationships object".
Expand All @@ -605,7 +628,15 @@ export default Serializer.extend({
let data = null;
let relationshipHash = resourceHash[relationshipKey];
if (relationshipMeta.kind === 'belongsTo') {
data = this.extractRelationship(relationshipMeta.type, relationshipHash);
if (relationshipMeta.options.polymorphic) {
// extracting a polymorphic belongsTo may need more information
// than the type and the hash (which might only be an id) for the
// relationship, hence we pass the key, resource and
// relationshipMeta too
data = this.extractPolymorphicRelationship(relationshipMeta.type, relationshipHash, { key, resourceHash, relationshipMeta });
} else {
data = this.extractRelationship(relationshipMeta.type, relationshipHash);
}
} else if (relationshipMeta.kind === 'hasMany') {
data = Ember.isNone(relationshipHash) ? null : relationshipHash.map((item) => this.extractRelationship(relationshipMeta.type, item));
}
Expand Down
75 changes: 75 additions & 0 deletions packages/ember-data/lib/serializers/rest-serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,37 @@ var camelize = Ember.String.camelize;
*/
var RESTSerializer = JSONSerializer.extend({

/**
`keyForPolymorphicType` can be used to define a custom key when
serializing and deserializing a polymorphic type. By default, the
returned key is `${key}Type`.
Example
```app/serializers/post.js
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
keyForPolymorphicType: function(key, relationship) {
var relationshipKey = this.keyForRelationship(key);
return 'type-' + relationshipKey;
}
});
```
@method keyForPolymorphicType
@param {String} key
@param {String} typeClass
@param {String} method
@return {String} normalized key
*/
keyForPolymorphicType: function(key, typeClass, method) {
var relationshipKey = this.keyForRelationship(key);

return `${relationshipKey}Type`;
},

/**
Normalizes a part of the JSON payload returned by
the server. You should override this method, munge the hash
Expand Down Expand Up @@ -689,6 +720,50 @@ var RESTSerializer = JSONSerializer.extend({
} else {
json[key + "Type"] = Ember.String.camelize(belongsTo.modelName);
}
},

/**
You can use this method to customize how a polymorphic relationship should
be extracted.
@method extractPolymorphicRelationship
@param {Object} relationshipType
@param {Object} relationshipHash
@param {Object} relationshipOptions
@return {Object}
*/
extractPolymorphicRelationship: function(relationshipType, relationshipHash, relationshipOptions) {
var { key, resourceHash, relationshipMeta } = relationshipOptions;

// A polymorphic belongsTo relationship can be present in the payload
// either in the form where the `id` and the `type` are given:
//
// {
// message: { id: 1, type: 'post' }
// }
//
// or by the `id` and a `<relationship>Type` attribute:
//
// {
// message: 1,
// messageType: 'post'
// }
//
// The next code checks if the latter case is present and returns the
// corresponding JSON-API representation. The former case is handled within
// the base class JSONSerializer.
var isPolymorphic = relationshipMeta.options.polymorphic;
var typeProperty = this.keyForPolymorphicType(key, relationshipType, 'deserialize');

if (isPolymorphic && resourceHash.hasOwnProperty(typeProperty) && typeof relationshipHash !== 'object') {
let type = this.modelNameFromPayloadKey(resourceHash[typeProperty]);
return {
id: relationshipHash,
type: type
};
}

return this._super(...arguments);
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,41 @@ test('serializeBelongsTo with async polymorphic', function() {
deepEqual(json, expected, 'returned JSON is correct');
});

test('keyForPolymorphicType can be used to overwrite how the type of a polymorphic record is looked up for normalization', function() {
var json = {
doomsdayDevice: {
id: '1',
evilMinion: '2',
typeForEvilMinion: 'evilMinion'
}
};

var expected = {
data: {
type: 'doomsday-device',
id: '1',
attributes: {},
relationships: {
evilMinion: {
data: {
type: 'evil-minion',
id: '2'
}
}
}
},
included: []
};

env.restSerializer.keyForPolymorphicType = function() {
return 'typeForEvilMinion';
};

var normalized = env.restSerializer.normalizeResponse(env.store, DoomsdayDevice, json, null, 'findRecord');

deepEqual(normalized, expected, 'normalized JSON is correct');
});

test('serializeIntoHash uses payloadKeyFromModelName to normalize the payload root key', function() {
run(function() {
league = env.store.createRecord('home-planet', { name: "Umber", id: "123" });
Expand All @@ -475,6 +510,42 @@ test('serializeIntoHash uses payloadKeyFromModelName to normalize the payload ro
});
});

test('normalizeResponse with async polymorphic belongsTo, using <relationshipName>Type', function() {
env.registry.register('serializer:application', DS.RESTSerializer.extend());
var store = env.store;
env.adapter.findRecord = (store, type) => {
if (type.modelName === 'doomsday-device') {
return {
doomsdayDevice: {
id: 1,
name: "DeathRay",
evilMinion: 1,
evilMinionType: 'yellowMinion'
}
};
}

equal(type.modelName, 'yellow-minion');

return {
yellowMinion: {
id: 1,
type: 'yellowMinion',
name: 'Alex',
eyes: 3
}
};
};

run(function() {
store.findRecord('doomsday-device', 1).then((deathRay) => {
return deathRay.get('evilMinion');
}).then((evilMinion) => {
equal(evilMinion.get('eyes'), 3);
});
});
});

test('normalizeResponse with async polymorphic belongsTo', function() {
env.registry.register('serializer:application', DS.RESTSerializer.extend({
isNewSerializerAPI: true
Expand Down

0 comments on commit 1d85e5c

Please sign in to comment.