Permalink
Browse files

Add hasOne relationship

Models can now define a hasOne relationship as the inverse of a belongsTo
relationship:

  App.Person = DS.Model.extend({
    heart: DS.hasOne(App.Heart)
  });

  App.Heart = DS.Model.extend({
    person: DS.belongsTo(App.Person)
  });
  • Loading branch information...
1 parent 73ad70c commit 8833f8dadd43273a35c48ca70447aeba79adf3da @elliterate elliterate committed Feb 27, 2013
Showing with 1,292 additions and 65 deletions.
  1. +4 −0 packages/ember-data/lib/serializers/fixture_serializer.js
  2. +37 −3 packages/ember-data/lib/serializers/json_serializer.js
  3. +10 −0 packages/ember-data/lib/serializers/rest_serializer.js
  4. +14 −4 packages/ember-data/lib/system/model/model.js
  5. +1 −0 packages/ember-data/lib/system/relationships.js
  6. +9 −4 packages/ember-data/lib/system/relationships/ext.js
  7. +77 −0 packages/ember-data/lib/system/relationships/has_one.js
  8. +2 −2 packages/ember-data/lib/system/relationships/one_to_many_change.js
  9. +140 −7 packages/ember-data/lib/system/serializer.js
  10. +46 −0 packages/ember-data/tests/integration/dirtiness_test.js
  11. +31 −2 packages/ember-data/tests/integration/embedded/embedded_dirtying_test.js
  12. +69 −0 packages/ember-data/tests/integration/embedded/embedded_loading_test.js
  13. +90 −2 packages/ember-data/tests/integration/embedded/embedded_without_ids_test.js
  14. +43 −0 packages/ember-data/tests/integration/inverse_relationships_test.js
  15. +25 −2 packages/ember-data/tests/integration/json_serialization_test.js
  16. +21 −0 packages/ember-data/tests/integration/materialization_tests.js
  17. +12 −4 packages/ember-data/tests/integration/relationships/introspection_test.js
  18. +6 −3 packages/ember-data/tests/integration/relationships/inverse_test.js
  19. +29 −2 packages/ember-data/tests/integration/relationships/one_to_none_relationships_test.js
  20. +14 −2 packages/ember-data/tests/integration/relationships/one_to_one_relationships_test.js
  21. +462 −19 packages/ember-data/tests/integration/rollback_test.js
  22. +30 −6 packages/ember-data/tests/unit/json_serializer_test.js
  23. +115 −2 packages/ember-data/tests/unit/relationships_test.js
  24. +5 −1 packages/ember-data/tests/unit/serializers/rest_serializer_test.js
View
4 packages/ember-data/lib/serializers/fixture_serializer.js
@@ -66,5 +66,9 @@ DS.FixtureSerializer = DS.Serializer.extend({
extractBelongsTo: function(type, hash, key) {
return hash[key];
+ },
+
+ extractHasOne: function(type, hash, key) {
+ return hash[key];
}
});
View
40 packages/ember-data/lib/serializers/json_serializer.js
@@ -91,15 +91,19 @@ DS.JSONSerializer = DS.Serializer.extend({
return hash[key];
},
+ extractHasOne: function(type, hash, key) {
+ return hash[key];
+ },
+
addBelongsTo: function(hash, record, key, relationship) {
var type = record.constructor,
name = relationship.key,
value = null,
- embeddedChild;
+ embeddedParent;
if (this.embeddedType(type, name)) {
- if (embeddedChild = get(record, name)) {
- value = this.serialize(embeddedChild, { includeId: true });
+ if (embeddedParent = get(record, name)) {
+ value = this.serialize(embeddedParent, { includeId: true });
}
hash[key] = value;
@@ -110,6 +114,36 @@ DS.JSONSerializer = DS.Serializer.extend({
},
/**
+ Adds a has-one relationship to the JSON hash being built.
+
+ The default REST semantics are to only add a has-one relationship if it
+ is embedded. If the relationship was initially loaded by ID, we assume that
+ that was done as a performance optimization, and that changes to the
+ has-one should be saved as foreign key changes on the child's belongs-to
+ relationship.
+
+ @param {Object} hash the JSON being built
+ @param {DS.Model} record the record being serialized
+ @param {String} key the JSON key into which the serialized relationship
+ should be saved
+ @param {Object} relationship metadata about the relationship being serialized
+ */
+ addHasOne: function(hash, record, key, relationship) {
+ var type = record.constructor,
+ name = relationship.key,
+ value = null,
+ embeddedChild;
+
+ if (this.embeddedType(type, name)) {
+ if (embeddedChild = get(record, name)) {
+ value = this.serialize(embeddedChild, { includeId: true });
+ }
+
+ hash[key] = value;
+ }
+ },
+
+ /**
Adds a has-many relationship to the JSON hash being built.
The default REST semantics are to only add a has-many relationship if it
View
10 packages/ember-data/lib/serializers/rest_serializer.js
@@ -15,6 +15,16 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
return key + "_id";
},
+ keyForHasOne: function(type, name) {
+ var key = this.keyForAttributeName(type, name);
+
+ if (this.embeddedType(type, name)) {
+ return key;
+ }
+
+ return key + "_id";
+ },
+
keyForHasMany: function(type, name) {
var key = this.keyForAttributeName(type, name);
View
18 packages/ember-data/lib/system/model/model.js
@@ -210,6 +210,7 @@ DS.Model = Ember.Object.extend(Ember.Evented, LoadPromise, {
this._data = {
attributes: {},
belongsTo: {},
+ hasOne: {},
hasMany: {},
id: null
};
@@ -236,6 +237,10 @@ DS.Model = Ember.Object.extend(Ember.Evented, LoadPromise, {
this._data.belongsTo[name] = id;
},
+ materializeHasOne: function(name, id) {
+ this._data.hasOne[name] = id;
+ },
+
rollback: function() {
this._setup();
this.send('becameClean');
@@ -264,14 +269,19 @@ DS.Model = Ember.Object.extend(Ember.Evented, LoadPromise, {
observation becomes more unified with regular observers.
*/
suspendRelationshipObservers: function(callback, binding) {
- var observers = get(this.constructor, 'relationshipNames').belongsTo;
+ var belongsToObservers = get(this.constructor, 'relationshipNames').belongsTo;
+ var hasOneObservers = get(this.constructor, 'relationshipNames').hasOne;
var self = this;
try {
this._suspendedRelationships = true;
- Ember._suspendObservers(self, observers, null, 'belongsToDidChange', function() {
- Ember._suspendBeforeObservers(self, observers, null, 'belongsToWillChange', function() {
- callback.call(binding || self);
+ Ember._suspendObservers(self, belongsToObservers, null, 'belongsToDidChange', function() {
+ Ember._suspendBeforeObservers(self, belongsToObservers, null, 'belongsToWillChange', function() {
+ Ember._suspendObservers(self, hasOneObservers, null, 'hasOneDidChange', function() {
+ Ember._suspendBeforeObservers(self, hasOneObservers, null, 'hasOneWillChange', function() {
+ callback.call(binding || self);
+ });
+ });
});
});
} finally {
View
1 packages/ember-data/lib/system/relationships.js
@@ -1,4 +1,5 @@
require("ember-data/system/relationships/belongs_to");
+require("ember-data/system/relationships/has_one");
require("ember-data/system/relationships/has_many");
require("ember-data/system/relationships/ext");
require("ember-data/system/relationships/one_to_many_change");
View
13 packages/ember-data/lib/system/relationships/ext.js
@@ -34,9 +34,14 @@ DS.Model.reopen({
// the computed property.
var meta = value.meta();
- if (meta.isRelationship && meta.kind === 'belongsTo') {
- Ember.addObserver(proto, key, null, 'belongsToDidChange');
- Ember.addBeforeObserver(proto, key, null, 'belongsToWillChange');
+ if (meta.isRelationship) {
+ if (meta.kind === 'belongsTo') {
+ Ember.addObserver(proto, key, null, 'belongsToDidChange');
+ Ember.addBeforeObserver(proto, key, null, 'belongsToWillChange');
+ } else if (meta.kind === 'hasOne') {
+ Ember.addObserver(proto, key, null, 'hasOneDidChange');
+ Ember.addBeforeObserver(proto, key, null, 'hasOneWillChange');
+ }
}
if (meta.isAttribute) {
@@ -162,7 +167,7 @@ DS.Model.reopenClass({
@readOnly
*/
relationshipNames: Ember.computed(function() {
- var names = { hasMany: [], belongsTo: [] };
+ var names = { hasMany: [], belongsTo: [], hasOne: [] };
this.eachComputedProperty(function(name, meta) {
if (meta.isRelationship) {
View
77 packages/ember-data/lib/system/relationships/has_one.js
@@ -0,0 +1,77 @@
+var get = Ember.get, set = Ember.set,
+ none = Ember.isNone;
+
+DS.hasOne = function(type, options) {
+ Ember.assert("The first argument DS.hasOne must be a model type or string, like DS.hasOne(App.Person)", !!type && (typeof type === 'string' || DS.Model.detect(type)));
+
+ options = options || {};
+
+ var meta = { type: type, isRelationship: true, options: options, kind: 'hasOne' };
+
+ return Ember.computed(function(key, value) {
+ if (arguments.length === 2) {
+ return value === undefined ? null : value;
+ }
+
+ var data = get(this, 'data').hasOne,
+ store = get(this, 'store'), id;
+
+ if (typeof type === 'string') {
+ type = get(this, type, false) || get(Ember.lookup, type);
+ }
+
+ id = data[key];
+
+ if (!id) {
+ return null;
+ } else if (typeof id === 'object') {
+ return store.recordForReference(id);
+ } else {
+ return store.find(type, id);
+ }
+ }).property('data').meta(meta);
+};
+
+/**
+ These observers observe all `hasOne` relationships on the record. See
+ `relationships/ext` to see how these observers get their dependencies.
+
+ */
+
+DS.Model.reopen({
+ /** @private */
+ hasOneWillChange: Ember.beforeObserver(function(record, key) {
+ if (get(record, 'isLoaded')) {
+ var oldChild = get(record, key),
+ parentReference = get(record, '_reference'),
+ store = get(record, 'store');
+
+ if (oldChild) {
+ var change = DS.RelationshipChange.createChange(parentReference, get(oldChild, '_reference'), store, { key: key, kind: "hasOne", changeType: "remove" });
+ change.sync();
+
+ this._changesToSync[key] = change;
+ }
+ }
+ }),
+
+ /** @private */
+ hasOneDidChange: Ember.immediateObserver(function(record, key) {
+ if (get(record, 'isLoaded')) {
+ var newChild = get(record, key);
+
+ if (newChild) {
+ var parentReference = get(record, '_reference'),
+ store = get(record, 'store');
+
+ var change = DS.RelationshipChange.createChange(parentReference, get(newChild, '_reference'), store, { key: key, kind: "hasOne", changeType: "add" });
+ change.sync();
+
+ if (this._changesToSync[key]) {
+ DS.OneToManyChange.ensureSameTransaction([change, this._changesToSync[key]], store);
+ }
+ }
+ }
+ delete this._changesToSync[key];
+ })
+});
View
4 packages/ember-data/lib/system/relationships/one_to_many_change.js
@@ -72,8 +72,8 @@ DS.RelationshipChange.determineRelationshipType = function(recordType, knownSide
return knownContainerType === "belongsTo" ? "oneToNone" : "manyToNone";
}
else{
- if(otherContainerType === "belongsTo"){
- return knownContainerType === "belongsTo" ? "oneToOne" : "manyToOne";
+ if(otherContainerType === "belongsTo" || otherContainerType === "hasOne"){
+ return knownContainerType === "belongsTo" || knownContainerType === "hasOne" ? "oneToOne" : "manyToOne";
}
else{
return knownContainerType === "belongsTo" ? "oneToMany" : "manyToMany";
View
147 packages/ember-data/lib/system/serializer.js
@@ -225,6 +225,13 @@ DS.Serializer = Ember.Object.extend({
}
}, this);
+ this.eachEmbeddedHasOne(type, function(name, relationship) {
+ var embeddedData = json[this.keyFor(relationship)];
+ if (!isNone(embeddedData)) {
+ this.extractEmbeddedHasOne(loader, relationship, embeddedData, reference, prematerialized);
+ }
+ }, this);
+
loader.prematerialize(reference, prematerialized);
return reference;
@@ -261,6 +268,18 @@ DS.Serializer = Ember.Object.extend({
}
},
+ extractEmbeddedHasOne: function(loader, relationship, data, parent, prematerialized) {
+ var reference = this.extractRecordRepresentation(loader, relationship.type, data, true);
+ prematerialized[relationship.key] = reference;
+
+ // If the embedded record should also be saved back when serializing the parent,
+ // make sure we set its parent since it will not have an ID.
+ var embeddedType = this.embeddedType(parent.type, relationship.key);
+ if (embeddedType === 'always') {
+ reference.parent = parent;
+ }
+ },
+
//.......................
//. SERIALIZATION HOOKS
//.......................
@@ -390,10 +409,10 @@ DS.Serializer = Ember.Object.extend({
By default, `addAttributes` loops over all of the relationships of the
passed record, maps the relationship names to the key for the serialized form,
- and then invokes the public `addBelongsTo` and `addHasMany` hooks.
+ and then invokes the public `addBelongsTo`, `addHasOne`, and `addHasMany` hooks.
- Since you can override `keyForBelongsTo`, `keyForHasMany`, `addBelongsTo`,
- `addHasMany`, and register mappings, you should rarely need to override this
+ Since you can override `keyForBelongsTo`, `keyForHasOne`, `keyForHasMany`, `addBelongsTo`,
+ `addHasOne`, `addHasMany`, and register mappings, you should rarely need to override this
hook.
@param {any} data the serialized representation that is being built
@@ -403,6 +422,8 @@ DS.Serializer = Ember.Object.extend({
record.eachRelationship(function(name, relationship) {
if (relationship.kind === 'belongsTo') {
this._addBelongsTo(data, record, name, relationship);
+ } else if (relationship.kind === 'hasOne') {
+ this._addHasOne(data, record, name, relationship);
} else if (relationship.kind === 'hasMany') {
this._addHasMany(data, record, name, relationship);
}
@@ -434,6 +455,30 @@ DS.Serializer = Ember.Object.extend({
addBelongsTo: Ember.K,
/**
+ A hook you can use to add a `hasOne` relationship to the
+ serialized representation.
+
+ The specifics of this hook are very adapter-specific, so there
+ is no default implementation. You can see `DS.JSONSerializer`
+ for an example of an implementation of the `addHasOne` hook.
+
+ The `hasOne` relationship object has the following properties:
+
+ * **type** a subclass of DS.Model that is the type of the
+ relationship. This is the first parameter to DS.hasOne
+ * **options** the options passed to the call to DS.hasOne
+ * **kind** always `hasOne`
+
+ Additional properties may be added in the future.
+
+ @param {any} data the serialized representation that is being built
+ @param {DS.Model} record the record to serialize
+ @param {String} key the key for the serialized object
+ @param {Object} relationship an object representing the relationship
+ */
+ addHasOne: Ember.K,
+
+ /**
A hook you can use to add a `hasMany` relationship to the
serialized representation.
@@ -580,6 +625,39 @@ DS.Serializer = Ember.Object.extend({
/**
A hook you can use in your serializer subclass to customize
+ how an unmapped `hasOne` relationship is converted into
+ a key.
+
+ By default, this method calls `keyForAttributeName`, so if
+ your naming convention is uniform across attributes and
+ relationships, you can use the default here and override
+ just `keyForAttributeName` as needed.
+
+ For example, if the `hasOne` names in your JSON always
+ begin with `BT_` (e.g. `BT_posts`), you can strip out the
+ `BT_` prefix:"
+
+ ```javascript
+ App.MySerializer = DS.Serializer.extend({
+ // ...
+ keyForHasOne: function(type, name) {
+ return name.match(/^BT_(.*)$/)[1].camelize();
+ }
+ });
+ ```
+
+ @param {DS.Model subclass} type the type of the record with
+ the `hasOne` relationship.
+ @param {String} name the relationship name to convert into a key
+
+ @returns {String} the key
+ */
+ keyForHasOne: function(type, name) {
+ return this.keyForAttributeName(type, name);
+ },
+
+ /**
+ A hook you can use in your serializer subclass to customize
how an unmapped `hasMany` relationship is converted into
a key.
@@ -676,6 +754,12 @@ DS.Serializer = Ember.Object.extend({
} else {
this.materializeBelongsTo(name, record, hash, relationship, prematerialized);
}
+ } else if (relationship.kind === 'hasOne') {
+ if (prematerialized && prematerialized.hasOwnProperty(name)) {
+ record.materializeHasOne(name, prematerialized[name]);
+ } else {
+ this.materializeHasOne(name, record, hash, relationship, prematerialized);
+ }
}
}, this);
},
@@ -690,6 +774,11 @@ DS.Serializer = Ember.Object.extend({
record.materializeBelongsTo(name, this.extractBelongsTo(record.constructor, hash, key));
},
+ materializeHasOne: function(name, record, hash, relationship) {
+ var key = this._keyForHasOne(record.constructor, relationship.key);
+ record.materializeHasOne(name, this.extractHasOne(record.constructor, hash, key));
+ },
+
_extractEmbeddedRelationship: function(type, hash, name, relationshipType) {
var key = this['_keyFor' + relationshipType](type, name);
@@ -812,13 +901,32 @@ DS.Serializer = Ember.Object.extend({
return this._keyFromMappingOrHook('keyForBelongsTo', type, name);
},
+ /**
+ @private
+
+ This method is called to get a key used in the data from
+ a hasOne relationship. It first checks for any mappings before
+ calling the public hook `keyForHasOne`.
+
+ @param {DS.Model subclass} type the type of the record with
+ the `hasOne` relationship.
+ @param {String} name the relationship name to convert into a key
+
+ @returns {String} the key
+ */
+ _keyForHasOne: function(type, name) {
+ return this._keyFromMappingOrHook('keyForHasOne', type, name);
+ },
+
keyFor: function(description) {
var type = description.parentType,
name = description.key;
switch (description.kind) {
case 'belongsTo':
return this._keyForBelongsTo(type, name);
+ case 'hasOne':
+ return this._keyForHasOne(type, name);
case 'hasMany':
return this._keyForHasMany(type, name);
}
@@ -840,6 +948,7 @@ DS.Serializer = Ember.Object.extend({
_keyForHasMany: function(type, name) {
return this._keyFromMappingOrHook('keyForHasMany', type, name);
},
+
/**
@private
@@ -860,6 +969,22 @@ DS.Serializer = Ember.Object.extend({
@private
This method converts the relationship name to a key for serialization,
+ and then invokes the public `addHasOne` hook.
+
+ @param {any} data the serialized representation that is being built
+ @param {DS.Model} record the record to serialize
+ @param {String} name the relationship name
+ @param {Object} relationship an object representing the relationship
+ */
+ _addHasOne: function(data, record, name, relationship) {
+ var key = this._keyForHasOne(record.constructor, name);
+ this.addHasOne(data, record, key, relationship);
+ },
+
+ /**
+ @private
+
+ This method converts the relationship name to a key for serialization,
and then invokes the public `addHasMany` hook.
@param {any} data the serialized representation that is being built
@@ -1014,21 +1139,25 @@ DS.Serializer = Ember.Object.extend({
eachEmbeddedRecord: function(record, callback, binding) {
this.eachEmbeddedBelongsToRecord(record, callback, binding);
+ this.eachEmbeddedHasOneRecord(record, callback, binding);
this.eachEmbeddedHasManyRecord(record, callback, binding);
},
eachEmbeddedBelongsToRecord: function(record, callback, binding) {
- var type = record.constructor;
-
this.eachEmbeddedBelongsTo(record.constructor, function(name, relationship, embeddedType) {
var embeddedRecord = get(record, name);
if (embeddedRecord) { callback.call(binding, embeddedRecord, embeddedType); }
});
},
- eachEmbeddedHasManyRecord: function(record, callback, binding) {
- var type = record.constructor;
+ eachEmbeddedHasOneRecord: function(record, callback, binding) {
+ this.eachEmbeddedHasOne(record.constructor, function(name, relationship, embeddedType) {
+ var embeddedRecord = get(record, name);
+ if (embeddedRecord) { callback.call(binding, embeddedRecord, embeddedType); }
+ });
+ },
+ eachEmbeddedHasManyRecord: function(record, callback, binding) {
this.eachEmbeddedHasMany(record.constructor, function(name, relationship, embeddedType) {
var array = get(record, name);
for (var i=0, l=get(array, 'length'); i<l; i++) {
@@ -1045,6 +1174,10 @@ DS.Serializer = Ember.Object.extend({
this.eachEmbeddedRelationship(type, 'belongsTo', callback, binding);
},
+ eachEmbeddedHasOne: function(type, callback, binding) {
+ this.eachEmbeddedRelationship(type, 'hasOne', callback, binding);
+ },
+
eachEmbeddedRelationship: function(type, kind, callback, binding) {
type.eachRelationship(function(name, relationship) {
var embeddedType = this.embeddedType(type, name);
View
46 packages/ember-data/tests/integration/dirtiness_test.js
@@ -56,6 +56,52 @@ test("By default, changing the relationship between two records does not cause t
ok(!comment.get('isDirty'), "comment should not be dirty");
});
+test("By default, changing a one-to-one relationship from the child causes both records to become dirty", function() {
+ var Post = DS.Model.extend();
+
+ var Attachment = DS.Model.extend({
+ post: DS.belongsTo(Post)
+ });
+
+ Post.reopen({
+ attachment: DS.hasOne(Attachment)
+ });
+
+ store.load(Post, { id: 1, attachment: 1 });
+ store.load(Attachment, { id: 1, post: 1 });
+
+ var post = store.find(Post, 1);
+ var attachment = store.find(Attachment, 1);
+
+ attachment.set('post', null);
+
+ ok(post.get('isDirty'), "post should be dirty");
+ ok(attachment.get('isDirty'), "attachment should be dirty");
+});
+
+test("By default, changing a one-to-one relationship from the parent causes both records to become dirty", function() {
+ var Post = DS.Model.extend();
+
+ var Attachment = DS.Model.extend({
+ post: DS.belongsTo(Post)
+ });
+
+ Post.reopen({
+ attachment: DS.hasOne(Attachment)
+ });
+
+ store.load(Post, { id: 1, attachment: 1 });
+ store.load(Attachment, { id: 1, post: 1 });
+
+ var post = store.find(Post, 1);
+ var attachment = store.find(Attachment, 1);
+
+ post.set('attachment', null);
+
+ ok(post.get('isDirty'), "post should be dirty");
+ ok(attachment.get('isDirty'), "attachment should be dirty");
+});
+
test("If dirtyRecordsForAttributeChange does not add the record to the dirtyRecords set, it does not become dirty", function() {
store.load(Person, { id: 1, firstName: "Yehuda" });
var wycats = store.find(Person, 1);
View
33 packages/ember-data/tests/integration/embedded/embedded_dirtying_test.js
@@ -1,5 +1,5 @@
var attr = DS.attr;
-var Post, Comment, User, Vote, Blog;
+var Post, Comment, User, Vote, Blog, Avatar;
var Adapter, App;
var adapter, store, post;
var forEach = Ember.EnumerableUtils.forEach;
@@ -15,13 +15,21 @@ var forEach = Ember.EnumerableUtils.forEach;
// belongsTo / \
// / \ hasMany
// User Votes
+// /
+// /
+// Avatar
module("Dirtying of Embedded Records", {
setup: function() {
App = Ember.Namespace.create({ name: "App" });
+ Avatar = App.Avatar = DS.Model.extend({
+ filename: attr('string')
+ });
+
User = App.User = DS.Model.extend({
- name: attr('string')
+ name: attr('string'),
+ avatar: DS.hasOne(Avatar)
});
Vote = App.Vote = DS.Model.extend({
@@ -50,6 +58,10 @@ module("Dirtying of Embedded Records", {
Adapter = DS.RESTAdapter.extend();
+ Adapter.map(User, {
+ avatar: { embedded: 'always' }
+ });
+
Adapter.map(Comment, {
user: { embedded: 'always' },
votes: { embedded: 'always' }
@@ -78,13 +90,19 @@ module("Dirtying of Embedded Records", {
comments: [{
title: "Why not use a more lightweight solution?",
user: {
+ avatar: {
+ filename: "mongodb_user.jpg"
+ },
name: "mongodb_user"
},
votes: [ { voter: "tomdale" }, { voter: "wycats" } ]
},
{
title: "This does not seem to reflect the Unix philosophy haha",
user: {
+ avatar: {
+ filename: "microuser.jpg"
+ },
name: "microuser",
},
votes: [ { voter: "ebryn" } ]
@@ -110,6 +128,10 @@ function assertTreeIs(state) {
assertRecordIs(comment, state);
if (comment.get('user')) {
assertRecordIs(comment.get('user'), state);
+
+ if (comment.get('user.avatar')) {
+ assertRecordIs(comment.get('user.avatar'), state);
+ }
}
comment.get('votes').forEach(function(vote) {
assertRecordIs(vote, state);
@@ -136,6 +158,13 @@ test("Modifying a record embedded via a belongsTo relationship should dirty the
assertEmbeddedLoadNotDirtied();
});
+test("Modifying a record embedded via a hasOne relationship should dirty the entire tree", function() {
+ var avatar = post.get('comments.firstObject.user.avatar');
+ avatar.set('filename', "photo2.jpg");
+ assertTreeIs('dirty');
+ assertEmbeddedLoadNotDirtied();
+});
+
test("Modifying a record embedded via a hasMany relationship should dirty the entire tree", function() {
var vote = post.get('comments.firstObject.votes.firstObject');
vote.set('voter', "[dead]");
View
69 packages/ember-data/tests/integration/embedded/embedded_loading_test.js
@@ -8,6 +8,10 @@ var App = Ember.Namespace.create({
var Person = App.Person = DS.Model.extend();
+var Avatar = App.Avatar = DS.Model.extend({
+ filename: DS.attr('string')
+});
+
var Comment = App.Comment = DS.Model.extend({
user: DS.belongsTo(Person)
});
@@ -19,6 +23,7 @@ var Group = App.Group = DS.Model.extend({
Person.reopen({
name: DS.attr('string'),
+ avatar: DS.hasOne(Avatar),
group: DS.belongsTo(Group),
comments: DS.hasMany(Comment)
});
@@ -37,6 +42,7 @@ module("Embedded Loading", {
teardown: function() {
Ember.lookup = originalLookup;
+ store.destroy();
}
});
@@ -296,3 +302,66 @@ test("updating a embedded record with a belongsTo relationship is serialize corr
deepEqual(commentJSON, { id: 1, user: { id: 4, name: "Peter Pan", group: null }});
});
+test("A nested hasOne relationship can be marked as embedded via the `map` API", function() {
+ Adapter.map(Comment, {
+ user: { embedded: 'load' }
+ });
+
+ Adapter.map(Person, {
+ avatar: { embedded: 'load' }
+ });
+
+ adapter = Adapter.create();
+ store.set('adapter', adapter);
+
+ adapter.load(store, Comment, {
+ id: 1,
+ user: {
+ id: 2,
+ name: "Yehuda Katz",
+ avatar: {
+ id: 3,
+ filename: "photo.jpg"
+ }
+ }
+ });
+
+ var comment = store.find(Comment, 1);
+ var avatar = store.find(Avatar, 3);
+
+ strictEqual(avatar.get('filename'), "photo.jpg", "Avatar is addressable by its ID despite being loaded via embedding");
+ strictEqual(comment.get('user.avatar'), avatar, "relationship references the globally addressable record");
+});
+
+test("An embedded hasOne relationship is serialized correctly on update", function() {
+ Adapter.map(Person, {
+ avatar: { embedded: 'load' }
+ });
+
+ adapter = Adapter.create();
+ serializer = adapter.get('serializer');
+ store.set('adapter', adapter);
+
+ adapter.load(store, Person, {
+ id: 1,
+ name: "Yehuda Katz",
+ avatar: {
+ id: 2,
+ filename: "photo.jpg"
+ }
+ });
+ adapter.load(store, Avatar, {
+ id: 3,
+ filename: "photo2.jpg"
+ });
+
+ var person = store.find(Person, 1);
+ var avatar1 = store.find(Avatar, 2);
+ var avatar2 = store.find(Avatar, 3);
+
+ person.set('avatar', avatar2);
+ strictEqual(person.get('avatar'), avatar2, "updated relationship references the globally addressable record");
+
+ var personJSON = serializer.serialize(person, { includeId: true });
+ deepEqual(personJSON, { avatar: { id: 3, filename: "photo2.jpg" }, id: 1, name: "Yehuda Katz"});
+});
View
92 packages/ember-data/tests/integration/embedded/embedded_without_ids_test.js
@@ -1,13 +1,18 @@
var store, Adapter, adapter;
-var Post, Comment, User, Pingback, Like;
+var Post, Comment, User, Pingback, Like, Avatar;
var attr = DS.attr;
module("Embedded Relationships Without IDs", {
setup: function() {
var App = Ember.Namespace.create({ name: "App" });
+ Avatar = App.Avatar = DS.Model.extend({
+ filename: DS.attr('string')
+ });
+
User = App.User = DS.Model.extend({
- name: attr('string')
+ name: attr('string'),
+ avatar: DS.hasOne(Avatar)
});
Comment = App.Comment = DS.Model.extend({
@@ -28,6 +33,10 @@ module("Embedded Relationships Without IDs", {
Adapter = DS.RESTAdapter.extend();
+ Adapter.map(User, {
+ avatar: { embedded: 'always' }
+ });
+
Adapter.map(Comment, {
user: { embedded: 'always' }
});
@@ -93,6 +102,7 @@ asyncTest("Embedded belongsTo relationships can be saved when embedded: always i
comment: {
title: "Why not use a more lightweight solution?",
user: {
+ avatar: null,
name: "mongodb_expert"
}
}
@@ -125,6 +135,82 @@ asyncTest("Embedded belongsTo relationships can be saved when embedded: always i
}
});
+test("An embedded record can be accessed via a hasOne relationship but does not have an ID", function() {
+ adapter.load(store, User, {
+ id: 1,
+ name: "John Doe",
+ avatar: {
+ filename: "photo.jpg"
+ }
+ });
+
+ adapter.load(store, User, {
+ id: 2,
+ name: "Jane Doe",
+ avatar: {
+ filename: "photo2.jpg"
+ }
+ });
+
+ var user1 = store.find(User, 1);
+ var user2 = store.find(User, 2);
+
+ var avatar1 = user1.get('avatar');
+ var avatar2 = user2.get('avatar');
+
+ equal(avatar1.get('filename'), "photo.jpg", "the embedded record is found and its attributes are materialized");
+ equal(avatar1.get('id'), null, "the embedded record does not have an id");
+
+ equal(avatar2.get('filename'), "photo2.jpg", "the embedded record is found and its attributed are materialized");
+ equal(avatar2.get('id'), null, "the embedded record does not have an id");
+});
+
+asyncTest("Embedded hasOne relationships can be saved when embedded: always is true", function() {
+ adapter.load(store, User, {
+ id: 1,
+ name: "John Doe",
+ avatar: {
+ filename: "photo.jpg"
+ }
+ });
+
+ adapter.ajax = function(url, type, hash) {
+ deepEqual(hash.data, {
+ user: {
+ name: "John Doe",
+ avatar: {
+ filename: "photo2.jpg"
+ }
+ }
+ });
+
+ setTimeout(function() {
+ hash.success.call(hash.context);
+ done();
+ });
+ };
+
+ var transaction = store.transaction();
+
+ var user = store.find(User, 1);
+ var avatar = user.get('avatar');
+
+ transaction.add(avatar);
+ transaction.add(user);
+
+ avatar.set('filename', 'photo2.jpg');
+ equal(avatar.get('isDirty'), true, "avatar becomes dirty after changing a property");
+ equal(user.get('isDirty'), true, "user becomes dirty when its embedded avatar becomes dirty");
+
+ transaction.commit();
+
+ function done() {
+ equal(avatar.get('isDirty'), false, "avatar becomes clean after commit");
+ equal(user.get('isDirty'), false, "user becomes clean after commit");
+ start();
+ }
+});
+
test("Embedded records can be accessed via a hasMany relationship without having IDs", function() {
adapter.load(store, Post, {
id: 1,
@@ -269,12 +355,14 @@ asyncTest("Embedded records that contain embedded records can be saved", functio
comments: [{
title: "Wouldn't a more lightweight solution be better? This feels very monolithic.",
user: {
+ avatar: null,
name: "mongodb_user"
}
},
{
title: "This does not seem to reflect the Unix philosophy haha",
user: {
+ avatar: null,
name: "microuser"
}
}],
View
43 packages/ember-data/tests/integration/inverse_relationships_test.js
@@ -90,3 +90,46 @@ test("When a record's belongsTo relationship is set, it can specify the inverse
equal(post.get('youComments.length'), 1, "youComments had the post added");
equal(post.get('everyoneWeKnowComments.length'), 0, "everyoneWeKnowComments has no posts");
});
+
+test("When a record is added to a has-one relationship, the inverse belongsTo is determined automatically", function() {
+ var Person = DS.Model.extend(),
+ Address = DS.Model.extend();
+
+ Person.reopen({
+ address: DS.hasOne(Address)
+ });
+ Address.reopen({
+ person: DS.belongsTo(Person)
+ });
+
+ var person = store.createRecord(Person),
+ address = store.createRecord(Address);
+
+ equal(address.get('person'), null, "no person has been set on the address");
+
+ person.set('address', address);
+ equal(address.get('person'), person, "person was set on the address");
+});
+
+test("When a record is added to a has-one relationship, the inverse belongsTo can be set explicitly", function() {
+ var Person = DS.Model.extend(),
+ Address = DS.Model.extend();
+
+ Person.reopen({
+ address: DS.hasOne(Address, { inverse: 'tenant' })
+ });
+ Address.reopen({
+ landlord: DS.belongsTo(Person),
+ tenant: DS.belongsTo(Person)
+ });
+
+ var person = store.createRecord(Person),
+ address = store.createRecord(Address);
+
+ equal(address.get('landlord'), null, "landlord is initially null");
+ equal(address.get('tenant'), null, "tenant is initially null");
+
+ person.set('address', address);
+ equal(address.get('landlord'), null, "no landlord was set on the address");
+ equal(address.get('tenant'), person, "tenant was set on the address");
+});
View
27 packages/ember-data/tests/integration/json_serialization_test.js
@@ -109,28 +109,35 @@ test("by default, addId calls primaryKey", function() {
deepEqual(json, { __key__: "EWOT" });
});
-var Comment, comment;
+var Attachment, Comment, attachment, comment;
module("Adapter serialization with relationships", {
setup: function() {
store = DS.Store.create();
Post = DS.Model.extend();
+ Attachment = DS.Model.extend({
+ post: DS.belongsTo(Post)
+ });
+
Comment = DS.Model.extend({
post: DS.belongsTo(Post)
});
Post.reopen({
- comments: DS.hasMany(Comment)
+ comments: DS.hasMany(Comment),
+ attachment: DS.hasOne(Attachment)
});
serializer = DS.JSONSerializer.create();
post = store.createRecord(Post);
comment = store.createRecord(Comment);
+ attachment = store.createRecord(Attachment);
post.get('comments').pushObject(comment);
+ post.set('attachment', attachment);
},
teardown: function() {
@@ -152,6 +159,8 @@ test("calling serialize with a record with relationships invokes addRelationship
});
test("the default addRelationships calls addBelongsTo", function() {
+ expect(3);
+
serializer.addBelongsTo = function(hash, record, key, relationship) {
equal(relationship.kind, "belongsTo");
equal(key, 'post');
@@ -162,6 +171,8 @@ test("the default addRelationships calls addBelongsTo", function() {
});
test("the default addRelationships calls addHasMany", function() {
+ expect(3);
+
serializer.addHasMany = function(hash, record, key, relationship) {
equal(relationship.kind, "hasMany");
equal(key, 'comments');
@@ -171,6 +182,18 @@ test("the default addRelationships calls addHasMany", function() {
serializer.serialize(post);
});
+test("the default addRelationships calls addHasOne", function() {
+ expect(3);
+
+ serializer.addHasOne = function(hash, record, key, relationship) {
+ equal(relationship.kind, "hasOne");
+ equal(key, 'attachment');
+ equal(record, post);
+ };
+
+ serializer.serialize(post);
+});
+
test("loadValue should be called once per sideloaded type", function() {
var payload, loader, K = Ember.K, loadedTypes = [], App = Ember.Namespace.create({
toString: function() { return "App"; }
View
21 packages/ember-data/tests/integration/materialization_tests.js
@@ -151,6 +151,27 @@ test("when materializing a record, the serializer's extractHasMany method should
var person = store.find(Person, 1);
});
+test("when materializing a record, the serializer's extractHasOne method should be invoked", function() {
+ expect(3);
+
+ Person.reopen({
+ protege: DS.hasOne(Person)
+ });
+
+ store.load(Person, { id: 1, protege: 2 });
+
+ serializer.extractHasOne = function(type, hash, name) {
+ equal(type, Person);
+ deepEqual(hash, {
+ id: 1,
+ protege: 2
+ });
+ equal(name, 'protege');
+ };
+
+ var person = store.find(Person, 1);
+});
+
test("when materializing a record, the serializer's extractBelongsTo method should be invoked", function() {
expect(3);
View
16 packages/ember-data/tests/integration/relationships/introspection_test.js
@@ -1,4 +1,4 @@
-var Blog, User, Post;
+var Blog, User, Post, Config;
var lookup, oldLookup;
module("Relationship Introspection", {
@@ -8,10 +8,11 @@ module("Relationship Introspection", {
User = DS.Model.extend();
Post = DS.Model.extend();
+ Config = DS.Model.extend();
Blog = DS.Model.extend({
admins: DS.hasMany(User),
owner: DS.belongsTo(User),
-
+ config: DS.hasOne(Config),
posts: DS.hasMany(Post)
});
},
@@ -29,19 +30,23 @@ test("DS.Model class computed property `relationships` returns a map keyed on ty
expected = [{ name: 'posts', kind: 'hasMany' }];
deepEqual(relationships.get(Post), expected, "post relationships returns expected array");
+
+ expected = [{ name: 'config', kind: 'hasOne' }];
+ deepEqual(relationships.get(Config), expected, "config relationships returns expected array");
});
test("DS.Model class computed property `relationships` returns a map keyed on types when types are specified as strings", function() {
Blog = DS.Model.extend({
admins: DS.hasMany('User'),
owner: DS.belongsTo('User'),
-
+ config: DS.hasOne('Config'),
posts: DS.hasMany('Post')
});
Ember.lookup = {
User: DS.Model.extend(),
- Post: DS.Model.extend()
+ Post: DS.Model.extend(),
+ Config: DS.Model.extend()
};
var relationships = Ember.get(Blog, 'relationships');
@@ -51,4 +56,7 @@ test("DS.Model class computed property `relationships` returns a map keyed on ty
expected = [{ name: 'posts', kind: 'hasMany' }];
deepEqual(relationships.get(Ember.lookup.Post), expected, "post relationships returns expected array");
+
+ expected = [{ name: 'config', kind: 'hasOne' }];
+ deepEqual(relationships.get(Ember.lookup.Config), expected, "config relationships returns expected array");
});
View
9 packages/ember-data/tests/integration/relationships/inverse_test.js
@@ -24,6 +24,7 @@ module("Inverse test", {
});
test("One to one relationships should be identified correctly", function() {
+ var type;
App.Post = DS.Model.extend({
title: DS.attr('string')
@@ -35,11 +36,13 @@ test("One to one relationships should be identified correctly", function() {
});
App.Post.reopen({
- comment: DS.belongsTo(App.Comment)
+ comment: DS.hasOne(App.Comment)
});
- var type = DS.RelationshipChange.determineRelationshipType(App.Post, {key: "comment", kind: "belongsTo"});
-
+ type = DS.RelationshipChange.determineRelationshipType(App.Post, {key: "comment", kind: "hasOne"});
+ equal(type, "oneToOne", "Relationship type is oneToOne");
+
+ type = DS.RelationshipChange.determineRelationshipType(App.Comment, {key: "post", kind: "belongsTo"});
equal(type, "oneToOne", "Relationship type is oneToOne");
});
View
31 packages/ember-data/tests/integration/relationships/one_to_none_relationships_test.js
@@ -15,8 +15,13 @@ module("One-to-None Relationships", {
toString: function() { return "App"; }
});
+ App.Attachment = DS.Model.extend({
+ filename: DS.attr('string')
+ });
+
App.Post = DS.Model.extend({
- title: DS.attr('string')
+ title: DS.attr('string'),
+ attachment: DS.hasOne(App.Attachment)
});
App.Comment = DS.Model.extend({
@@ -51,7 +56,7 @@ test("Setting a record's belongsTo relationship to another record, should work",
});
test("Setting a record's belongsTo relationship to null should work", function() {
- store.load(App.Post, { id: 1, title: "parent", comments: [2, 3] });
+ store.load(App.Post, { id: 1, title: "parent" });
store.load(App.Comment, { id: 2, body: "child", post: 1 });
store.load(App.Comment, { id: 3, body: "child", post: 1 });
@@ -62,3 +67,25 @@ test("Setting a record's belongsTo relationship to null should work", function()
comment1.set('post', null);
equal(comment1.get('post'), null, "belongsTo relationship has been set to null");
});
+
+test("Setting a record's hasOne relationship to another record, should work", function() {
+ store.load(App.Post, { id: 1, title: "parent" });
+ store.load(App.Attachment, { id: 2, filename: "episode.mp3" });
+
+ var post = store.find(App.Post, 1),
+ attachment = store.find(App.Attachment, 2);
+
+ post.set('attachment', attachment);
+ deepEqual(post.get('attachment'), attachment, "post should have the correct attachment set");
+});
+
+test("Setting a record's hasOne relationship to null should work", function() {
+ store.load(App.Post, { id: 1, title: "parent", attachment: 2 });
+ store.load(App.Attachment, { id: 2, filename: "episode.mp3" });
+
+ var post = store.find(App.Post, 1),
+ attachment = store.find(App.Attachment, 2);
+
+ post.set('attachment', null);
+ equal(post.get('attachment'), null, "attachment has been set to null");
+});
View
16 packages/ember-data/tests/integration/relationships/one_to_one_relationships_test.js
@@ -25,7 +25,7 @@ module("One-to-One Relationships", {
});
App.Post.reopen({
- comment: DS.belongsTo(App.Comment)
+ comment: DS.hasOne(App.Comment)
});
},
@@ -41,7 +41,7 @@ function verifySynchronizedOneToOne(post, comment, expectedHasMany) {
equal(post.get('comment'), comment);
}
-test("When setting a record's belongsTo relationship to another record, that record should be added to the inverse belongsTo", function() {
+test("When setting a record's belongsTo relationship to another record, that record should be added to the inverse hasOne", function() {
store.load(App.Post, { id: 1, title: "parent" });
store.load(App.Comment, { id: 2, body: "child" });
@@ -51,6 +51,18 @@ test("When setting a record's belongsTo relationship to another record, that rec
comment.set('post', post);
verifySynchronizedOneToOne(post, comment);
});
+
+test("When setting a record's hasOne relationship to another record, that record should be added to the inverse belongsTo", function() {
+ store.load(App.Post, { id: 1, title: "parent" });
+ store.load(App.Comment, { id: 2, body: "child" });
+
+ var post = store.find(App.Post, 1),
+ comment = store.find(App.Comment, 2);
+
+ post.set('comment', comment);
+ verifySynchronizedOneToOne(post, comment);
+});
+
/*
test("When setting a record's belongsTo relationship to null, that record should be removed from the inverse hasMany array", function() {
store.load(App.Post, { id: 1, title: "parent", comments: [2, 3] });
View
481 packages/ember-data/tests/integration/rollback_test.js
@@ -1,25 +1,31 @@
-var store, Comment, Post;
+var store, Comment, Post, Attachment;
module("Transaction Rollback", {
setup: function() {
store = DS.Store.create({ adapter: 'DS.Adapter' });
- Post = DS.Model.extend({
- title: DS.attr('string')
- });
+ Post = DS.Model.extend();
+ Attachment = DS.Model.extend();
+ Comment = DS.Model.extend();
+ Post.reopen({
+ title: DS.attr('string'),
+ comments: DS.hasMany(Comment),
+ attachment: DS.hasOne(Attachment)
+ });
Post.toString = function() { return "Post"; };
- Comment = DS.Model.extend({
+ Comment.reopen({
title: DS.attr('string'),
post: DS.belongsTo(Post)
});
-
Comment.toString = function() { return "Comment"; };
- Post.reopen({
- comments: DS.hasMany(Comment)
+ Attachment.reopen({
+ url: DS.attr('string'),
+ post: DS.belongsTo(Post)
});
+ Attachment.toString = function() { return "Attachment"; };
},
teardown: function() {
@@ -75,9 +81,9 @@ test("A loaded record that is deleted and then rolled back is not dirty.", funct
ok(!post.get('isDeleted'), "record is not deleted");
});
-// UPDATED
+// UPDATED - belongsTo (for a hasMany)
-test("A loaded record in a transaction with a changed belongsTo should revert to the old relationship when the transaction is rolled back. (A=>null)", function() {
+test("A loaded record in a transaction with a changed belongsTo (for a hasMany) should revert to the old relationship when the transaction is rolled back. (A=>null)", function() {
store.load(Post, { id: 1, title: "My Darkest Node.js Fantasies", comments: [ 1, 2 ] });
store.load(Comment, { id: 1, title: "I don't see the appeal of Rails these days when Node.js and Django are both as mature and inherently more scalable than Rails", post: 1 });
store.load(Comment, { id: 2, title: "I was skeptical about http://App.net before I paid the $$, but now I am all excited about it.", post: 1 });
@@ -111,7 +117,7 @@ test("A loaded record in a transaction with a changed belongsTo should revert to
deepEqual(post.get('comments').toArray(), [ comment1, comment2 ], "property is rolled back to its original value");
});
-test("A loaded record in a transaction with a changed belongsTo should revert to the old relationship when the transaction is rolled back. (null=>A)", function() {
+test("A loaded record in a transaction with a changed belongsTo (for a hasMany) should revert to the old relationship when the transaction is rolled back. (null=>A)", function() {
store.load(Post, { id: 1, title: "My Darkest Node.js Fantasies" });
store.load(Comment, { id: 1, title: "I don't see the appeal of Rails these days when Node.js and Django are both as mature and inherently more scalable than Rails" });
store.load(Comment, { id: 2, title: "I was skeptical about http://App.net before I paid the $$, but now I am all excited about it." });
@@ -148,7 +154,7 @@ test("A loaded record in a transaction with a changed belongsTo should revert to
deepEqual(post.get('comments').toArray(), [ ], "property is rolled back to its original value");
});
-test("A loaded record in a transaction with a changed belongsTo should revert to the old relationship when the transaction is rolled back. (A=>B)", function() {
+test("A loaded record in a transaction with a changed belongsTo (for a hasMany) should revert to the old relationship when the transaction is rolled back. (A=>B)", function() {
store.load(Post, { id: 1, title: "My Darkest Node.js Fantasies", comments: [ 1, 2 ] });
store.load(Post, { id: 2, title: "VIM for iPad Best Practices" });
store.load(Comment, { id: 1, title: "I don't see the appeal of Rails these days when Node.js and Django are both as mature and inherently more scalable than Rails", post: 1 });
@@ -190,6 +196,221 @@ test("A loaded record in a transaction with a changed belongsTo should revert to
deepEqual(post.get('comments').toArray(), [ comment1, comment2 ], "property is rolled back to its original value");
});
+// UPDATED - belongsTo (for a hasOne)
+
+test("A loaded record in a transaction with a changed belongsTo (for a hasOne) should revert to the old relationship when the transaction is rolled back. (A=>null)", function() {
+ store.load(Post, { id: 1, title: "My Darkest Node.js Fantasies", attachment: 1 });
+ store.load(Attachment, { id: 1, url: "http://www.example.com/podcast/ep1.mp3", post: 1 });
+
+ var post = store.find(Post, 1);
+ var attachment = store.find(Attachment, 1);
+
+ var transaction = store.transaction();
+ transaction.add(post);
+
+ ok(!post.get('isDirty'), "precond - record should not yet be dirty");
+ ok(!attachment.get('isDirty'), "precond - record should not yet be dirty");
+
+ attachment.set('post', null);
+
+ ok(post.get('isDirty'), "precond - record should be dirty after change");
+ ok(attachment.get('isDirty'), "precond - record should be dirty after change");
+
+ equal(post.get('attachment'), null, "precond - property reflects changed value");
+ equal(attachment.get('post'), null, "precond - property reflects changed value");
+
+ transaction.rollback();
+
+ ok(!post.get('isDirty'), "record should not be dirty after rollback");
+ ok(!attachment.get('isDirty'), "record should not be dirty after rollback");
+
+ equal(post.get('attachment'), attachment, "property is rolled back to its original value");
+ equal(attachment.get('post'), post, "property is rolled back to its original value");
+});
+
+test("A loaded record in a transaction with a changed belongsTo (for a hasOne) should revert to the old relationship when the transaction is rolled back. (null=>A)", function() {
+ store.load(Post, { id: 1, title: "My Darkest Node.js Fantasies" });
+ store.load(Attachment, { id: 1, url: "http://www.example.com/podcast/ep1.mp3" });
+
+ var post = store.find(Post, 1);
+ var attachment = store.find(Attachment, 1);
+
+ equal(post.get('attachment'), null, "precond - the original value is null");
+ equal(attachment.get('post'), null, "precond - the original value is null");
+
+ var transaction = store.transaction();
+ transaction.add(attachment);
+
+ ok(!post.get('isDirty'), "precond - record should not yet be dirty");
+ ok(!attachment.get('isDirty'), "precond - record should not yet be dirty");
+
+ attachment.set('post', post);
+
+ ok(post.get('isDirty'), "precond - record should be dirty after change");
+ ok(attachment.get('isDirty'), "precond - record should be dirty after change");
+
+ equal(post.get('attachment'), attachment, "precond - property reflects changed value");
+ equal(attachment.get('post'), post, "precond - property reflects changed value");
+
+ transaction.rollback();
+
+ ok(!post.get('isDirty'), "record should not be dirty after rollback");
+ ok(!attachment.get('isDirty'), "record should not be dirty after rollback");
+
+ equal(post.get('attachment'), null, "property is rolled back to its original value");
+ equal(attachment.get('post'), null, "property is rolled back to its original value");
+});
+
+test("A loaded record in a transaction with a changed belongsTo (for a hasOne) should revert to the old relationship when the transaction is rolled back. (A=>B)", function() {
+ store.load(Post, { id: 1, title: "My Darkest Node.js Fantasies", attachment: 1 });
+ store.load(Post, { id: 2, title: "VIM for iPad Best Practices" });
+ store.load(Attachment, { id: 1, url: "http://www.example.com/podcast/ep1.mp3", post: 1 });
+
+ var post1 = store.find(Post, 1);
+ var post2 = store.find(Post, 2);
+ var attachment = store.find(Attachment, 1);
+
+ equal(post1.get('attachment'), attachment, "precond - the original value is the attachment");
+ equal(post2.get('attachment'), null, "precond - the original value is null");
+ equal(attachment.get('post'), post1, "precond - the original value is the first post");
+
+ var transaction = store.transaction();
+ transaction.add(attachment);
+
+ ok(!post1.get('isDirty'), "precond - record should not yet be dirty");
+ ok(!post2.get('isDirty'), "precond - record should not yet be dirty");
+ ok(!attachment.get('isDirty'), "precond - record should not yet be dirty");
+
+ attachment.set('post', post2);
+
+ ok(post1.get('isDirty'), "precond - record should be dirty after change");
+ ok(post2.get('isDirty'), "precond - record should be dirty after change");
+ ok(attachment.get('isDirty'), "precond - record should be dirty after change");
+
+ equal(post1.get('attachment'), null, "precond - property reflects changed value");
+ equal(post2.get('attachment'), attachment, "precond - property reflects changed value");
+ equal(attachment.get('post'), post2, "precond - property reflects changed value");
+
+ transaction.rollback();
+
+ ok(!post1.get('isDirty'), "record should not be dirty after rollback");
+ ok(!post2.get('isDirty'), "record should not be dirty after rollback");
+ ok(!attachment.get('isDirty'), "record should not be dirty after rollback");
+
+ equal(post1.get('attachment'), attachment, "property is rolled back to its original value");
+ equal(post2.get('attachment'), null, "property is rolled back to its original value");
+ equal(attachment.get('post'), post1, "property is rolled back to its original value");
+});
+
+// UPDATED - hasOne
+
+test("A loaded record in a transaction with a changed hasOne should revert to the old relationship when the transaction is rolled back. (A=>null)", function() {
+ store.load(Post, { id: 1, title: "My Darkest Node.js Fantasies", attachment: 1 });
+ store.load(Attachment, { id: 1, url: "http://www.example.com/podcast/ep1.mp3", post: 1 });
+
+ var post = store.find(Post, 1);
+ var attachment = store.find(Attachment, 1);
+
+ var transaction = store.transaction();
+ transaction.add(post);
+
+ ok(!post.get('isDirty'), "precond - record should not yet be dirty");
+ ok(!attachment.get('isDirty'), "precond - record should not yet be dirty");
+
+ post.set('attachment', null);
+
+ ok(post.get('isDirty'), "precond - record should be dirty after change");
+ ok(attachment.get('isDirty'), "precond - record should be dirty after change");
+
+ equal(post.get('attachment'), null, "precond - property reflects changed value");
+ equal(attachment.get('post'), null, "precond - property reflects changed value");
+
+ transaction.rollback();
+
+ ok(!post.get('isDirty'), "record should not be dirty after rollback");
+ ok(!attachment.get('isDirty'), "record should not be dirty after rollback");
+
+ equal(post.get('attachment'), attachment, "property is rolled back to its original value");
+ equal(attachment.get('post'), post, "property is rolled back to its original value");
+});
+
+test("A loaded record in a transaction with a changed hasOne should revert to the old relationship when the transaction is rolled back. (null=>A)", function() {
+ store.load(Post, { id: 1, title: "My Darkest Node.js Fantasies" });
+ store.load(Attachment, { id: 1, url: "http://www.example.com/podcast/ep1.mp3" });
+
+ var post = store.find(Post, 1);
+ var attachment = store.find(Attachment, 1);
+
+ equal(attachment.get('post'), null, "precond - the original value is null");
+ equal(post.get('attachment'), null, "precond - the original value is null");
+
+ var transaction = store.transaction();
+ transaction.add(post);
+
+ ok(!post.get('isDirty'), "precond - record should not yet be dirty");
+ ok(!attachment.get('isDirty'), "precond - record should not yet be dirty");
+
+ post.set('attachment', attachment);
+
+ ok(post.get('isDirty'), "precond - record should be dirty after change");
+ ok(attachment.get('isDirty'), "precond - record should be dirty after change");
+
+ equal(attachment.get('post'), post, "precond - property reflects changed value");
+ equal(post.get('attachment'), attachment, "precond - property reflects changed value");
+
+ transaction.rollback();
+
+ ok(!post.get('isDirty'), "record should not be dirty after rollback");
+ ok(!attachment.get('isDirty'), "record should not be dirty after rollback");
+
+ equal(attachment.get('post'), null, "property is rolled back to its original value");
+ equal(post.get('attachment'), null, "property is rolled back to its original value");
+});
+
+test("A loaded record in a transaction with a changed hasOne should revert to the old relationship when the transaction is rolled back. (A=>B)", function() {
+ store.load(Post, { id: 1, title: "My Darkest Node.js Fantasies", attachment: 1 });
+
+ store.load(Attachment, { id: 1, url: "http://www.example.com/podcast/ep1.mp3", post: 1 });
+ store.load(Attachment, { id: 2, url: "http://www.example.com/podcast/ep2.mp3" });
+
+ var post = store.find(Post, 1);
+ var attachment1 = store.find(Attachment, 1);
+ var attachment2 = store.find(Attachment, 2);
+
+ equal(post.get('attachment'), attachment1, "precond - the original value is the first attachment");
+ equal(attachment1.get('post'), post, "precond - the original value is the post");
+ equal(attachment2.get('post'), null, "precond - the original value is null");
+
+ var transaction = store.transaction();
+ transaction.add(post);
+
+ ok(!post.get('isDirty'), "precond - record should not yet be dirty");
+ ok(!attachment1.get('isDirty'), "precond - record should not yet be dirty");
+ ok(!attachment2.get('isDirty'), "precond - record should not yet be dirty");
+
+ post.set('attachment', attachment2);
+
+ ok(post.get('isDirty'), "precond - record should be dirty after change");
+ ok(attachment1.get('isDirty'), "precond - record should be dirty after change");
+ ok(attachment2.get('isDirty'), "precond - record should be dirty after change");
+
+ equal(post.get('attachment'), attachment2, "precond - property reflects changed value");
+ equal(attachment1.get('post'), null, "precond - property reflects changed value");
+ equal(attachment2.get('post'), post, "precond - property reflects changed value");
+
+ transaction.rollback();
+
+ ok(!post.get('isDirty'), "record should not be dirty after rollback");
+ ok(!attachment1.get('isDirty'), "record should not be dirty after rollback");
+ ok(!attachment2.get('isDirty'), "record should not be dirty after rollback");
+
+ equal(post.get('attachment'), attachment1, "property is rolled back to its original value");
+ equal(attachment1.get('post'), post, "property is rolled back to its original value");
+ equal(attachment2.get('post'), null, "property is rolled back to its original value");
+});
+
+// UPDATED - hasMany
+
test("A loaded record in a transaction with a changed hasMany should revert to the old relationship when the transaction is rolled back. (A=>null)", function() {
store.load(Post, { id: 1, title: "My Darkest Node.js Fantasies", comments: [ 1, 2 ] });
store.load(Comment, { id: 1, title: "I don't see the appeal of Rails these days when Node.js and Django are both as mature and inherently more scalable than Rails", post: 1 });
@@ -346,9 +567,9 @@ test("A loaded record in a transaction with a changed hasMany (without first rem
deepEqual(post.get('comments').toArray(), [ comment1, comment2 ], "property is rolled back to its original value");
});
-// CREATED - Changing belongsTo
+// CREATED - Changing belongsTo (for a hasMany)
-test("A created record in a transaction with a changed belongsTo (child is newly created, but parent is not) should revert to the old relationship when the transaction is rolled back. (null=>A)", function() {
+test("A created record in a transaction with a changed belongsTo (for a hasMany) (child is newly created, but parent is not) should revert to the old relationship when the transaction is rolled back. (null=>A)", function() {
store.load(Post, { id: 1, title: "My Darkest Node.js Fantasies", comments: [ 2 ] });
store.load(Comment, { id: 2, title: "I don't see the appeal of Rails these days when Node.js and Django are both as mature and inherently more scalable than Rails", post: 1 });
@@ -381,7 +602,7 @@ test("A created record in a transaction with a changed belongsTo (child is newly
deepEqual(post.get('comments').toArray(), [ comment2 ], "property is rolled back to its original value");
});
-test("A loaded record in a transaction with a changed belongsTo (parent is newly created, but child is not) should revert to the old relationship when the transaction is rolled back. (null=>A)", function() {
+test("A created record in a transaction with a changed belongsTo (for a hasMany) (parent is newly created, but child is not) should revert to the old relationship when the transaction is rolled back. (null=>A)", function() {
var transaction = store.transaction();
store.load(Comment, { id: 1, title: "I don't see the appeal of Rails these days when Node.js and Django are both as mature and inherently more scalable than Rails" });
@@ -416,7 +637,7 @@ test("A loaded record in a transaction with a changed belongsTo (parent is newly
deepEqual(post.get('comments').toArray(), [ ], "property is rolled back to its original value");
});
-test("A loaded record in a transaction with a changed belongsTo (parent and child are both newly created) should revert to the old relationship when the transaction is rolled back. (A=>B)", function() {
+test("A created record in a transaction with a changed belongsTo (for a hasMany) (parent and child are both newly created) should revert to the old relationship when the transaction is rolled back. (null=>A)", function() {
var transaction = store.transaction();
var post = transaction.createRecord(Post, { title: "My Darkest Node.js Fantasies" });
@@ -447,9 +668,101 @@ test("A loaded record in a transaction with a changed belongsTo (parent and chil
deepEqual(post.get('comments').toArray(), [ ], "property is rolled back to its original value");
});
+// CREATED - Changing belongsTo (for a hasOne)
+
+test("A created record in a transaction with a changed belongsTo (for a hasOne) (child is newly created, but parent is not) should revert to the old relationship when the transaction is rolled back. (null=>A)", function() {
+ store.load(Post, { id: 1, title: "My Darkest Node.js Fantasies" });
+
+ var transaction = store.transaction();
+
+ var post = store.find(Post, 1);
+ var attachment = transaction.createRecord(Attachment, { url: "http://www.example.com/podcast/ep1.mpe" });
+
+ equal(post.get('attachment'), null, "precond - the original value is null");
+ equal(attachment.get('post'), null, "precond - the original value is null");
+
+ transaction.add(post);
+
+ attachment.set('post', post);
+
+ equal(post.get('attachment'), attachment, "precond - the new value is the attachment");
+ equal(attachment.get('post'), post, "precond - the new value is the post");
+
+ ok(post.get('isDirty'), "precond - record should be dirty");
+ ok(attachment.get('isDirty'), "precond - record should be dirty");
+
+ transaction.rollback();
+
+ ok(!post.get('isDirty'), "record should not be dirty after rollback");
+ ok(!attachment.get('isDirty'), "record should not be dirty after rollback");
+
+ equal(post.get('attachment'), null, "property is rolled back to its original value");
+ equal(attachment.get('post'), null, "property is rolled back to its original value");
+});
+
+test("A created record in a transaction with a changed belongsTo (for a hasOne) (parent is newly created, but child is not) should revert to the old relationship when the transaction is rolled back. (null=>A)", function() {
+ var transaction = store.transaction();
+
+ store.load(Attachment, { id: 1, url: "http://www.example.com/podcast/ep1.mp3" });
+
+ var post = transaction.createRecord(Post, { title: "My Darkest Node.js Fantasies" });
+ var attachment = store.find(Attachment, 1);
+
+ equal(post.get('attachment'), null, "precond - the original value is null");
+ equal(attachment.get('post'), null, "precond - the original value is null");
+
+ ok(post.get('isDirty'), "precond - record should be dirty");
+ ok(!attachment.get('isDirty'), "precond - record should not yet be dirty");
+
+ attachment.set('post', post);
+
+ ok(post.get('isDirty'), "precond - record should be dirty after change");
+ ok(attachment.get('isDirty'), "precond - record should be dirty after change");
+
+ equal(attachment.get('post'), post, "precond - property reflects changed value");
+ equal(post.get('attachment'), attachment, "precond - property reflects changed value");
+
+ transaction.rollback();
+
+ ok(!post.get('isDirty'), "record should not be dirty after rollback");
+ ok(!attachment.get('isDirty'), "record should not be dirty after rollback");
+
+ equal(attachment.get('post'), null, "property is rolled back to its original value");
+ equal(post.get('attachment'), null, "property is rolled back to its original value");
+});
+
+test("A created record in a transaction with a changed belongsTo (for a hasOne) (parent and child are both newly created) should revert to the old relationship when the transaction is rolled back. (null=>A)", function() {
+ var transaction = store.transaction();
+
+ var post = transaction.createRecord(Post, { title: "My Darkest Node.js Fantasies" });
+ var attachment = transaction.createRecord(Attachment, { url: "http://www.example.com/podcast/ep1.mp3" });
+
+ equal(post.get('attachment'), null, "precond - the original value is null");
+ equal(attachment.get('post'), null, "precond - the original value is null");
+
+ ok(post.get('isDirty'), "precond - record should be dirty");
+ ok(attachment.get('isDirty'), "precond - record should be dirty");
+
+ attachment.set('post', post);
+
+ ok(post.get('isDirty'), "precond - record should be dirty after change");
+ ok(attachment.get('isDirty'), "precond - record should be dirty after change");
+
+ equal(post.get('attachment'), attachment, "precond - property reflects changed value");
+ equal(attachment.get('post'), post, "precond - property reflects changed value");
+
+ transaction.rollback();
+
+ ok(!post.get('isDirty'), "record should not be dirty after rollback");
+ ok(!attachment.get('isDirty'), "record should not be dirty after rollback");
+
+ equal(post.get('attachment'), null, "property is rolled back to its original value");
+ equal(attachment.get('post'), null, "property is rolled back to its original value");
+});
+
// CREATED - Changing hasMany
-test("A created record in a transaction with a changed hasMany (child is newly created, but parent is not) should revert to the old relationship when the transaction is rolled back. (null=>A)", function() {
+test("A created record in a transaction with a changed hasMany (child is newly created, but parent is not) should revert to the old relationship when the transaction is rolled back. (A=>B)", function() {
store.load(Post, { id: 1, title: "My Darkest Node.js Fantasies", comments: [ 2 ] });
store.load(Comment, { id: 2, title: "I don't see the appeal of Rails these days when Node.js and Django are both as mature and inherently more scalable than Rails", post: 1 });
@@ -483,7 +796,7 @@ test("A created record in a transaction with a changed hasMany (child is newly c
deepEqual(post.get('comments').toArray(), [ comment2 ], "property is rolled back to its original value");
});
-test("A created record in a transaction with a changed belongsTo (parent is newly created, but child is not) should revert to the old relationship when the transaction is rolled back. (null=>A)", function() {
+test("A created record in a transaction with a changed hasMany (parent is newly created, but child is not) should revert to the old relationship when the transaction is rolled back. (null=>A)", function() {
var transaction = store.transaction();
store.load(Comment, { id: 1, title: "I don't see the appeal of Rails these days when Node.js and Django are both as mature and inherently more scalable than Rails" });
@@ -518,7 +831,7 @@ test("A created record in a transaction with a changed belongsTo (parent is newl
deepEqual(post.get('comments').toArray(), [ ], "property is rolled back to its original value");
});
-test("A created record in a transaction with a changed belongsTo (parent and child are both newly created) should revert to the old relationship when the transaction is rolled back. (A=>B)", function() {
+test("A created record in a transaction with a changed hasMany (parent and child are both newly created) should revert to the old relationship when the transaction is rolled back. (null=>A)", function() {
var transaction = store.transaction();
var post = transaction.createRecord(Post, { title: "My Darkest Node.js Fantasies" });
@@ -549,6 +862,105 @@ test("A created record in a transaction with a changed belongsTo (parent and chi
deepEqual(post.get('comments').toArray(), [ ], "property is rolled back to its original value");
});
+// CREATED - Changing hasOne
+
+test("A created record in a transaction with a changed hasOne (child is newly created, but parent is not) should revert to the old relationship when the transaction is rolled back. (A=>B)", function() {
+ store.load(Post, { id: 1, title: "My Darkest Node.js Fantasies", attachment: 2 });
+ store.load(Attachment, { id: 2, url: "http://www.example.com/podcast/ep1.mp3", post: 1 });
+
+ var transaction = store.transaction();
+
+ var post = store.find(Post, 1);
+ var attachment1 = store.find(Attachment, 2);
+ var attachment2 = transaction.createRecord(Attachment, { url: "http://www.example.com/podcast/ep2.mp3" });
+
+ equal(post.get('attachment'), attachment1, "precond - the original value is the existing attachment");
+ equal(attachment1.get('post'), post, "precond - the original value is the post");
+ equal(attachment2.get('post'), null, "precond - the original value is null");
+
+ transaction.add(post);
+
+ post.set('attachment', attachment2);
+
+ equal(post.get('attachment'), attachment2, "precond - the new value is the new attachment");
+ equal(attachment1.get('post'), null, "precond - the new value is null");
+ equal(attachment2.get('post'), post, "precond - the new value is the post");
+
+ ok(post.get('isDirty'), "precond - record should be dirty");
+ ok(attachment1.get('isDirty'), "precond - record should be dirty");
+ ok(attachment2.get('isDirty'), "precond - record should be dirty");
+
+ transaction.rollback();
+
+ ok(!post.get('isDirty'), "record should not be dirty after rollback");
+ ok(!attachment1.get('isDirty'), "record should not be dirty after rollback");
+ ok(!attachment2.get('isDirty'), "record should not be dirty after rollback");
+
+ equal(post.get('attachment'), attachment1, "property is rolled back to its original value");
+ equal(attachment1.get('post'), post, "property is rolled back to its original value");
+ equal(attachment2.get('post'), null, "property is rolled back to its original value");
+});
+
+test("A created record in a transaction with a changed hasOne (parent is newly created, but child is not) should revert to the old relationship when the transaction is rolled back. (null=>A)", function() {
+ var transaction = store.transaction();
+
+ store.load(Attachment, { id: 1, url: "http://www.example.com/podcast/ep1.mp3" });
+
+ var post = transaction.createRecord(Post, { title: "My Darkest Node.js Fantasies" });
+ var attachment = store.find(Attachment, 1);
+
+ equal(post.get('attachment'), null, "precond - the original value is null");
+ equal(attachment.get('post'), null, "precond - the original value is null");
+
+ ok(post.get('isDirty'), "precond - record should be dirty");
+ ok(!attachment.get('isDirty'), "precond - record should not yet be dirty");
+
+ post.set('attachment', attachment);
+
+ ok(post.get('isDirty'), "precond - record should be dirty after change");
+ ok(attachment.get('isDirty'), "precond - record should be dirty after change");
+
+ equal(post.get('attachment'), attachment, "precond - property reflects changed value");
+ equal(attachment.get('post'), post, "precond - property reflects changed value");
+
+ transaction.rollback();
+
+ ok(!post.get('isDirty'), "record should not be dirty after rollback");
+ ok(!attachment.get('isDirty'), "record should not be dirty after rollback");
+
+ equal(post.get('attachment'), null, "property is rolled back to its original value");
+ equal(attachment.get('post'), null, "property is rolled back to its original value");
+});
+
+test("A created record in a transaction with a changed hasOne (parent and child are both newly created) should revert to the old relationship when the transaction is rolled back. (null=>A)", function() {
+ var transaction = store.transaction();
+
+ var post = transaction.createRecord(Post, { title: "My Darkest Node.js Fantasies" });
+ var attachment = transaction.createRecord(Attachment, { url: "http://www.example.com/podcast/ep1.mp3" });
+
+ equal(post.get('attachment'), null, "precond - the original value is null");
+ equal(attachment.get('post'), null, "precond - the original value is null");
+
+ ok(post.get('isDirty'), "precond - record should be dirty");
+ ok(attachment.get('isDirty'), "precond - record should be dirty");
+
+ post.set('attachment', attachment);
+
+ ok(post.get('isDirty'), "precond - record should be dirty after change");
+ ok(attachment.get('isDirty'), "precond - record should be dirty after change");
+
+ equal(post.get('attachment'), attachment, "precond - property reflects changed value");
+ equal(attachment.get('post'), post, "precond - property reflects changed value");
+
+ transaction.rollback();
+
+ ok(!post.get('isDirty'), "record should not be dirty after rollback");
+ ok(!attachment.get('isDirty'), "record should not be dirty after rollback");
+
+ equal(post.get('attachment'), null, "property is rolled back to its original value");
+ equal(attachment.get('post'), null, "property is rolled back to its original value");
+});
+
// DELETED
test("A deleted record should be restored to a hasMany relationship if the transaction is rolled back", function() {
@@ -586,6 +998,37 @@ test("A deleted record should be restored to a hasMany relationship if the trans
deepEqual(post.get('comments').toArray(), [ comment1, comment2 ], "property is rolled back to its original value");
});
+test("A deleted record should be restored to a hasOne relationship if the transaction is rolled back", function() {
+ store.load(Post, { id: 1, title: "My Darkest Node.js Fantasies", attachment: 1 });
+ store.load(Attachment, { id: 1, url: "http://www.example.com/podcast/ep1.mp3", post: 1 });
+
+ var post = store.find(Post, 1);
+ var attachment = store.find(Attachment, 1);
+
+ var transaction = store.transaction();
+ transaction.add(post);
+ transaction.add(attachment);
+
+ ok(!post.get('isDirty'), "precond - record should not yet be dirty");
+ ok(!attachment.get('isDirty'), "precond - record should not yet be dirty");
+
+ attachment.deleteRecord();
+
+ ok(post.get('isDirty'), "precond - record should be dirty after change");
+ ok(attachment.get('isDirty'), "precond - record should be dirty after change");
+
+ equal(attachment.get('post'), null, "precond - property reflects changed value");
+ equal(post.get('attachment'), null, "precond - deleted record is removed from parent's hasOne");
+
+ transaction.rollback();
+
+ ok(!post.get('isDirty'), "record should not be dirty after rollback");
+ ok(!attachment.get('isDirty'), "record should not be dirty after rollback");
+
+ equal(attachment.get('post'), post, "property is rolled back to its original value");
+ equal(post.get('attachment'), attachment, "property is rolled back to its original value");
+});
+
test("A deleted record should be restored to a belongsTo relationship if the transaction is rolled back", function() {
store.load(Post, { id: 1, title: "My Darkest Node.js Fantasies", comments: [ 1 ] });
store.load(Comment, { id: 1, title: "I don't see the appeal of Rails these days when Node.js and Django are both as mature and inherently more scalable than Rails", post: 1 });
View
36 packages/ember-data/tests/unit/json_serializer_test.js
@@ -3,6 +3,7 @@ var MockModel = Ember.Object.extend({
this.materializedAttributes = {};
this.hasMany = {};
this.belongsTo = {};
+ this.hasOne = {};
},
eachAttribute: function(callback, binding) {
@@ -37,6 +38,10 @@ var MockModel = Ember.Object.extend({
materializeBelongsTo: function(name, id) {
this.belongsTo[name] = id;
+ },
+
+ materializeHasOne: function(name, id) {
+ this.hasOne[name] = id;
}
});
@@ -107,13 +112,14 @@ test("Mapped attributes should be used when materializing a record from JSON.",
});
test("Mapped relationships should be used when serializing a record to JSON.", function() {
- expect(8);
+ expect(12);
- Person.relationships = { addresses: 'hasMany' };
+ Person.relationships = { addresses: 'hasMany', heart: 'hasOne' };
window.Address.relationships = { person: 'belongsTo' };
serializer.map(Person, {
- addresses: { key: 'ADDRESSES!' }
+ addresses: { key: 'ADDRESSES!' },
+ heart: { key: '<3' }
});
serializer.map('Address', {
@@ -147,16 +153,29 @@ test("Mapped relationships should be used when serializing a record to JSON.", f
});
};
+ serializer.addHasOne = function(hash, record, key, relationship) {
+ ok(typeof hash === 'object', "a hash to build is passed");
+ equal(record, person, "the record to serialize should be passed");
+ equal(key, '<3', "the key to add to the hash respects the mapping");
+
+ // The mocked record uses a simplified relationship description
+ deepEqual(relationship, {
+ kind: 'hasOne',
+ key: 'heart'
+ });
+ };
+
serializer.serialize(person);
serializer.serialize(address);
});
test("mapped relationships are respected when materializing a record from JSON", function() {
- Person.relationships = { addresses: 'hasMany' };
+ Person.relationships = { addresses: 'hasMany', heart: 'hasOne' };
window.Address.relationships