Skip to content

Commit

Permalink
Close the loop for relations - Fix #311
Browse files Browse the repository at this point in the history
  • Loading branch information
neumino committed Aug 13, 2015
1 parent 3bfcb7d commit 5591cb6
Show file tree
Hide file tree
Showing 4 changed files with 289 additions and 65 deletions.
11 changes: 4 additions & 7 deletions lib/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -1684,15 +1684,12 @@ Document.prototype.purge = function(callback) {
}).nodeify(callback);
}

Document.prototype.removeRelations = function(relationsToRemove) {
Document.prototype.removeRelation = function() {
var self = this;
var pk = self._getModel()._pk;
return new Promise(function(resolve, reject) {
self.getModel().get(self[pk]).removeRelations(relationsToRemove).run().then(function(result) {
self._merge(result);
resolve(self);
}).error(reject);
})

var query = self.getModel().get(this[pk])
return query.removeRelation.apply(query, arguments);
}

/**
Expand Down
170 changes: 131 additions & 39 deletions lib/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,16 @@ function Query(model, query, options, error) {
// defined this.error.
this._error = error;
}
this._pointWrite = false;
}

Query.prototype.setPostValidation = function() {
this._postValidation = true;
}

Query.prototype.setPointWrite = function() {
this._pointWrite = true;
}

/**
* Execute a Query and expect the results to be object(s) that can be converted
Expand Down Expand Up @@ -201,10 +209,13 @@ Query.prototype._validateQueryResult = function(result) {
}
return Promise.all(promises).then(function(result) {
if (self._isPointWrite()) {
if (result.length > 1) {
throw new Error('A point write returned multiple values')
}
return result[0];
}
return result;
}).error(function(error) {
}).catch(function(error) {
if (error instanceof Errors.DocumentNotFound) {
// Should we send back null?
}
Expand Down Expand Up @@ -451,7 +462,9 @@ Query.prototype.addRelation = function(field, joinedDocument) {
case 'hasOne':
case 'hasMany':
if (joinedDocument[joinedModel._pk] === undefined) {
return Promise.reject(new Error('Primary key for the joined document not found for a `hasOne/hasMany` relation.'));
return new Query(model, self, {},
new Error('Primary key for the joined document not found for a `hasOne/hasMany` relation.')
);
}
var updateValue = {};
updateValue[joins[field].rightKey] = self._query(joins[field].leftKey);
Expand All @@ -460,7 +473,9 @@ Query.prototype.addRelation = function(field, joinedDocument) {
var updateValue = {};
if (joinedDocument[joins[field].rightKey] === undefined) {
if (joinedDocument[joinedModel._pk] === undefined) {
return Promise.reject(new Error('The primary key or the joined key must be defined in the joined document for a `belongsTo` relation.'));
return new Query(model, self, {},
new Error('The primary key or the joined key must be defined in the joined document for a `belongsTo` relation.')
);
}
updateValue[joins[field].leftKey] = joinedModel.get(joinedDocument[joinedModel._pk]).bracket(joins[field].rightKey)._query;
}
Expand All @@ -474,7 +489,9 @@ Query.prototype.addRelation = function(field, joinedDocument) {
var link;
if (joinedDocument[joins[field].rightKey] === undefined) {
if (joinedDocument[joinedModel._pk] === undefined) {
return Promise.reject(new Error('The primary key or the joined key must be defined in the joined document for a `hasAndBelongsToMany` relation.'));
return new Query(model, self, {},
new Error('The primary key or the joined key must be defined in the joined document for a `hasAndBelongsToMany` relation.')
);
}
link = joinedModel.get(joinedDocument[joinedModel._pk]).bracket(joins[field].rightKey)._query
}
Expand All @@ -489,11 +506,11 @@ Query.prototype.addRelation = function(field, joinedDocument) {
return r.branch(
rightKey.lt(leftKey),
r.object(
'id', leftKey.add('_').add(rightKey),
'id', rightKey.add('_').add(leftKey),
joins[field].leftKey+"_"+joins[field].leftKey, [leftKey, rightKey]
),
r.object(
'id', rightKey.add('_').add(leftKey),
'id', leftKey.add('_').add(rightKey),
joins[field].leftKey+"_"+joins[field].leftKey, [leftKey, rightKey]
)
)
Expand All @@ -503,19 +520,35 @@ Query.prototype.addRelation = function(field, joinedDocument) {
else {
linkValue = self._query(joins[field].leftKey).do(function(leftKey) {
return link.do(function(rightKey) {
return r.branch(
rightKey.lt(leftKey),
r.object(
if (model.getTableName() < joinedModel.getTableName()) {
return r.object(
'id', leftKey.add('_').add(rightKey),
model.getTableName()+"_"+joins[field].leftKey, leftKey,
joinedModel.getTableName()+"_"+joins[field].rightKey,rightKey
),
r.object(
)
}
else if (model.getTableName() > joinedModel.getTableName()) {
return r.object(
'id', rightKey.add('_').add(leftKey),
model.getTableName()+"_"+joins[field].leftKey, leftKey,
joinedModel.getTableName()+"_"+joins[field].rightKey,rightKey
)
)
}
else {
return r.branch(
rightKey.lt(leftKey),
r.object(
'id', leftKey.add('_').add(rightKey),
model.getTableName()+"_"+joins[field].leftKey, leftKey,
joinedModel.getTableName()+"_"+joins[field].rightKey,rightKey
),
r.object(
'id', rightKey.add('_').add(leftKey),
model.getTableName()+"_"+joins[field].leftKey, leftKey,
joinedModel.getTableName()+"_"+joins[field].rightKey,rightKey
)
)
}
});
});
}
Expand All @@ -528,7 +561,9 @@ Query.prototype.addRelation = function(field, joinedDocument) {
)
}).execute()
default:
return Promise.reject(new Error('The provided field `'+field+'` does not store joined documents.'));
return new Query(model, self, {},
new Error('The provided field `'+field+'` does not store joined documents.')
);
}
}

Expand All @@ -546,71 +581,128 @@ Query.prototype.removeRelation = function(field, joinedDocument) {
var joinedModel = joins[field].model;
var r = self._model._thinky.r;

var queries = [];
var query;
switch (joins[field].type) {
case 'hasOne':
// TODO Check that getAll return only one document?
return joinedModel.getAll(self._query(joins[field].leftKey), {index: joins[field].rightKey}).replace(function(row) {
query = joinedModel.getAll(self._query(joins[field].leftKey), {index: joins[field].rightKey}).replace(function(row) {
return row.without(joins[field].rightKey)
})
break;
});
query.setPostValidation();
query.setPointWrite();
return query;
case 'hasMany':
if (joinedDocument === undefined) {
return joinedModel.getAll(self._query(joins[field].leftKey), {index: joins[field].rightKey}).replace(function(row) {
query = joinedModel.getAll(self._query(joins[field].leftKey), {index: joins[field].rightKey}).replace(function(row) {
return row.without(joins[field].rightKey)
})
}
else {
return joinedModel.getAll(r.expr(joinedDocument)(joinedModel._pk)).replace(function(row) {
query = joinedModel.getAll(r.expr(joinedDocument)(joinedModel._pk)).replace(function(row) {
return row.without(joins[field].rightKey)
})
}
break;
query.setPostValidation();
return query;
case 'belongsTo':
return self._query.replace(function(row) {
query = self.replace(function(row) {
return row.without(joins[field].leftKey)
})
break;
query.setPostValidation();
return query;
case 'hasAndBelongsToMany':
var linkModel = joins[field].linkModel;
if (joinedDocument === undefined) {
return self._query(joins[field].leftKey).do(function(keys) {
//TODO Handle no keys
query = self._query(joins[field].leftKey).do(function(leftKey) {
// range are not supported at the moment, so keys is an object and we don't have to worry about empty sequences
if ((model.getTableName() === joinedModel.getTableName())
&& (joins[field].leftKey === joins[field].rightKey)) {
return linkModel.getAll(keys, {index: joins[field].leftKey+'_'+joins[field].leftKey}).delete()
return linkModel.getAll(leftKey, {index: joins[field].leftKey+'_'+joins[field].leftKey}).delete()._query
}
else {
return linkModel.getAll(keys, {index: joins[field].link+'_'+joins[field].leftKey}).delete()
return linkModel.getAll(leftKey, {index: model.getTableName()+'_'+joins[field].leftKey}).delete()._query
}
}).do(function(result) {
return r.branch(
result('errors').eq(0),
true, // not relevant value
r.error(result('errors'))
)
)
})
}
else {
if (joinedDocument[joins[field].rightKey] === undefined) {
if (joinedDocument[joinedModel._pk] === undefined) {
return Promise.reject(new Error('The primary key or the joined key must be defined in the joined document for a `belongsTo` relation.'));
return new Query(model, self, {},
new Error('The primary key or the joined key must be defined in the joined document for a `hasAndBelongsToMany` relation.')
);
}

return self._query(join.leftKey).do(function(keys) {
return joinedModel.get(joinedDocument[joinedModel._pk]).bracket(joins[field].rightKey).do(function(rightKey) {
return linkModel.getAll(keys.add('_').add(rightKey)).delete()
});
})
if ((model.getTableName() === joinedModel.getTableName())
&& (joins[field].leftKey === joins[field].rightKey)) {
query = self._query(joins[field].leftKey).do(function(leftKey) {
return joinedModel.get(joinedDocument[joinedModel._pk]).bracket(joins[field].rightKey).do(function(rightKey) {
if (model.getTableName() < joinedModel.getTableName()) {
return linkModel.getAll(leftKey.add('_').add(rightKey)).delete()._query;
}
else if (model.getTableName() > joinedModel.getTableName()) {
return linkModel.getAll(rightKey.add('_').add(leftKey)).delete()._query;
}
else {
return r.branch(
leftKey.lt(rightKey),
linkModel.getAll(leftKey.add('_').add(rightKey)).delete()._query,
linkModel.getAll(rightKey.add('_').add(leftKey)).delete()._query
)
}
});
})
}
else {
query = self._query(joins[field].leftKey).do(function(leftKey) {
return joinedModel.get(joinedDocument[joinedModel._pk]).bracket(joins[field].rightKey).do(function(rightKey) {
if (model.getTableName() < joinedModel.getTableName()) {
return linkModel.getAll(leftKey.add('_').add(rightKey)).delete()._query
}
else if (model.getTableName() > joinedModel.getTableName()) {
return linkModel.getAll(rightKey.add('_').add(leftKey)).delete()._query
}
else {
return r.branch(
leftKey.lt(rightKey),
linkModel.getAll(leftKey.add('_').add(rightKey)).delete()._query,
linkModel.getAll(rightKey.add('_').add(leftKey)).delete()._query
)

}
});
})
}
}
else {
return self._query(join.leftKey).do(function(keys) {
linkModel.getAll(keys.add('_').add(joinedDocument(joins[field].rightKey))).delete()
query = self._query(joins[field].leftKey).do(function(leftKey) {
var rightKey = r.expr(joinedDocument[joins[field].rightKey]);
if (model.getTableName() < joinedModel.getTableName()) {
return linkModel.getAll(leftKey.add('_').add(rightKey)).delete()._query
}
else if (model.getTableName() > joinedModel.getTableName()) {
return linkModel.getAll(rightKey.add('_').add(leftKey)).delete()._query
}
else {
return r.branch(
leftKey.lt(rightKey),
linkModel.getAll(leftKey.add('_').add(rightKey)).delete()._query,
linkModel.getAll(rightKey.add('_').add(leftKey)).delete()._query
)

}
})
}
}
return query;
default:
return Promise.reject(new Error('The provided field `'+field+'` does not store joined documents.'));
return new Query(model, self, {},
new Error('The provided field `'+field+'` does not store joined documents.')
);
}
};

Expand Down Expand Up @@ -710,7 +802,7 @@ Query.prototype.removeRelation = function(field, joinedDocument) {
})();

Query.prototype._isPointWrite = function() {
return Array.isArray(this._query._query) &&
return this._pointWrite || (Array.isArray(this._query._query) &&
(this._query._query.length > 1) &&
Array.isArray(this._query._query[1]) &&
(this._query._query[1].length > 0) &&
Expand All @@ -719,7 +811,7 @@ Query.prototype._isPointWrite = function() {
Array.isArray(this._query._query[1][0][1]) &&
(this._query._query[1][0][1].length > 0) &&
Array.isArray(this._query._query[1][0][1][0]) &&
(this._query._query[1][0][1][0][0] === 16)
(this._query._query[1][0][1][0][0] === 16))
}

/**
Expand Down
18 changes: 8 additions & 10 deletions test/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -3053,7 +3053,7 @@ describe('hooks', function() {
});
});

describe('removeRelations', function(){
describe('removeRelation', function(){
afterEach(cleanTables);

it('should work for hasOne', function(done) {
Expand All @@ -3080,8 +3080,8 @@ describe('removeRelations', function(){
doc.otherDoc = otherDoc;

doc.saveAll().then(function(doc) {
return doc.removeRelations({otherDoc: true})
}).then(function(result) {
return doc.removeRelation('otherDoc')
}).then(function() {
return OtherModel.get(otherDoc.id).run()
}).then(function(doc) {
assert.equal(doc.foreignKey, undefined);
Expand Down Expand Up @@ -3113,7 +3113,7 @@ describe('removeRelations', function(){
doc.otherDocs = [otherDoc];

doc.saveAll().then(function(doc) {
return doc.removeRelations({otherDocs: true})
return doc.removeRelation('otherDocs')
}).then(function(doc) {
return OtherModel.get(otherDoc.id).run()
}).then(function(doc) {
Expand Down Expand Up @@ -3146,10 +3146,8 @@ describe('removeRelations', function(){
doc.otherDoc = otherDoc;

doc.saveAll().then(function(doc) {
return doc.removeRelations({otherDoc: true})
}).then(function(newDoc) {
assert.equal(doc.foreignKey, undefined);
assert.equal(newDoc.foreignKey, undefined);
return doc.removeRelation('otherDoc')
}).then(function() {
return Model.get(doc.id).run()
}).then(function(doc) {
assert.equal(doc.foreignKey, undefined);
Expand Down Expand Up @@ -3181,8 +3179,8 @@ describe('removeRelations', function(){
doc.otherDocs = [otherDoc];

doc.saveAll().then(function(doc) {
return doc.removeRelations({otherDocs: true})
}).then(function(doc) {
return doc.removeRelation('otherDocs')
}).then(function() {
return Model.get(doc.id).getJoin({otherDocs: true}).run()
}).then(function(doc) {
assert.equal(doc.otherDocs.length, 0);
Expand Down
Loading

0 comments on commit 5591cb6

Please sign in to comment.