Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'igor-pristine' into embedded-records

Conflicts:
	packages/ember-data/lib/system/relationships/ext.js
	packages/ember-data/lib/system/relationships/one_to_many_change.js
	packages/ember-data/tests/integration/transactions/basic_test.js
  • Loading branch information...
commit 2c93e094d25daab7e888828b8cf14bcd62a25d56 2 parents e644411 + 2e017f6
Tom Dale authored
View
54 packages/ember-data/lib/system/record_arrays/many_array.js
@@ -105,16 +105,13 @@ DS.ManyArray = DS.RecordArray.extend({
//var record = this.objectAt(i);
//if (!record) { continue; }
- var change = DS.OneToManyChange.createChange(reference.clientId, get(this, 'store'), {
+ var change = DS.RelationshipChange.createChange(owner.get('clientId'), reference.clientId, get(this, 'store'), {
parentType: owner.constructor,
- hasManyName: name,
- parentClientId: owner.get('clientId'),
- changeType: "remove"
+ changeType: "remove",
+ kind: "hasMany",
+ key: name
});
- change.hasManyName = name;
- if (change.oldParent === undefined) { change.oldParent = get(owner, 'clientId'); }
- change.newParent = null;
this._changesToSync.add(change);
}
}
@@ -137,17 +134,14 @@ DS.ManyArray = DS.RecordArray.extend({
for (var i=index; i<index+added; i++) {
var reference = get(this, 'content').objectAt(i);
- var change = DS.OneToManyChange.createChange(reference.clientId, store, {
+ var change = DS.RelationshipChange.createChange(owner.get('clientId'), reference.clientId, store, {
parentType: owner.constructor,
- hasManyName: name,
- parentClientId: owner.get('clientId'),
- changeType: "add"
+ changeType: "add",
+ kind:"hasMany",
+ key: name
});
change.hasManyName = name;
- // The oldParent will be looked up in `sync` if it
- // was not set by `belongsToWillChange`.
- change.newParent = get(owner, 'clientId');
this._changesToSync.add(change);
}
@@ -177,36 +171,6 @@ DS.ManyArray = DS.RecordArray.extend({
this.pushObject(record);
return record;
- },
-
- /**
- METHODS FOR USE BY INVERSE RELATIONSHIPS
- ========================================
-
- These methods exists so that belongsTo relationships can
- set their inverses without causing an infinite loop.
-
- This creates two APIs:
-
- * the normal enumerable API, which is used by clients
- of the `ManyArray` and triggers a change to inverse
- `belongsTo` relationships.
- * `removeFromContent` and `addToContent`, which are
- used by inverse relationships and do not trigger a
- change to `belongsTo` relationships.
-
- Unlike the normal `addObject` and `removeObject` APIs,
- these APIs manipulate the `content` array without
- triggering side-effects.
- */
-
- /** @private */
- removeFromContent: function(record) {
- get(this, 'content').removeObject(get(record, 'reference'));
- },
-
- /** @private */
- addToContent: function(record) {
- get(this, 'content').addObject(get(record, 'reference'));
}
+
});
View
7 packages/ember-data/lib/system/relationships/belongs_to.js
@@ -34,9 +34,6 @@ DS.belongsTo = function(type, options) {
These observers observe all `belongsTo` relationships on the record. See
`relationships/ext` to see how these observers get their dependencies.
- The observers use `removeFromContent` and `addToContent` to avoid
- going through the public Enumerable API that would try to set the
- inverse (again) and trigger an infinite loop.
*/
DS.Model.reopen({
@@ -48,7 +45,7 @@ DS.Model.reopen({
var childId = get(record, 'clientId'),
store = get(record, 'store');
if (oldParent){
- var change = DS.OneToManyChange.createChange(childId, store, { belongsToName: key, parentClientId: get(oldParent,'clientId'), changeType: "remove" });
+ var change = DS.RelationshipChange.createChange(childId, get(oldParent, 'clientId'), store, { key: key, kind:"belongsTo", changeType: "remove" });
change.sync();
this._changesToSync[key] = change;
}
@@ -62,7 +59,7 @@ DS.Model.reopen({
if(newParent){
var childId = get(record, 'clientId'),
store = get(record, 'store');
- var change = DS.OneToManyChange.createChange(childId, store, { belongsToName: key, parentClientId: get(newParent, 'clientId'), changeType: "add" });
+ var change = DS.RelationshipChange.createChange(childId, get(newParent, 'clientId'), store, { key: key, kind:"belongsTo", changeType: "add" });
change.sync();
if(this._changesToSync[key]){
DS.OneToManyChange.ensureSameTransaction([change, this._changesToSync[key]], store);
View
15 packages/ember-data/lib/system/relationships/ext.js
@@ -322,23 +322,14 @@ DS.Model.reopen({
Ember Data uses the name of the relationship returned to reflect the changed
relationship on the other side.
*/
-DS._inverseNameFor = function(modelType, inverseModelType, inverseRelationshipKind) {
+DS._inverseRelationshipFor = function(modelType, inverseModelType) {
var relationshipMap = get(modelType, 'relationships'),
possibleRelationships = relationshipMap.get(inverseModelType),
possible, actual, oldValue;
if (!possibleRelationships) { return; }
-
- for (var i = 0, l = possibleRelationships.length; i < l; i++) {
- possible = possibleRelationships[i];
-
- if (possible.kind === inverseRelationshipKind) {
- actual = possible;
- break;
- }
- }
-
- if (actual) { return actual.name; }
+ if (possibleRelationships.length > 1) { return; }
+ return possibleRelationships[0];
};
/**
View
353 packages/ember-data/lib/system/relationships/one_to_many_change.js
@@ -2,13 +2,15 @@ var get = Ember.get, set = Ember.set;
var forEach = Ember.EnumerableUtils.forEach;
DS.RelationshipChange = function(options) {
- this.oldParent = options.oldParent;
- this.child = options.child;
- this.belongsToName = options.belongsToName;
+ this.firstRecordClientId = options.firstRecordClientId;
+ this.firstRecordKind = options.firstRecordKind;
+ this.firstRecordName = options.firstRecordName;
+ this.secondRecordClientId = options.secondRecordClientId;
+ this.secondRecordKind = options.secondRecordKind;
+ this.secondRecordName = options.secondRecordName;
this.store = options.store;
this.committed = {};
this.changeType = options.changeType;
- this.parentClientId = options.parentClientId;
};
DS.RelationshipChangeAdd = function(options){
@@ -35,7 +37,12 @@ DS.RelationshipChangeRemove.create = function(options) {
};
DS.OneToManyChange = {};
-DS.OneToManyChange.create = function(options){
+DS.OneToNoneChange = {};
+DS.ManyToNoneChange = {};
+DS.OneToOneChange = {};
+DS.ManyToManyChange = {};
+
+DS.RelationshipChange._createChange = function(options){
if(options.changeType === "add"){
return DS.RelationshipChangeAdd.create(options);
}
@@ -44,34 +51,199 @@ DS.OneToManyChange.create = function(options){
}
};
+
+DS.RelationshipChange.determineRelationshipType = function(recordType, knownSide){
+ var knownKey = knownSide.key, key, type, otherContainerType,assoc;
+ var knownContainerType = knownSide.kind;
+ var options = recordType.metaForProperty(knownKey).options;
+ var otherType = DS._inverseTypeFor(recordType, knownKey);
+
+ if(options.inverse){
+ key = options.inverse;
+ otherContainerType = get(otherType, 'relationshipsByName').get(key).kind;
+ }
+ else if(assoc = DS._inverseRelationshipFor(otherType, recordType)){
+ key = assoc.name;
+ otherContainerType = assoc.kind;
+ }
+ if(!key){
+ return knownContainerType === "belongsTo" ? "oneToNone" : "manyToNone";
+ }
+ else{
+ if(otherContainerType === "belongsTo"){
+ return knownContainerType === "belongsTo" ? "oneToOne" : "manyToOne";
+ }
+ else{
+ return knownContainerType === "belongsTo" ? "oneToMany" : "manyToMany";
+ }
+ }
+
+};
+
+DS.RelationshipChange.createChange = function(firstRecordClientId, secondRecordClientId, store, options){
+ // Get the type of the child based on the child's client ID
+ var firstRecordType = store.typeForClientId(firstRecordClientId), key, changeType;
+ changeType = DS.RelationshipChange.determineRelationshipType(firstRecordType, options);
+ if (changeType === "oneToMany"){
+ return DS.OneToManyChange.createChange(firstRecordClientId, secondRecordClientId, store, options);
+ }
+ else if (changeType === "manyToOne"){
+ return DS.OneToManyChange.createChange(secondRecordClientId, firstRecordClientId, store, options);
+ }
+ else if (changeType === "oneToNone"){
+ return DS.OneToNoneChange.createChange(firstRecordClientId, "", store, options);
+ }
+ else if (changeType === "manyToNone"){
+ return DS.ManyToNoneChange.createChange(firstRecordClientId, "", store, options);
+ }
+ else if (changeType === "oneToOne"){
+ return DS.OneToOneChange.createChange(firstRecordClientId, secondRecordClientId, store, options);
+ }
+ else if (changeType === "manyToMany"){
+ return DS.ManyToManyChange.createChange(firstRecordClientId, secondRecordClientId, store, options);
+ }
+};
+
+/** @private */
+DS.OneToNoneChange.createChange = function(childClientId, parentClientId, store, options) {
+ var key = options.key;
+ var change = DS.RelationshipChange._createChange({
+ firstRecordClientId: childClientId,
+ store: store,
+ changeType: options.changeType,
+ firstRecordName: key,
+ firstRecordKind: "belongsTo"
+ });
+
+ store.addRelationshipChangeFor(childClientId, key, parentClientId, null, change);
+
+ return change;
+};
+
+/** @private */
+DS.ManyToNoneChange.createChange = function(childClientId, parentClientId, store, options) {
+ var key = options.key;
+ var change = DS.RelationshipChange._createChange({
+ secondRecordClientId: childClientId,
+ store: store,
+ changeType: options.changeType,
+ secondRecordName: options.key,
+ secondRecordKind: "hasMany"
+ });
+
+ store.addRelationshipChangeFor(childClientId, key, parentClientId, null, change);
+ return change;
+};
+
+
+/** @private */
+DS.ManyToManyChange.createChange = function(childClientId, parentClientId, store, options) {
+ // Get the type of the child based on the child's client ID
+ var childType = store.typeForClientId(childClientId), key;
+
+ // If the name of the belongsTo side of the relationship is specified,
+ // use that
+ // If the type of the parent is specified, look it up on the child's type
+ // definition.
+ key = options.key;
+
+ var change = DS.RelationshipChange._createChange({
+ firstRecordClientId: childClientId,
+ secondRecordClientId: parentClientId,
+ firstRecordKind: "hasMany",
+ secondRecordKind: "hasMany",
+ store: store,
+ changeType: options.changeType,
+ firstRecordName: key
+ });
+
+ store.addRelationshipChangeFor(childClientId, key, parentClientId, null, change);
+
+
+ return change;
+};
+
/** @private */
-DS.OneToManyChange.createChange = function(childClientId, store, options) {
+DS.OneToOneChange.createChange = function(childClientId, parentClientId, store, options) {
// Get the type of the child based on the child's client ID
var childType = store.typeForClientId(childClientId), key;
+
+ // If the name of the belongsTo side of the relationship is specified,
+ // use that
+ // If the type of the parent is specified, look it up on the child's type
+ // definition.
+ if (options.parentType) {
+ key = inverseBelongsToName(options.parentType, childType, options.key);
+ //DS.OneToOneChange.maintainInvariant( options, store, childClientId, key );
+ } else if (options.key) {
+ key = options.key;
+ } else {
+ Ember.assert("You must pass either a parentType or belongsToName option to OneToManyChange.forChildAndParent", false);
+ }
+
+ var change = DS.RelationshipChange._createChange({
+ firstRecordClientId: childClientId,
+ secondRecordClientId: parentClientId,
+ firstRecordKind: "belongsTo",
+ secondRecordKind: "belongsTo",
+ store: store,
+ changeType: options.changeType,
+ firstRecordName: key
+ });
+
+ store.addRelationshipChangeFor(childClientId, key, parentClientId, null, change);
+
+
+ return change;
+};
+
+DS.OneToOneChange.maintainInvariant = function(options, store, childClientId, key){
+ if (options.changeType === "add" && store.recordIsMaterialized(childClientId)) {
+ var child = store.findByClientId(null, childClientId);
+ var oldParent = get(child, key);
+ if (oldParent){
+ var correspondingChange = DS.OneToOneChange.createChange(childClientId, oldParent.get('clientId'), store, {
+ parentType: options.parentType,
+ hasManyName: options.hasManyName,
+ changeType: "remove",
+ key: options.key
+ });
+ store.addRelationshipChangeFor(childClientId, key, options.parentClientId , null, correspondingChange);
+ correspondingChange.sync();
+ }
+ }
+};
+/** @private */
+DS.OneToManyChange.createChange = function(childClientId, parentClientId, store, options) {
+ // Get the type of the child based on the child's client ID
+ var childType = store.typeForClientId(childClientId), key;
+
// If the name of the belongsTo side of the relationship is specified,
// use that
// If the type of the parent is specified, look it up on the child's type
// definition.
if (options.parentType) {
- key = inverseBelongsToName(options.parentType, childType, options.hasManyName);
+ key = inverseBelongsToName(options.parentType, childType, options.key);
DS.OneToManyChange.maintainInvariant( options, store, childClientId, key );
- } else if (options.belongsToName) {
- key = options.belongsToName;
+ } else if (options.key) {
+ key = options.key;
} else {
Ember.assert("You must pass either a parentType or belongsToName option to OneToManyChange.forChildAndParent", false);
}
- var change = DS.OneToManyChange.create({
- child: childClientId,
- parentClientId: options.parentClientId,
+ var change = DS.RelationshipChange._createChange({
+ firstRecordClientId: childClientId,
+ secondRecordClientId: parentClientId,
+ firstRecordKind: "belongsTo",
+ secondRecordKind: "hasMany",
store: store,
- changeType: options.changeType
+ changeType: options.changeType,
+ firstRecordName: key
});
- store.addRelationshipChangeFor(childClientId, key, options.parentClientId, null, change);
+ store.addRelationshipChangeFor(childClientId, key, parentClientId, null, change);
- change.belongsToName = key;
return change;
};
@@ -82,11 +254,11 @@ DS.OneToManyChange.maintainInvariant = function(options, store, childClientId, k
var child = store.findByClientId(null, childClientId);
var oldParent = get(child, key);
if (oldParent){
- var correspondingChange = DS.OneToManyChange.createChange(childClientId, store, {
+ var correspondingChange = DS.OneToManyChange.createChange(childClientId, oldParent.get('clientId'), store, {
parentType: options.parentType,
hasManyName: options.hasManyName,
- parentClientId: oldParent.get('clientId'),
- changeType: "remove"
+ changeType: "remove",
+ key: options.key
});
store.addRelationshipChangeFor(childClientId, key, options.parentClientId , null, correspondingChange);
correspondingChange.sync();
@@ -97,8 +269,8 @@ DS.OneToManyChange.maintainInvariant = function(options, store, childClientId, k
DS.OneToManyChange.ensureSameTransaction = function(changes, store){
var records = Ember.A();
forEach(changes, function(change){
- records.addObject(change.getParent());
- records.addObject(change.getChild());
+ records.addObject(change.getSecondRecord());
+ records.addObject(change.getFirstRecord());
});
var transaction = store.ensureSameTransaction(records);
forEach(changes, function(change){
@@ -107,6 +279,7 @@ DS.OneToManyChange.ensureSameTransaction = function(changes, store){
};
DS.RelationshipChange.prototype = {
+
/**
Get the child type and ID, if available.
@@ -116,17 +289,17 @@ DS.RelationshipChange.prototype = {
return this.getTypeAndIdFor(this.child);
},
- getHasManyName: function() {
- var name = this.hasManyName, store = this.store, parent;
+ getSecondRecordName: function() {
+ var name = this.secondRecordName, store = this.store, parent;
if (!name) {
- parent = this.parentClientId;
+ parent = this.secondRecordClientId;
if (!parent) { return; }
- var childType = store.typeForClientId(this.child);
- var inverseType = DS._inverseTypeFor(childType, this.belongsToName);
- name = inverseHasManyName(inverseType, childType, this.belongsToName);
- this.hasManyName = name;
+ var childType = store.typeForClientId(this.firstRecordClientId);
+ var inverseType = DS._inverseTypeFor(childType, this.firstRecordName);
+ name = inverseHasManyName(inverseType, childType, this.firstRecordName);
+ this.secondRecordName = name;
}
return name;
@@ -137,18 +310,19 @@ DS.RelationshipChange.prototype = {
@returns {String}
*/
- getBelongsToName: function() {
- var name = this.belongsToName, store = this.store, parent;
+ getFirstRecordName: function() {
+ var name = this.firstRecordName, store = this.store, parent;
if (!name) {
- parent = this.oldParent || this.newParent;
+ parent = this.secondRecordClientId;
if (!parent) { return; }
- var childType = store.typeForClientId(this.child);
+ var childType = store.typeForClientId(this.firstRecordClientId);
var parentType = store.typeForClientId(parent);
- name = DS._inverseNameFor(childType, parentType, 'belongsTo', this.hasManyName);
+ if (!(childType && parentType)) { return; }
+ name = DS._inverseRelationshipFor(childType, parentType).name;
- this.belongsToName = name;
+ this.firstRecordName = name;
}
return name;
@@ -168,13 +342,13 @@ DS.RelationshipChange.prototype = {
/** @private */
destroy: function() {
- var childClientId = this.child,
- belongsToName = this.getBelongsToName(),
- hasManyName = this.getHasManyName(),
+ var childClientId = this.firstRecordClientId,
+ belongsToName = this.getFirstRecordName(),
+ hasManyName = this.getSecondRecordName(),
store = this.store,
child, oldParent, newParent, lastParent, transaction;
- store.removeRelationshipChangeFor(childClientId, belongsToName, this.parentClientId, hasManyName, this.changeType);
+ store.removeRelationshipChangeFor(childClientId, belongsToName, this.secondRecordClientId, hasManyName, this.changeType);
if (transaction = this.transaction) {
transaction.relationshipBecameClean(this);
@@ -193,13 +367,13 @@ DS.RelationshipChange.prototype = {
}
},
- getParent: function(){
- return this.getByClientId(this.parentClientId);
+ getSecondRecord: function(){
+ return this.getByClientId(this.secondRecordClientId);
},
/** @private */
- getChild: function() {
- return this.getByClientId(this.child);
+ getFirstRecord: function() {
+ return this.getByClientId(this.firstRecordClientId);
},
/**
@@ -211,8 +385,8 @@ DS.RelationshipChange.prototype = {
them all into that transaction.
*/
ensureSameTransaction: function() {
- var child = this.getChild(),
- parentRecord = this.getParent();
+ var child = this.getFirstRecord(),
+ parentRecord = this.getSecondRecord();
var transaction = this.store.ensureSameTransaction([child, parentRecord]);
@@ -221,10 +395,10 @@ DS.RelationshipChange.prototype = {
},
callChangeEvents: function(){
- var hasManyName = this.getHasManyName(),
- belongsToName = this.getBelongsToName(),
- child = this.getChild(),
- parentRecord = this.getParent();
+ var hasManyName = this.getSecondRecordName(),
+ belongsToName = this.getFirstRecordName(),
+ child = this.getFirstRecord(),
+ parentRecord = this.getSecondRecord();
var dirtySet = new Ember.OrderedSet();
@@ -247,15 +421,13 @@ DS.RelationshipChange.prototype = {
},
coalesce: function(){
- var relationshipPairs = this.store.relationshipChangePairsFor(this.child);
+ var relationshipPairs = this.store.relationshipChangePairsFor(this.firstRecordClientId);
forEach(relationshipPairs, function(pair){
var addedChange = pair["add"];
var removedChange = pair["remove"];
if(addedChange && removedChange) {
- window.coalescing = true;
addedChange.destroy();
removedChange.destroy();
- window.coalescing = false;
}
});
}
@@ -266,10 +438,10 @@ DS.RelationshipChangeRemove.prototype = Ember.create(DS.RelationshipChange.creat
DS.RelationshipChangeAdd.prototype.changeType = "add";
DS.RelationshipChangeAdd.prototype.sync = function() {
- var hasManyName = this.getHasManyName(),
- belongsToName = this.getBelongsToName(),
- child = this.getChild(),
- parentRecord = this.getParent();
+ var secondRecordName = this.getSecondRecordName(),
+ firstRecordName = this.getFirstRecordName(),
+ firstRecord = this.getFirstRecord(),
+ secondRecord = this.getSecondRecord();
//Ember.assert("You specified a hasMany (" + hasManyName + ") on " + (!belongsToName && (newParent || oldParent || this.lastParent).constructor) + " but did not specify an inverse belongsTo on " + child.constructor, belongsToName);
//Ember.assert("You specified a belongsTo (" + belongsToName + ") on " + child.constructor + " but did not specify an inverse hasMany on " + (!hasManyName && (newParent || oldParent || this.lastParentRecord).constructor), hasManyName);
@@ -279,16 +451,31 @@ DS.RelationshipChangeAdd.prototype.sync = function() {
this.callChangeEvents();
- if (parentRecord) {
- parentRecord.suspendRelationshipObservers(function(){
- get(parentRecord, hasManyName).addObject(child);
- });
+ if (secondRecord && firstRecord) {
+ if(this.secondRecordKind === "belongsTo"){
+ secondRecord.suspendRelationshipObservers(function(){
+ set(secondRecord, secondRecordName, firstRecord);
+ });
+
+ }
+ else if(this.secondRecordKind === "hasMany"){
+ secondRecord.suspendRelationshipObservers(function(){
+ get(secondRecord, secondRecordName).addObject(firstRecord);
+ });
+ }
}
- if (child && get(child, belongsToName) !== parentRecord) {
- child.suspendRelationshipObservers(function(){
- set(child, belongsToName, parentRecord);
- });
+ if (firstRecord && secondRecord && get(firstRecord, firstRecordName) !== secondRecord) {
+ if(this.firstRecordKind === "belongsTo"){
+ firstRecord.suspendRelationshipObservers(function(){
+ set(firstRecord, firstRecordName, secondRecord);
+ });
+ }
+ else if(this.firstdRecordKind === "hasMany"){
+ firstRecord.suspendRelationshipObservers(function(){
+ get(firstRecord, firstRecordName).addObject(secondRecord);
+ });
+ }
}
this.coalesce();
@@ -296,29 +483,41 @@ DS.RelationshipChangeAdd.prototype.sync = function() {
DS.RelationshipChangeRemove.prototype.changeType = "remove";
DS.RelationshipChangeRemove.prototype.sync = function() {
- var hasManyName = this.getHasManyName(),
- belongsToName = this.getBelongsToName(),
- child = this.getChild(),
- parentRecord = this.getParent();
+ var secondRecordName = this.getSecondRecordName(),
+ firstRecordName = this.getFirstRecordName(),
+ firstRecord = this.getFirstRecord(),
+ secondRecord = this.getSecondRecord();
//Ember.assert("You specified a hasMany (" + hasManyName + ") on " + (!belongsToName && (newParent || oldParent || this.lastParent).constructor) + " but did not specify an inverse belongsTo on " + child.constructor, belongsToName);
//Ember.assert("You specified a belongsTo (" + belongsToName + ") on " + child.constructor + " but did not specify an inverse hasMany on " + (!hasManyName && (newParent || oldParent || this.lastParentRecord).constructor), hasManyName);
- var transaction = this.ensureSameTransaction(child, parentRecord, hasManyName, belongsToName);
+ var transaction = this.ensureSameTransaction(firstRecord, secondRecord, secondRecordName, firstRecordName);
transaction.relationshipBecameDirty(this);
this.callChangeEvents();
- if (parentRecord) {
- parentRecord.suspendRelationshipObservers(function(){
- get(parentRecord, hasManyName).removeObject(child);
- });
+ if (secondRecord && firstRecord) {
+ if(this.secondRecordKind === "belongsTo"){
+ set(secondRecord, secondRecordName, null);
+ }
+ else if(this.secondRecordKind === "hasMany"){
+ secondRecord.suspendRelationshipObservers(function(){
+ get(secondRecord, secondRecordName).removeObject(firstRecord);
+ });
+ }
}
- if (child && get(child, belongsToName)) {
- child.suspendRelationshipObservers(function(){
- set(child, belongsToName, null);
- });
+ if (firstRecord && get(firstRecord, firstRecordName)) {
+ if(this.firstRecordKind === "belongsTo"){
+ firstRecord.suspendRelationshipObservers(function(){
+ set(firstRecord, firstRecordName, null);
+ });
+ }
+ else if(this.firstdRecordKind === "hasMany"){
+ firstRecord.suspendRelationshipObservers(function(){
+ get(firstRecord, firstRecordName).removeObject(secondRecord);
+ });
+ }
}
this.coalesce();
@@ -333,7 +532,7 @@ function inverseBelongsToName(parentType, childType, hasManyName) {
return belongsToName;
}
- return DS._inverseNameFor(childType, parentType, 'belongsTo');
+ return DS._inverseRelationshipFor(childType, parentType).name;
}
function inverseHasManyName(parentType, childType, belongsToName) {
@@ -344,5 +543,5 @@ function inverseHasManyName(parentType, childType, belongsToName) {
return hasManyName;
}
- return DS._inverseNameFor(parentType, childType, 'hasMany');
+ return DS._inverseRelationshipFor(parentType, childType).name;
}
View
137 packages/ember-data/tests/integration/relationships/inverse_test.js
@@ -0,0 +1,137 @@
+var get = Ember.get, set = Ember.set;
+
+var store, adapter, App, Post, Comment;
+
+module("Inverse test", {
+ setup: function() {
+ adapter = DS.Adapter.create();
+
+ store = DS.Store.create({
+ isDefaultStore: true,
+ adapter: adapter
+ });
+
+ App = Ember.Namespace.create({
+ toString: function() { return "App"; }
+ });
+ },
+
+ teardown: function() {
+ Ember.run(function() {
+ store.destroy();
+ });
+ }
+});
+
+test("One to one relationships should be identified correctly", function() {
+
+ App.Post = DS.Model.extend({
+ title: DS.attr('string')
+ });
+
+ App.Comment = DS.Model.extend({
+ body: DS.attr('string'),
+ post: DS.belongsTo(App.Post)
+ });
+
+ App.Post.reopen({
+ comment: DS.belongsTo(App.Comment)
+ });
+
+ var type = DS.RelationshipChange.determineRelationshipType(App.Post, {key: "comment", kind: "belongsTo"});
+
+ equal(type, "oneToOne", "Relationship type is oneToOne");
+});
+
+test("One to many relationships should be identified correctly", function() {
+
+ App.Post = DS.Model.extend({
+ title: DS.attr('string')
+ });
+
+ App.Comment = DS.Model.extend({
+ body: DS.attr('string'),
+ post: DS.hasMany(App.Post)
+ });
+
+ App.Post.reopen({
+ comment: DS.belongsTo(App.Comment)
+ });
+
+ var type = DS.RelationshipChange.determineRelationshipType(App.Post, {key: "comment", kind: "belongsTo"});
+
+ equal(type, "oneToMany", "Relationship type is oneToMany");
+});
+
+test("Many to one relationships should be identified correctly", function() {
+
+ App.Post = DS.Model.extend({
+ title: DS.attr('string')
+ });
+
+ App.Comment = DS.Model.extend({
+ body: DS.attr('string'),
+ post: DS.hasMany(App.Post)
+ });
+
+ App.Post.reopen({
+ comment: DS.belongsTo(App.Comment)
+ });
+
+ var type = DS.RelationshipChange.determineRelationshipType(App.Comment, {key: "post", kind: "hasMany"});
+
+ equal(type, "manyToOne", "Relationship type is manyToOne");
+});
+
+test("Many to many relationships should be identified correctly", function() {
+
+ App.Post = DS.Model.extend({
+ title: DS.attr('string')
+ });
+
+ App.Comment = DS.Model.extend({
+ body: DS.attr('string'),
+ post: DS.hasMany(App.Post)
+ });
+
+ App.Post.reopen({
+ comment: DS.belongsTo(App.Comment)
+ });
+
+ var type = DS.RelationshipChange.determineRelationshipType(App.Post, {key: "comment", kind: "hasMany"});
+
+ equal(type, "manyToMany", "Relationship type is manyTomany");
+});
+
+test("Many to none relationships should be identified correctly", function() {
+
+ App.Post = DS.Model.extend({
+ title: DS.attr('string')
+ });
+
+ App.Comment = DS.Model.extend({
+ body: DS.attr('string'),
+ post: DS.hasMany(App.Post)
+ });
+
+ var type = DS.RelationshipChange.determineRelationshipType(App.Comment, {key: "post", kind: "hasMany"});
+
+ equal(type, "manyToNone", "Relationship type is manyToNone");
+});
+
+test("One to none relationships should be identified correctly", function() {
+
+ App.Post = DS.Model.extend({
+ title: DS.attr('string')
+ });
+
+ App.Comment = DS.Model.extend({
+ body: DS.attr('string'),
+ post: DS.belongsTo(App.Post)
+ });
+
+ var type = DS.RelationshipChange.determineRelationshipType(App.Comment, {key: "post", kind: "belongsTo"});
+
+ equal(type, "oneToNone", "Relationship type is oneToNone");
+});
+
View
191 packages/ember-data/tests/integration/relationships/many_to_many_relationships_test.js
@@ -0,0 +1,191 @@
+var get = Ember.get, set = Ember.set;
+
+var store, adapter, App, Post, Comment;
+
+module("One-to-Many Relationships", {
+ setup: function() {
+ adapter = DS.Adapter.create();
+
+ store = DS.Store.create({
+ isDefaultStore: true,
+ adapter: adapter
+ });
+
+ App = Ember.Namespace.create({
+ toString: function() { return "App"; }
+ });
+
+ App.Post = DS.Model.extend({
+ title: DS.attr('string')
+ });
+
+ App.Comment = DS.Model.extend({
+ body: DS.attr('string'),
+ posts: DS.hasMany(App.Post)
+ });
+
+ App.Post.reopen({
+ comments: DS.hasMany(App.Comment)
+ });
+ },
+
+ teardown: function() {
+ Ember.run(function() {
+ store.destroy();
+ });
+ }
+});
+
+function verifySynchronizedManyToMany(post, comment, expectedHasMany) {
+ expectedHasMany = expectedHasMany || [comment];
+ deepEqual(post.get('comments').toArray(), [comment]);
+ deepEqual(comment.get('posts').toArray(), [post]);
+}
+
+test("When adding another record to a hasMany relationship, that record should be added to the inverse hasMany array", 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);
+
+ equal(post.get('comments.length'), 0, "precond - the post has no child comments yet");
+
+ comment.get('posts').addObject(post);
+ verifySynchronizedManyToMany(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] });
+ store.load(App.Comment, { id: 2, body: "child", post: 1 });
+ store.load(App.Comment, { id: 3, body: "child", post: 1 });
+
+ var post = store.find(App.Post, 1),
+ comment1 = store.find(App.Comment, 2),
+ comment2 = store.find(App.Comment, 3);
+
+ deepEqual(post.get('comments').toArray(), [comment1, comment2], "precond - the post has has two child comments");
+
+ comment1.set('post', null);
+ equal(comment1.get('post'), null, "belongsTo relationship has been set to null");
+ deepEqual(post.get('comments').toArray(), [ comment2 ], "the post comments array should have the remaining comment");
+});
+
+test("When adding a record to a hasMany array, its belongsTo is set", function() {
+ store.load(App.Post, { id: 1, title: "parent", comments: [2] });
+ store.load(App.Comment, { id: 2, body: "child", post: 1 });
+ store.load(App.Comment, { id: 3, body: "child" });
+
+ var post = store.find(App.Post, 1),
+ comment1 = store.find(App.Comment, 2),
+ comment2 = store.find(App.Comment, 3);
+
+ post.get('comments').addObject(comment2);
+ verifySynchronizedOneToMany(post, comment2, [comment1, comment2]);
+});
+
+test("When removing a record from a hasMany array, its belongsTo is set to null", function() {
+ store.load(App.Post, { id: 1, title: "parent", comments: [2, 3] });
+ store.load(App.Comment, { id: 2, body: "child", post: 1 });
+ store.load(App.Comment, { id: 3, body: "child", post: 1 });
+
+ var post = store.find(App.Post, 1),
+ comment1 = store.find(App.Comment, 2),
+ comment2 = store.find(App.Comment, 3);
+
+ post.get('comments').removeObject(comment1);
+ verifySynchronizedOneToMany(post, comment2);
+ equal(comment1.get('post'), null, "belongsTo relationship has been set to null");
+});
+
+test("When adding a record to a hasMany array, it should be removed from its old hasMany array, if there was one", function() {
+ store.load(App.Post, { id: 1, title: "old parent", comments: [3] });
+ store.load(App.Post, { id: 2, title: "new parent" });
+
+ store.load(App.Comment, { id: 3, body: "child", post: 1 });
+
+ var oldParent = store.find(App.Post, 1),
+ newParent = store.find(App.Post, 2),
+ child = store.find(App.Comment, 3);
+
+ verifySynchronizedOneToMany(oldParent, child);
+
+ newParent.get('comments').addObject(child);
+
+ deepEqual(oldParent.get('comments').toArray(), [], "old parent has no child comments");
+
+ verifySynchronizedOneToMany(newParent, child);
+});
+
+test("When changing a record's belongsTo, it should be removed from its old inverse hasMany array, if there was one", function() {
+ store.load(App.Post, { id: 1, title: "old parent", comments: [3] });
+ store.load(App.Post, { id: 2, title: "new parent" });
+
+ store.load(App.Comment, { id: 3, body: "child", post: 1 });
+
+ var oldParent = store.find(App.Post, 1),
+ newParent = store.find(App.Post, 2),
+ child = store.find(App.Comment, 3);
+
+ verifySynchronizedOneToMany(oldParent, child);
+
+ child.set('post', newParent);
+
+ deepEqual(oldParent.get('comments').toArray(), [], "old parent has no child comments");
+
+ verifySynchronizedOneToMany(newParent, child);
+});
+
+test("Deleting a record removes it from any inverse hasMany arrays to which it belongs.", function() {
+ var post, comment;
+
+ store.load(App.Post, { id: 1, title: "parent", comments: [1] });
+ store.load(App.Comment, { id: 1, title: "parent", post: 1 });
+
+ post = store.find(App.Post, 1);
+ comment = store.find(App.Comment, 1);
+
+ verifySynchronizedOneToMany(post, comment);
+
+ comment.deleteRecord();
+
+ equal(comment.get('post'), null, "the comment should no longer belong to a post");
+ deepEqual(post.get('comments').toArray(), [], "the post should no longer have any comments");
+});
+
+test("Deleting a newly created record removes it from any inverse hasMany arrays to which it belongs.", function() {
+ var post, comment;
+
+ store.load(App.Post, { id: 1, title: "parent" });
+
+ post = store.find(App.Post, 1);
+ comment = store.createRecord(App.Comment);
+
+ equal(comment.get('post'), null, "precond - the child should not yet belong to anyone");
+
+ post.get('comments').addObject(comment);
+
+ verifySynchronizedOneToMany(post, comment);
+
+ comment.deleteRecord();
+
+ equal(comment.get('post'), null, "the comment should no longer belong to a post");
+ deepEqual(post.get('comments').toArray(), [], "the post should no longer have any comments");
+});
+
+//test("When a record with a hasMany association is deleted, its associated record is materialized and its belongsTo is changed", function() {
+ //store.load(App.Post, { id: 1, title: "NEW! Ember Table", comments: [ 2 ] });
+ //store.load(App.Comment, { id: 2, body: "Needs more async", post: 1 });
+
+ //// Only find the post, not the comment. This ensures
+ //// that the comment is not yet materialized.
+ //var post = store.find(App.Post, 1);
+ //var comment = store.find(App.Comment, 2);
+ //post.deleteRecord();
+
+ //// Now that we've deleted the post, we should materialize the
+ //// comment and ensure that its inverse relationship has been
+ //// modified appropriately (i.e., set to null)
+ //equal(comment.get('post'), null, "the comment's post belongsTo relationship was set to null");
+//});
+*/
View
57 packages/ember-data/tests/integration/relationships/many_to_none_relationships_test.js
@@ -0,0 +1,57 @@
+var get = Ember.get, set = Ember.set;
+
+var store, adapter, App, Post, Comment;
+
+module("Many-to-None Relationships", {
+ setup: function() {
+ adapter = DS.Adapter.create();
+
+ store = DS.Store.create({
+ isDefaultStore: true,
+ adapter: adapter
+ });
+
+ App = Ember.Namespace.create({
+ toString: function() { return "App"; }
+ });
+
+ App.Comment = DS.Model.extend({
+ body: DS.attr('string'),
+ });
+
+ App.Post = DS.Model.extend({
+ title: DS.attr('string'),
+ comments: DS.hasMany(App.Comment)
+ });
+ },
+
+ teardown: function() {
+ Ember.run(function() {
+ store.destroy();
+ });
+ }
+});
+
+test("Adding a record to a hasMany relationship should work", 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.get('comments').pushObject(comment);
+ deepEqual(post.get('comments').toArray(), [comment], "post should have the comment added to its comments");
+});
+
+test("Removing a record from a hasMany relationship should work", function() {
+ store.load(App.Post, { id: 1, title: "parent", comments: [2, 3] });
+ store.load(App.Comment, { id: 2, body: "child" });
+ store.load(App.Comment, { id: 3, body: "child" });
+
+ var post = store.find(App.Post, 1),
+ comment1 = store.find(App.Comment, 2),
+ comment2 = store.find(App.Comment, 3);
+
+ post.get('comments').removeObject(comment1);
+ deepEqual(post.get('comments').toArray(), [comment2], "post should have the comment added to its comments");
+});
View
64 packages/ember-data/tests/integration/relationships/one_to_none_relationships_test.js
@@ -0,0 +1,64 @@
+var get = Ember.get, set = Ember.set;
+
+var store, adapter, App, Post, Comment;
+
+module("One-to-None Relationships", {
+ setup: function() {
+ adapter = DS.Adapter.create();
+
+ store = DS.Store.create({
+ isDefaultStore: true,
+ adapter: adapter
+ });
+
+ App = Ember.Namespace.create({
+ toString: function() { return "App"; }
+ });
+
+ App.Post = DS.Model.extend({
+ title: DS.attr('string')
+ });
+
+ App.Comment = DS.Model.extend({
+ body: DS.attr('string'),
+ post: DS.belongsTo(App.Post)
+ });
+
+ },
+
+ teardown: function() {
+ Ember.run(function() {
+ store.destroy();
+ });
+ }
+});
+
+function verifySynchronizedOneToMany(post, comment, expectedHasMany) {
+ expectedHasMany = expectedHasMany || [comment];
+ equal(comment.get('post'), post);
+ deepEqual(post.get('comments').toArray(), expectedHasMany);
+}
+
+test("Setting a record's belongsTo relationship to another record, should work", 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);
+
+ comment.set('post', post);
+ deepEqual(comment.get('post'), post, "comment should have the correct post set");
+});
+
+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.Comment, { id: 2, body: "child", post: 1 });
+ store.load(App.Comment, { id: 3, body: "child", post: 1 });
+
+ var post = store.find(App.Post, 1),
+ comment1 = store.find(App.Comment, 2),
+ comment2 = store.find(App.Comment, 3);
+
+ comment1.set('post', null);
+ equal(comment1.get('post'), null, "belongsTo relationship has been set to null");
+});
View
188 packages/ember-data/tests/integration/relationships/one_to_one_relationships_test.js
@@ -0,0 +1,188 @@
+var get = Ember.get, set = Ember.set;
+
+var store, adapter, App, Post, Comment;
+
+module("One-to-One Relationships", {
+ setup: function() {
+ adapter = DS.Adapter.create();
+
+ store = DS.Store.create({
+ isDefaultStore: true,
+ adapter: adapter
+ });
+
+ App = Ember.Namespace.create({
+ toString: function() { return "App"; }
+ });
+
+ App.Post = DS.Model.extend({
+ title: DS.attr('string')
+ });
+
+ App.Comment = DS.Model.extend({
+ body: DS.attr('string'),
+ post: DS.belongsTo(App.Post)
+ });
+
+ App.Post.reopen({
+ comment: DS.belongsTo(App.Comment)
+ });
+ },
+
+ teardown: function() {
+ Ember.run(function() {
+ store.destroy();
+ });
+ }
+});
+
+function verifySynchronizedOneToOne(post, comment, expectedHasMany) {
+ equal(comment.get('post'), post);
+ 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() {
+ 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);
+
+ comment.set('post', post);
+ 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] });
+ store.load(App.Comment, { id: 2, body: "child", post: 1 });
+ store.load(App.Comment, { id: 3, body: "child", post: 1 });
+
+ var post = store.find(App.Post, 1),
+ comment1 = store.find(App.Comment, 2),
+ comment2 = store.find(App.Comment, 3);
+
+ deepEqual(post.get('comments').toArray(), [comment1, comment2], "precond - the post has has two child comments");
+
+ comment1.set('post', null);
+ equal(comment1.get('post'), null, "belongsTo relationship has been set to null");
+ deepEqual(post.get('comments').toArray(), [ comment2 ], "the post comments array should have the remaining comment");
+});
+
+test("When adding a record to a hasMany array, its belongsTo is set", function() {
+ store.load(App.Post, { id: 1, title: "parent", comments: [2] });
+ store.load(App.Comment, { id: 2, body: "child", post: 1 });
+ store.load(App.Comment, { id: 3, body: "child" });
+
+ var post = store.find(App.Post, 1),
+ comment1 = store.find(App.Comment, 2),
+ comment2 = store.find(App.Comment, 3);
+
+ post.get('comments').addObject(comment2);
+ verifySynchronizedOneToMany(post, comment2, [comment1, comment2]);
+});
+
+test("When removing a record from a hasMany array, its belongsTo is set to null", function() {
+ store.load(App.Post, { id: 1, title: "parent", comments: [2, 3] });
+ store.load(App.Comment, { id: 2, body: "child", post: 1 });
+ store.load(App.Comment, { id: 3, body: "child", post: 1 });
+
+ var post = store.find(App.Post, 1),
+ comment1 = store.find(App.Comment, 2),
+ comment2 = store.find(App.Comment, 3);
+
+ post.get('comments').removeObject(comment1);
+ verifySynchronizedOneToMany(post, comment2);
+ equal(comment1.get('post'), null, "belongsTo relationship has been set to null");
+});
+
+test("When adding a record to a hasMany array, it should be removed from its old hasMany array, if there was one", function() {
+ store.load(App.Post, { id: 1, title: "old parent", comments: [3] });
+ store.load(App.Post, { id: 2, title: "new parent" });
+
+ store.load(App.Comment, { id: 3, body: "child", post: 1 });
+
+ var oldParent = store.find(App.Post, 1),
+ newParent = store.find(App.Post, 2),
+ child = store.find(App.Comment, 3);
+
+ verifySynchronizedOneToMany(oldParent, child);
+
+ newParent.get('comments').addObject(child);
+
+ deepEqual(oldParent.get('comments').toArray(), [], "old parent has no child comments");
+
+ verifySynchronizedOneToMany(newParent, child);
+});
+
+test("When changing a record's belongsTo, it should be removed from its old inverse hasMany array, if there was one", function() {
+ store.load(App.Post, { id: 1, title: "old parent", comments: [3] });
+ store.load(App.Post, { id: 2, title: "new parent" });
+
+ store.load(App.Comment, { id: 3, body: "child", post: 1 });
+
+ var oldParent = store.find(App.Post, 1),
+ newParent = store.find(App.Post, 2),
+ child = store.find(App.Comment, 3);
+
+ verifySynchronizedOneToMany(oldParent, child);
+
+ child.set('post', newParent);
+
+ deepEqual(oldParent.get('comments').toArray(), [], "old parent has no child comments");
+
+ verifySynchronizedOneToMany(newParent, child);
+});
+
+test("Deleting a record removes it from any inverse hasMany arrays to which it belongs.", function() {
+ var post, comment;
+
+ store.load(App.Post, { id: 1, title: "parent", comments: [1] });
+ store.load(App.Comment, { id: 1, title: "parent", post: 1 });
+
+ post = store.find(App.Post, 1);
+ comment = store.find(App.Comment, 1);
+
+ verifySynchronizedOneToMany(post, comment);
+
+ comment.deleteRecord();
+
+ equal(comment.get('post'), null, "the comment should no longer belong to a post");
+ deepEqual(post.get('comments').toArray(), [], "the post should no longer have any comments");
+});
+
+test("Deleting a newly created record removes it from any inverse hasMany arrays to which it belongs.", function() {
+ var post, comment;
+
+ store.load(App.Post, { id: 1, title: "parent" });
+
+ post = store.find(App.Post, 1);
+ comment = store.createRecord(App.Comment);
+
+ equal(comment.get('post'), null, "precond - the child should not yet belong to anyone");
+
+ post.get('comments').addObject(comment);
+
+ verifySynchronizedOneToMany(post, comment);
+
+ comment.deleteRecord();
+
+ equal(comment.get('post'), null, "the comment should no longer belong to a post");
+ deepEqual(post.get('comments').toArray(), [], "the post should no longer have any comments");
+});
+
+//test("When a record with a hasMany association is deleted, its associated record is materialized and its belongsTo is changed", function() {
+ //store.load(App.Post, { id: 1, title: "NEW! Ember Table", comments: [ 2 ] });
+ //store.load(App.Comment, { id: 2, body: "Needs more async", post: 1 });
+
+ //// Only find the post, not the comment. This ensures
+ //// that the comment is not yet materialized.
+ //var post = store.find(App.Post, 1);
+ //var comment = store.find(App.Comment, 2);
+ //post.deleteRecord();
+
+ //// Now that we've deleted the post, we should materialize the
+ //// comment and ensure that its inverse relationship has been
+ //// modified appropriately (i.e., set to null)
+ //equal(comment.get('post'), null, "the comment's post belongsTo relationship was set to null");
+//});
+*/
View
1  packages/ember-data/tests/integration/transactions/basic_test.js
@@ -308,4 +308,3 @@ test("modified records are reset when their transaction is rolled back", functio
equal(person.get('isValid'), true, "invalid record is now marked as valid");
});
-
View
10 packages/ember-data/tests/integration/transactions/relationships_test.js
@@ -14,7 +14,7 @@ Post.reopen({
comments: DS.hasMany(Comment)
});
-var adapter, store, transaction;
+var store, adapter, transaction;
module("Transactions and Relationships", {
setup: function() {
@@ -43,13 +43,13 @@ function expectRelationships(description) {
if (count) {
if(description[0]){
- QUnit.push(relationships[0].getParent() === description[0].parent, relationships[0].getParent(), description[0].parent, "oldParent is incorrect");
- QUnit.push(relationships[0].getChild() === description[0].child, relationships[0].child, description[0].child, "child in relationship 0 is incorrect");
+ QUnit.push(relationships[0].getSecondRecord() === description[0].parent, relationships[0].getSecondRecord(), description[0].parent, "oldParent is incorrect");
+ QUnit.push(relationships[0].getFirstRecord() === description[0].child, relationships[0].child, description[0].child, "child in relationship 0 is incorrect");
}
if(description[1]){
var relPosition = count === 2 ? 1 : 0;
- QUnit.push(relationships[relPosition].getChild() === description[1].child, relationships[relPosition].child, description[1].child, "child in relationship 1 is incorrect");
- QUnit.push(relationships[relPosition].getParent() === description[1].parent, relationships[relPosition].parent, description[1].parent, "newParent is incorrect");
+ QUnit.push(relationships[relPosition].getFirstRecord() === description[1].child, relationships[relPosition].child, description[1].child, "child in relationship 1 is incorrect");
+ QUnit.push(relationships[relPosition].getSecondRecord() === description[1].parent, relationships[relPosition].parent, description[1].parent, "newParent is incorrect");
}
}
}
Please sign in to comment.
Something went wrong with that request. Please try again.