Skip to content

Commit

Permalink
First pass at cleaning up typeKey usage, testing non camelCase
Browse files Browse the repository at this point in the history
  • Loading branch information
mixonic committed Feb 26, 2015
1 parent 6b61e3c commit 2a42c50
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 35 deletions.
14 changes: 6 additions & 8 deletions packages/ember-data/lib/serializers/embedded_records_mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -437,14 +437,13 @@ function extractEmbeddedHasManyPolymorphic(store, key, hash) {
var ids = [];

forEach(hash[key], function(data) {
var typeKey = data.type;
var embeddedSerializer = store.serializerFor(typeKey);
var embeddedType = store.modelFor(typeKey);
var embeddedType = store.modelFor(data.type);
var embeddedSerializer = store.serializerFor(embeddedType.typeKey);
var primaryKey = get(embeddedSerializer, 'primaryKey');

var embeddedRecord = embeddedSerializer.normalize(embeddedType, data, null);
store.push(embeddedType, embeddedRecord);
ids.push({ id: embeddedRecord[primaryKey], type: typeKey });
ids.push({ id: embeddedRecord[primaryKey], type: embeddedType.typeKey });
});

hash[key] = ids;
Expand All @@ -471,16 +470,15 @@ function extractEmbeddedBelongsToPolymorphic(store, key, hash) {
}

var data = hash[key];
var typeKey = data.type;
var embeddedSerializer = store.serializerFor(typeKey);
var embeddedType = store.modelFor(typeKey);
var embeddedType = store.modelFor(data.type);
var embeddedSerializer = store.serializerFor(embeddedType.typeKey);
var primaryKey = get(embeddedSerializer, 'primaryKey');

var embeddedRecord = embeddedSerializer.normalize(embeddedType, data, null);
store.push(embeddedType, embeddedRecord);

hash[key] = embeddedRecord[primaryKey];
hash[key + 'Type'] = typeKey;
hash[key + 'Type'] = embeddedType.typeKey;
return hash;
}

Expand Down
32 changes: 25 additions & 7 deletions packages/ember-data/lib/serializers/json_serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -487,17 +487,32 @@ export default Serializer.extend({
},

/**
You can use this method to customize how a serialized record is added to the complete
JSON hash to be sent to the server. By default the JSON Serializer does not namespace
the payload and just sends the raw serialized JSON object.
If your server expects namespaced keys, you should consider using the RESTSerializer.
Otherwise you can override this method to customize how the record is added to the hash.
Serialize a model snapshot into a server request payload.
For example, your server may expect underscored root objects.
By default the JSON Serializer does not namespace
payloads, instead a "raw" serialized object is sent. For example:
```js
{
id: 'car-one',
speed: 'fast'
}
```
Note the lack of a namespace, and of any information about what
model this payload refers to.
If your server expects namespaced payloads, you should consider using the RESTSerializer.
You can also override this method to customize serialization behavior.
For example, your server may expect underscored model namespaces:
```js
App.ApplicationSerializer = DS.RESTSerializer.extend({
serializeIntoHash: function(data, type, snapshot, options) {
// type.typeKey is a model name with formatting decided by your
// application container. In globals mode, ususally camelCase. In
// Ember-CLI, usually dasherize-case.
var root = Ember.String.decamelize(type.typeKey);
data[root] = this.serialize(snapshot, options);
}
Expand Down Expand Up @@ -678,7 +693,10 @@ export default Serializer.extend({
}
}
});
```
```
The default behavior of this method is to not serialize
type data for polymorphic relationships.
@method serializePolymorphicType
@param {DS.Snapshot} snapshot
Expand Down
57 changes: 38 additions & 19 deletions packages/ember-data/lib/serializers/rest_serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -505,15 +505,13 @@ var RESTSerializer = JSONSerializer.extend({
},

/**
This method is used to convert each JSON root key in the payload
into a typeKey that it can use to look up the appropriate model for
that part of the payload. By default the typeKey for a model is its
name in camelCase, so if your JSON root key is 'fast-car' you would
use typeForRoot to convert it to 'fastCar' so that Ember Data finds
the `FastCar` model.
This method is used to convert each JSON root key (from a payload)
into a model name. Model naming conventions may vary between
applications and environements, but will most often follow
these standards:
If you diverge from this norm you should also consider changes to
store._normalizeTypeKey as well.
* Ember-CLI applications will follow the pattern: `dasherized-model-name`.
* Globals-mode Ember applications will follow the pattern `camelCaseName`.
For example, your server may return prefixed root keys like so:
Expand All @@ -527,25 +525,43 @@ var RESTSerializer = JSONSerializer.extend({
```
In order for Ember Data to know that the model corresponding to
the 'response-fast-car' hash is `FastCar` (typeKey: 'fastCar'),
you can override typeForRoot to convert 'response-fast-car' to
'fastCar' like so:
the `response-fast-car` property is the Globals-mode class
`App.FastCar` the `typeForRoot` must be overridden. For example:
```js
App.ApplicationSerializer = DS.RESTSerializer.extend({
typeForRoot: function(root) {
// 'response-fast-car' should become 'fast-car'
var subRoot = root.substring(9);
// normalizes 'fast-car' to 'fastCar'
// singularize for good measure
return subRoot.singularize().camelize();
}
});
```
The default implementation of `typeForRoot` is to
`singularize` then `camelize` a payload property name.
This does not technically match the conventions of
Ember-CLI, and although it will work well in apps
and acceptance tests, may be problematic in unit tests.
// _super normalizes 'fast-car' to 'fastCar'
return this._super(subRoot);
A suggested implementation for Ember-CLI users would
be:
```js
// app/serializers/application.js
import DS from "ember-data";
export default DS.RESTSerializer.extend({
typeForRoot: function(root) {
return subRoot.singularize().dasherize();
}
});
```
@method typeForRoot
@param {String} key
@return {String} the model's typeKey
@return {String} the model's name in the container
*/
typeForRoot: function(key) {
return camelize(singularize(key));
Expand Down Expand Up @@ -702,14 +718,17 @@ var RESTSerializer = JSONSerializer.extend({

/**
You can use this method to customize the root keys serialized into the JSON.
By default the REST Serializer sends the typeKey of a model, which is a camelized
version of the name.
By default the REST Serializer sends a camelized version of the model name.
This can be changed by overridding the `serializeIntoHash` function.
For example, your server may expect underscored root objects.
```js
App.ApplicationSerializer = DS.RESTSerializer.extend({
serializeIntoHash: function(data, type, record, options) {
// type.typeKey is a model name with formatting decided by your
// application container. In globals mode, ususally camelCase. In
// Ember-CLI, usually dasherize-case.
var root = Ember.String.decamelize(type.typeKey);
data[root] = this.serialize(record, options);
}
Expand All @@ -723,13 +742,13 @@ var RESTSerializer = JSONSerializer.extend({
@param {Object} options
*/
serializeIntoHash: function(hash, type, snapshot, options) {
hash[type.typeKey] = this.serialize(snapshot, options);
hash[camelize(type.typeKey)] = this.serialize(snapshot, options);
},

/**
You can use this method to customize how polymorphic objects are serialized.
By default the JSON Serializer creates the key by appending `Type` to
the attribute and value from the model's camelcased model name.
By default the payload property name is the model's name converted to
camelCase, with `Type` appended. For example, `fastCarType`.
@method serializePolymorphicType
@param {DS.Snapshot} snapshot
Expand Down
2 changes: 1 addition & 1 deletion packages/ember-data/lib/system/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -1411,7 +1411,7 @@ Store = Ember.Object.extend({
if (typeof key === 'string') {
factory = this.modelFactoryFor(key);
if (!factory) {
//Support looking up mixins as base types for polymorphic relationships
// Support looking up mixins as base types for polymorphic relationships
factory = this._modelForMixin(key);
}
if (!factory) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1321,3 +1321,164 @@ test("serializing relationships with an embedded and without calls super when no
ok(calledSerializeBelongsTo);
ok(calledSerializeHasMany);
});

module("integration/embedded_records_mixin - EmbeddedRecordsMixin with dasherized model names in container", {
setup: function() {
SuperVillain = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
homePlanet: DS.belongsTo("home-planet", { inverse: 'villains' }),
secretLab: DS.belongsTo("secret-lab"),
secretWeapons: DS.hasMany("secret-weapon"),
evilMinions: DS.hasMany("evil-minion")
});
HomePlanet = DS.Model.extend({
name: DS.attr('string'),
villains: DS.hasMany('super-villain', { inverse: 'homePlanet' })
});
SecretLab = DS.Model.extend({
minionCapacity: DS.attr('number'),
vicinity: DS.attr('string'),
superVillain: DS.belongsTo('super-villain')
});
BatCave = SecretLab.extend({
infiltrated: DS.attr('boolean')
});
SecretWeapon = DS.Model.extend({
name: DS.attr('string'),
superVillain: DS.belongsTo('super-villain')
});
LightSaber = SecretWeapon.extend({
color: DS.attr('string')
});
EvilMinion = DS.Model.extend({
superVillain: DS.belongsTo('super-villain'),
name: DS.attr('string')
});
env = setupStore({
'super-villain': SuperVillain,
'home-planet': HomePlanet,
'secret-lab': SecretLab,
'bat-cave': BatCave,
'secret-weapon': SecretWeapon,
'light-saber': LightSaber,
'evil-minion': EvilMinion
});
env.store.modelFor('super-villain');
env.store.modelFor('home-planet');
env.store.modelFor('secret-lab');
env.store.modelFor('bat-cave');
env.store.modelFor('secret-weapon');
env.store.modelFor('light-saber');
env.store.modelFor('evil-minion');
},

teardown: function() {
run(env.store, 'destroy');
}
});

test("extractSingle with polymorphic hasMany", function() {
SuperVillain.reopen({
secretWeapons: DS.hasMany("secret-weapon", { polymorphic: true })
});

env.registry.register('adapter:super-villain', DS.ActiveModelAdapter);
env.registry.register('serializer:super-villain', DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
secretWeapons: { embedded: 'always' }
},
typeForRoot: function(type){
return Ember.String.dasherize(type);
}
}));
var serializer = env.container.lookup("serializer:super-villain");

var json_hash = {
super_villain: {
id: "1",
first_name: "Tom",
last_name: "Dale",
secret_weapons: [
{
id: "1",
type: "LightSaber",
name: "Tom's LightSaber",
color: "Red"
},
{
id: "1",
type: "SecretWeapon",
name: "The Death Star"
}
]
}
};

var json = run(function() {
return serializer.extractSingle(env.store, SuperVillain, json_hash);
});

deepEqual(json, {
id: "1",
firstName: "Tom",
lastName: "Dale",
secretWeapons: [
{ id: "1", type: "light-saber" },
{ id: "1", type: "secret-weapon" }
]
}, "Primary hash was correct");

equal( env.store.recordForId("secret-weapon", "1").get("name"), "The Death Star",
"Embedded polymorphic SecretWeapon found" );
equal( env.store.recordForId("light-saber", "1").get("name"), "Tom's LightSaber",
"Embedded polymorphic LightSaber found" );

});

test("extractSingle with polymorphic belongsTo", function() {
expect(2);

SuperVillain.reopen({
secretLab: DS.belongsTo("secret-lab", { polymorphic: true })
});

env.registry.register('adapter:super-villain', DS.ActiveModelAdapter);
env.registry.register('serializer:super-villain', DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
secretLab: { embedded: 'always' }
},
typeForRoot: function(type){
return Ember.String.dasherize(type);
}
}));
var serializer = env.container.lookup("serializer:super-villain");

var json_hash = {
super_villain: {
id: "1",
first_name: "Tom",
last_name: "Dale",
secret_lab: {
id: "1",
type: "BatCave",
infiltrated: true
}
}
};

var json = run(function() {
return serializer.extractSingle(env.store, SuperVillain, json_hash);
});

deepEqual(json, {
id: "1",
firstName: "Tom",
lastName: "Dale",
secretLab: "1",
secretLabType: "bat-cave"
}, "Primary has was correct");

equal( env.store.recordForId("bat-cave", "1").get("infiltrated"), true,
"Embedded polymorphic BatCave was found" );
});

0 comments on commit 2a42c50

Please sign in to comment.