node2neo-model
Advanced tools
Comparing version
@@ -0,1 +1,8 @@ | ||
## 0.0.3 (2013-10-19) | ||
- Updated to the latest node2neo-schema (validate now synchronous) | ||
- Fixed create and find bugs | ||
- Added getRelationships method | ||
## 0.0.2 (2013-10-18) | ||
@@ -2,0 +9,0 @@ - General bug fixing |
@@ -163,3 +163,3 @@ 'use strict'; | ||
//remove offending constraint from the _transactions list | ||
self._statements = self._statements.filter(filterConstraints); | ||
// self._statements = self._statements.filter(filterConstraints); | ||
@@ -206,4 +206,4 @@ self.applyIndexes(callback); | ||
results.results[0].data.forEach(function(element, index){ | ||
var id = results.results[0].data[index].row[0]; | ||
var node = results.results[0].data[index].row[1]; | ||
node._id = results.results[0].data[index].row[0]; | ||
@@ -218,11 +218,12 @@ // remove fields not required | ||
var rel; | ||
var rel = {}; | ||
// if there is a relationship as well | ||
if(results.results[0].data[index].row[2]){ | ||
rel = results.results[0].data[index].row[3]; | ||
rel.data = results.results[0].data[index].row[3]; | ||
rel.type = results.results[0].data[index].row[2]; | ||
rel._id = results.results[0].data[index].row[4]; | ||
} | ||
var response; | ||
if(rel) { | ||
if(rel._id) { | ||
response = { | ||
@@ -232,7 +233,5 @@ node:node, | ||
}; | ||
response.node._id = id; | ||
} | ||
else { | ||
response = node; | ||
response._id = id; | ||
} | ||
@@ -267,2 +266,3 @@ | ||
Model.prototype.findOneAndUpdate = readActions.findOneAndUpdate; | ||
Model.prototype.getRelationships = readActions.getRelationships; | ||
@@ -269,0 +269,0 @@ |
185
lib/read.js
@@ -12,7 +12,2 @@ 'use strict'; | ||
* | ||
* Fields will return object with only those fields | ||
* At present everything is being returned from the database so there is no performance benefit | ||
* This will be investigated in a later release | ||
* User.find({first:'Rory', last: 'Madden'}, 'first last gender', function(err, nodes){}) | ||
* | ||
* Options can include: limit, skip, using (to specify an index) or orderBy | ||
@@ -29,3 +24,2 @@ * limit: options = {limit: 10} | ||
* @param {Object} conditions Object of fields and value | ||
* @param {String} fields Space delimited string | ||
* @param {Object} options Object of options | ||
@@ -234,3 +228,3 @@ * @param {Function} callback | ||
// }); | ||
return this.find({ _id: id }, options, callback); | ||
return this.find({ _id: parseInt(id,10) }, options, callback); | ||
}; | ||
@@ -292,2 +286,177 @@ | ||
}); | ||
}; | ||
}; | ||
/** | ||
* Get relationships from a node | ||
* | ||
* conditions contains: | ||
* types: single relationship type or array of types to be returned | ||
* direction: 'to' or 'from'. Leave blank for both | ||
* label: the single label to return | ||
* nodeConditions: key:value query conditions to match data on the related nodes | ||
* relConditions: key:value query conditions to match data on the relationship | ||
* | ||
* | ||
* @param {Number} id The id of the node whose relationships you are searching | ||
* @param {Object} conditions Object containing query options for matching the relationships | ||
* @param {Object} options Limit, orderBy, skip and Using options. Optional. | ||
* @param {Function} callback | ||
* @return {Array} {rels: Array of relationships, nodes: Array of nodes}. | ||
* @api public | ||
*/ | ||
module.exports.getRelationships = function(id, conditions, options, callback){ | ||
if(typeof options === 'function'){ | ||
callback = options; | ||
options = {}; | ||
} | ||
if(typeof conditions === 'function'){ | ||
callback = conditions; | ||
conditions = {} | ||
} | ||
if(typeof id === 'function'){ | ||
callback = id; | ||
return callback(new Node2NeoError('Invalid getRelationships request. You must provide the id of the node to request its relationships.')); | ||
} | ||
// Assume no types | ||
var types = ''; | ||
if(conditions.types){ | ||
if (conditions.types instanceof Array) { | ||
conditions.types.forEach(function(element){ | ||
types += ':' + element + '| '; | ||
}); | ||
// clear the last | | ||
types = types.substr(0, types.length-2); | ||
} | ||
else{ | ||
types = ':' + conditions.types; | ||
} | ||
} | ||
var params = { | ||
id: parseInt(id, 10) | ||
}; | ||
var query = 'START n=node({id}) MATCH (n)'; | ||
if(conditions.direction && conditions.direction === 'in') query += '<'; | ||
query += '-[r' + types + ']-'; | ||
if(conditions.direction && conditions.direction === 'out') query += '>'; | ||
if(conditions.label) query += '(o:' + conditions.label +')'; | ||
else query += '(o)'; | ||
var firstWhere = true; | ||
if(conditions.nodeConditions){ | ||
// loop through all conditions and add a WHERE clause | ||
for(var key in conditions.nodeConditions){ | ||
if(firstWhere) { | ||
query += ' WHERE o.' + key + ' = {' + key + '}'; | ||
firstWhere = false; | ||
} | ||
else query += ' AND o.' + key + ' = {' + key + '}'; | ||
params[key] = conditions.nodeConditions[key]; | ||
} | ||
} | ||
if(conditions.relConditions){ | ||
// loop through all conditions and add a WHERE clause | ||
for(var key in conditions.relConditions){ | ||
if(firstWhere) { | ||
query += ' WHERE r.' + key + ' = {' + relKey + '}'; | ||
firstWhere = false; | ||
} | ||
else query += ' AND r.' + key + ' = {' + relKey + '}'; | ||
params[relKey] = conditions.relConditions[key]; | ||
} | ||
} | ||
query += ' RETURN id(r), startnode(r), r, type(r), id(o), o, labels(o)'; | ||
//if there are options add in the options | ||
if(options){ | ||
for(var option in options){ | ||
// many options can be array of values orderBy, using | ||
switch(option) { | ||
case 'limit': | ||
// expected format options = { limit: 1 } | ||
query += ' LIMIT ' + options[option]; | ||
break; | ||
case 'orderBy': | ||
//expected format options = {orderBy: [{ field: 'name', nulls: true}, {field: 'gender', desc: true}] } | ||
// nulls and desc are optional | ||
for(var k=0, lenO = options[option].length; k < lenO; k++){ | ||
if(options[option][k].field){ | ||
query += ' ORDER BY r.' + options[option][k].field; | ||
if(options[option][k].nulls) query += '?'; | ||
if(options[option][k].desc) query += ' DESC'; | ||
query += ', '; | ||
} | ||
} | ||
// clean up comma at end | ||
if(query.substr(-2,2) === ', ') query = query.substr(0, query.length - 2); | ||
break; | ||
case 'skip': | ||
// expected format options = { skip: 1 } | ||
query += ' SKIP ' + options[option]; | ||
break; | ||
case 'using': | ||
//expected format options = {using: ['name', 'gender'] } | ||
if(typeof options[option] === 'array'){ | ||
for(var l=0, lenO = options[option].length; l<lenO; l++){ | ||
query += ' USING INDEX r:'+ this.modelName + '(' + options[option][l] + ')'; | ||
} | ||
} | ||
else query += ' USING INDEX r:'+ this.modelName + '(' + options[option] + ')'; | ||
break; | ||
} | ||
} | ||
} | ||
var statement = { | ||
statement: query, | ||
parameters: params | ||
}; | ||
var self = this; | ||
this.db.beginTransaction({statements:[statement]}, {commit:true}, function(err, results){ | ||
if(err) return callback(err); | ||
else { | ||
if(results.errors.length > 0){ | ||
return callback(results.errors); | ||
} | ||
else { | ||
return callback(null, parseRelationships(id, results)); | ||
} | ||
} | ||
}); | ||
}; | ||
var parseRelationships = function(id, results){ | ||
var result = { | ||
nodes: [], | ||
rels: [] | ||
}; | ||
results.results[0].data.forEach(function(element, index){ | ||
var relId = results.results[0].data[index].row[0]; | ||
var relStart = results.results[0].data[index].row[1]; | ||
var rel = results.results[0].data[index].row[2]; | ||
var type = results.results[0].data[index].row[3]; | ||
var otherId = results.results[0].data[index].row[4]; | ||
var otherNode = results.results[0].data[index].row[5]; | ||
otherNode._nodeType = results.results[0].data[index].row[6][0]; | ||
var direction = relStart === id ? 'from': 'to'; | ||
otherNode._id = otherId; | ||
result.rels.push({ | ||
_id: relId, | ||
type: type, | ||
direction: direction, | ||
data: rel | ||
}); | ||
result.nodes.push(otherNode); | ||
}); | ||
return result; | ||
}; |
273
lib/write.js
@@ -38,97 +38,98 @@ 'use strict'; | ||
create: function(cb){ | ||
self.schema.validate(data, function(err, data){ | ||
if(err) return cb(err); | ||
var cypher, relationshipCypher, returnCypher, params = {}; | ||
// pull out subschema elements and get separate cyphers | ||
var response = parseSubSchema(data, self.schema); | ||
data = response.data; | ||
data = self.schema.validate(data); | ||
if(data instanceof Error) return cb(data); | ||
var cypher, relationshipCypher, returnCypher, params = {}; | ||
cypher = 'CREATE (n:' + self.label + ' {props})'; | ||
returnCypher = ' RETURN id(n), n'; | ||
params.props = data; | ||
// pull out subschema elements and get separate cyphers | ||
var response = parseSubSchema(data, self.schema); | ||
data = response.data; | ||
// do you want to create a relationship as part of the creation | ||
if(options.relationship){ | ||
if(!options.relationship.direction || !options.relationship.type || !options.relationship.indexField || | ||
!options.relationship.indexValue || !options.relationship.nodeLabel){ | ||
return cb(new Node2NeoError('Create ' + self.label +': Invalid relationship details')); | ||
} | ||
cypher = 'CREATE (n:' + self.label + ' {props})'; | ||
returnCypher = ' RETURN id(n), n'; | ||
params.props = data; | ||
//relationship is valid so setup some parameters | ||
params.indexValue = options.relationship.indexValue; | ||
// do you want to create a relationship as part of the creation | ||
if(options.relationship){ | ||
if(!options.relationship.direction || !options.relationship.type || !options.relationship.indexField || | ||
!options.relationship.indexValue || !options.relationship.nodeLabel){ | ||
return cb(new Node2NeoError('Create ' + self.label +': Invalid relationship details')); | ||
} | ||
// Lookup logic - Relationship (based on _id) and EventNodes, if requested | ||
// Need to lookup starting nodes if the relationship requested has an indexField of _id | ||
// Need to lookup user if evtNodes user is specified | ||
// Need to lookup LATEST EVENT for user and/or relationshipNode if eventNodes requested | ||
// e.g options: {eventNodes:{user:true, relationshipNode:true}} | ||
if(options.relationship.indexField === '_id') { | ||
relationshipCypher = 'START relNode=node({indexValue}) '; | ||
} | ||
else { | ||
relationshipCypher += 'MATCH relNode:' + options.relationship.nodeLabel +' WHERE relNode.' + | ||
options.relationship.indexField + '={indexValue} '; | ||
} | ||
//relationship is valid so setup some parameters | ||
params.indexValue = options.relationship.indexValue; | ||
//insert the relationship information in front of the existing create cypher | ||
cypher = relationshipCypher + cypher; | ||
//add the relationship creation information to the cypher | ||
if(options.relationship.direction === 'to') cypher += '<'; | ||
cypher += '-[rel:'+ options.relationship.type + ' {relData}]-'; | ||
if(options.relationship.direction === 'from') cypher += '>'; | ||
cypher += 'relNode'; | ||
params.relData = options.relationship.data || {}; | ||
// Lookup logic - Relationship (based on _id) and EventNodes, if requested | ||
// Need to lookup starting nodes if the relationship requested has an indexField of _id | ||
// Need to lookup user if evtNodes user is specified | ||
// Need to lookup LATEST EVENT for user and/or relationshipNode if eventNodes requested | ||
// e.g options: {eventNodes:{user:true, relationshipNode:true}} | ||
if(options.relationship.indexField === '_id') { | ||
relationshipCypher = 'START relNode=node({indexValue}) '; | ||
} | ||
else { | ||
relationshipCypher = 'MATCH (relNode:' + options.relationship.nodeLabel +') WHERE relNode.' + | ||
options.relationship.indexField + '={indexValue} '; | ||
} | ||
//return the relationship information | ||
returnCypher += ', type(rel), rel'; | ||
//insert the relationship information in front of the existing create cypher | ||
cypher = relationshipCypher + cypher; | ||
} | ||
//add the relationship creation information to the cypher | ||
if(options.relationship.direction === 'to') cypher += '<'; | ||
cypher += '-[rel:'+ options.relationship.type + ' {relData}]-'; | ||
if(options.relationship.direction === 'from') cypher += '>'; | ||
cypher += 'relNode'; | ||
params.relData = options.relationship.data || {}; | ||
//merge params | ||
// for(var atr in response.subSchemaParams){ | ||
// params[atr] = response.subSchemaParams[atr]; | ||
// } | ||
//return the relationship information | ||
returnCypher += ', type(rel), rel, id(rel)'; | ||
// convert the statement into the correct format | ||
var statement = { | ||
// statement : cypher + response.subSchemaCypher + returnCypher + response.subSchemaReturn, | ||
statement : cypher + returnCypher, | ||
parameters: params | ||
}; | ||
} | ||
if(options.transaction){ | ||
//merge params | ||
// for(var atr in response.subSchemaParams){ | ||
// params[atr] = response.subSchemaParams[atr]; | ||
// } | ||
options.transaction.exec(statement, function(err, response){ | ||
if(err) return cb(err); | ||
else { | ||
var node = self.parseResults({results: response}, {limit: 1}); | ||
// convert the statement into the correct format | ||
var statement = { | ||
// statement : cypher + response.subSchemaCypher + returnCypher + response.subSchemaReturn, | ||
statement : cypher + returnCypher, | ||
parameters: params | ||
}; | ||
// push events to the Transaction | ||
options.transaction._events.push({ | ||
eventEmitter: self.schema, | ||
args: ['create', node] | ||
}); | ||
if(options.transaction){ | ||
return cb(null, node); | ||
} | ||
}); | ||
} | ||
else{ | ||
// if there is no transaction execute the statement | ||
self.db.beginTransaction({statements: [statement] }, {commit:true}, function(err, response){ | ||
if(err) return cb(err); | ||
else { | ||
var node = self.parseResults(response, {limit: 1}); | ||
self.schema.emit('create', node); | ||
return cb(null, node); | ||
} | ||
}); | ||
} | ||
}); | ||
options.transaction.exec(statement, function(err, response){ | ||
if(err) return cb(err); | ||
else { | ||
var node = self.parseResults({results: response}, {limit: 1}); | ||
// push events to the Transaction | ||
options.transaction._events.push({ | ||
eventEmitter: self.schema, | ||
args: ['create', node] | ||
}); | ||
return cb(null, node); | ||
} | ||
}); | ||
} | ||
else{ | ||
// if there is no transaction execute the statement | ||
self.db.beginTransaction({statements: [statement] }, {commit:true}, function(err, response){ | ||
if(err) return cb(err); | ||
else { | ||
var node = self.parseResults(response, {limit: 1}); | ||
self.schema.emit('create', node); | ||
return cb(null, node); | ||
} | ||
}); | ||
} | ||
} | ||
@@ -273,75 +274,75 @@ }, | ||
self.schema.validate(node, function(err, model){ | ||
if(err) return callback(err); | ||
// set the initial params | ||
var params = { | ||
nodeId: id | ||
}; | ||
var model = self.schema.validate(node); | ||
if(model instanceof Error) return callback(model); | ||
// prepare the params for the updates and create the setQUeyr syntax | ||
var setQuery = ''; | ||
var removeProperties = ' REMOVE'; | ||
for(var key in updates){ | ||
// if there is an existing value then setup the value for the event node | ||
// set the initial params | ||
var params = { | ||
nodeId: id | ||
}; | ||
// if the value of the field is to be set to a non null then setup the update and the event node | ||
if(model[key] !== null && typeof model[key] !== 'undefined'){ | ||
params[key+'_NEW'] = model[key]; | ||
setQuery += ' SET n.'+ key +' = {'+key+ '_NEW}'; | ||
} | ||
// otherwise the value should be removed | ||
else if (model[key] === null){ | ||
removeProperties += ' n.'+key+', '; | ||
// prepare the params for the updates and create the setQUeyr syntax | ||
var setQuery = ''; | ||
var removeProperties = ' REMOVE'; | ||
for(var key in updates){ | ||
// if there is an existing value then setup the value for the event node | ||
} | ||
// tidy up remove properties | ||
if(removeProperties === ' REMOVE') removeProperties = ''; | ||
else removeProperties = removeProperties.slice(0, -2); | ||
// if the value of the field is to be set to a non null then setup the update and the event node | ||
if(model[key] !== null && typeof model[key] !== 'undefined'){ | ||
params[key+'_NEW'] = model[key]; | ||
setQuery += ' SET n.'+ key +' = {'+key+ '_NEW}'; | ||
} | ||
// otherwise the value should be removed | ||
else if (model[key] === null){ | ||
removeProperties += ' n.'+key+', '; | ||
var query = 'START n = node({nodeId})'; | ||
} | ||
} | ||
// tidy up remove properties | ||
if(removeProperties === ' REMOVE') removeProperties = ''; | ||
else removeProperties = removeProperties.slice(0, -2); | ||
query += setQuery; | ||
query += removeProperties; | ||
var query = 'START n = node({nodeId})'; | ||
query += ' RETURN id(n), n'; | ||
query += setQuery; | ||
query += removeProperties; | ||
// push the statement to the | ||
var statement = { | ||
statement : query, | ||
parameters: params | ||
}; | ||
query += ' RETURN id(n), n'; | ||
if(options.transaction){ | ||
// push the statement to the | ||
var statement = { | ||
statement : query, | ||
parameters: params | ||
}; | ||
options.transaction.exec(statement, function(err, response){ | ||
if(err) { | ||
// close the transaction | ||
options.transaction.remove(function(err2){ | ||
return callback(err); | ||
}); | ||
} | ||
else { | ||
var node = self.parseResults({results: response}, {limit: 1}); | ||
if(options.transaction){ | ||
options.transaction._events.push({ | ||
eventEmitter: self.schema, | ||
args: ['update', node, updates] | ||
}); | ||
options.transaction.exec(statement, function(err, response){ | ||
if(err) { | ||
// close the transaction | ||
options.transaction.remove(function(err2){ | ||
return callback(err); | ||
}); | ||
} | ||
else { | ||
var node = self.parseResults({results: response}, {limit: 1}); | ||
return callback(null, node); | ||
} | ||
}); | ||
} | ||
else{ | ||
// if there is no transaction execute the statement | ||
self.db.beginTransaction({statements: [statement] }, {commit:true}, function(err, response){ | ||
if(err) return callback(err); | ||
options.transaction._events.push({ | ||
eventEmitter: self.schema, | ||
args: ['update', node, updates] | ||
}); | ||
var node = self.parseResults(response, {limit: 1}); | ||
self.schema.emit('update', node, updates); | ||
return callback(null, node); | ||
}); | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
else{ | ||
// if there is no transaction execute the statement | ||
self.db.beginTransaction({statements: [statement] }, {commit:true}, function(err, response){ | ||
if(err) return callback(err); | ||
var node = self.parseResults(response, {limit: 1}); | ||
self.schema.emit('update', node, updates); | ||
return callback(null, node); | ||
}); | ||
} | ||
}; | ||
@@ -365,3 +366,3 @@ | ||
var params = { | ||
nodeId: id | ||
nodeId: parseInt(id, 10) | ||
}; | ||
@@ -368,0 +369,0 @@ |
{ | ||
"name": "node2neo-model", | ||
"description": "Model support for Node2neo", | ||
"version": "0.0.2", | ||
"version": "0.0.3", | ||
"author": { | ||
@@ -6,0 +6,0 @@ "name": "Rory Madden", |
200
README.md
@@ -1,81 +0,103 @@ | ||
# Node2Neo | ||
# Node2Neo Model | ||
A Node.js transaction REST API for neo4j. | ||
Node2neo uses the transaction endpoints feature of the Neo4j REST API. This reduces the traffic between the server and neo4j and allows for multiple http requests within the same transaction. | ||
Model support for Node2Neo. This module builds upon the node2neo family of modules. | ||
A model uses a node2neo-schema, but can be used independently if required. | ||
This is intended to be a building block for future modules to add advanced functionality. | ||
NOTE: Neo4j 2.0 is required. | ||
## Installation | ||
npm install node2neo | ||
## Usage | ||
#### Connecting to the db | ||
#### Create a new model | ||
```js | ||
var db = require('node2neo')('http://localhost:7474'); | ||
``` | ||
var Model = require('node2neo-model'); | ||
There are only five methods offered by node2neo: | ||
1. beginTransaction | ||
2. executeStatement | ||
3. commitTransaction | ||
4. removeTransaction | ||
5. getTransactionId (helper) | ||
#### Begin Transaction | ||
To create a transaction use the db.beginTransaction function. The statements provided to the beginTransaction function must match the required neo4j format. | ||
````js | ||
var transStatement = { | ||
statements: [{ | ||
statement: 'CREATE n:User RETURN n' | ||
}, { | ||
statement: 'CREATE o:Person RETURN o' | ||
}] | ||
Var schema = { | ||
first: {type: String, required: true}, | ||
email: {type:String, required: true, index: true}, | ||
birthday: Date | ||
} | ||
db.beginTransaction(transStatement, function(err, results){ | ||
var User = Model.model('User', schema); | ||
User.create({first: 'Name', email: 'test@test.com'}, function(err, user){ | ||
... | ||
}) | ||
``` | ||
The first step in the creation of a model is to validate that the data passed in matches the structure defined in the schema. THis reduces teh need to perform checking of input data in your code! | ||
You can immediately commit a transaction by passing an options variable with a commmit property | ||
The structure of the create function is .create(data, options, callback); | ||
Often when creating a model you want to create a relationship to another object. You can pass in a relationship object to the optional options object. | ||
```js | ||
db.beginTransaction(transStatement, { commit: true }, function(err, results){ | ||
var relationship = { | ||
indexField: 'email', | ||
indexValue: 'test@test.com', | ||
type: 'FRIEND', | ||
direction: 'to', | ||
data: { | ||
optional: true | ||
} | ||
} | ||
User.create({first: 'Other', email: 'other@test.com'}, {relationship: relationship}, function(err, results){ | ||
// results.node = the newly created node | ||
// results.rel = the newly created relationship | ||
}); | ||
``` | ||
}) | ||
#### Update a model | ||
To update a model you need an instance of the existing model. There are three ways to update a model: findByIdAndUpdate, findOneAndUpdate or update. | ||
```js | ||
User.findByIdAndUpdate(id, updates, options, callback){ | ||
// finds the model and then calls User.update with the model | ||
}); | ||
User.findOneAndUpdate(conditions, updates, options, callback){ | ||
// finds the model and then calls User.update with the model | ||
}); | ||
User.update(model, updates, options, callback){ | ||
}); | ||
``` | ||
The response format for an open transaction is as follows (closed transaction have a different response) | ||
Again the updates are validated against the schema definitions. | ||
#### Transactions | ||
The options argument can be used to pass in transactions as well. Transactions let you perform multiple operations with a single atomic transaction. This is useful where you have multiple all-or-nothing statemetns that you want to run. | ||
There are issues with the database locking if two transactions attempt to update the same node so be careful. | ||
NOTE: You cannot create and update a model in the same transaction as the node does not exist in the database until the complete transaction has been committed. | ||
```js | ||
response: { | ||
results: [{ | ||
commit: '...commit string...', | ||
transaction: {expires: '...transaction timeout period...'}, | ||
errors: [] | ||
results: [{ | ||
columns: ['n', 'o'], | ||
data: [{ | ||
row: [{ name: 'MyNode'}, {name: 'YourNode'}] | ||
}] | ||
}] | ||
var Transaction = new Transaction(db); | ||
}] | ||
} | ||
var trans1 = new Transaction; | ||
trans1.begin(function(err){ | ||
User.create(data, {transaction: trans1}, function(err, user){ | ||
Event.create(data, {transaction: trans1}, function(err, event){ | ||
Other.update(model, updates, {transaction: trans1}, function(err, other){ | ||
trans1.commit(function(err){ | ||
// all or nothing commit | ||
}) | ||
}); | ||
}); | ||
}); | ||
}); | ||
``` | ||
#### Execute Statement | ||
To perform another action in the same transaction use the execute Statement function | ||
#### Remove a node | ||
To remove a node simply pass its id to the remove function. The options object can contain a force option. Removing a node in Neo4j will fail if it has any relationships. The force option deletes the node and all relationships that the node has. | ||
In order to execute a statement you will need the transaction Id or commit string. | ||
You can use the db.getTransactionId(commitString) helper to get the transaction id from the commit string or just pass in the commit string | ||
When removing nodes be carefult not to leave orphan nodes - remove does not cascade. | ||
```js | ||
db.executeStatement(commit, statements, function(err, results){ | ||
User.remove(id, {force: true}, function(err){ | ||
@@ -85,37 +107,75 @@ }) | ||
Transaction will expire after aperiod of time. You can prevent a transaction from expiring by submitting an empty statements array | ||
#### Find | ||
To find a node you can use the find method. | ||
Find returns an array of nodes and includes an _id field to enable easy referencing of the node. | ||
```js | ||
var statemetns = { | ||
statements: [] | ||
} | ||
db.executeStatement(commit, statements, function(err, results){ | ||
User.find({email: 'test@test.com'}, function(err, user){ | ||
}) | ||
``` | ||
#### Commit a transaction | ||
All of the statements in a transaction are not commited until the whole transaction is committed. You can optionally include some further statements at this point before committing the transaction. The result from a commit is different: | ||
// the returned format | ||
[{ | ||
_id: 17534, | ||
first: 'Name', | ||
email: 'test@test.com' | ||
}] | ||
'''js | ||
You can pass optional parameters to find: | ||
- limit: limit the number of results | ||
- skip: skip the x number of initial results | ||
- orderBy: sort the results e.g. orderBy: [{field: 'first', desc:true, nulls:true}] | ||
- using: define teh index to use in the lookup | ||
#### Find One | ||
Find returns an array of nodes. However if you know you only want one result you can use findOne. This method does not validate that there is only one result though, if there are multiple results only the first result will be returned. | ||
```js | ||
db.commitTransaction(commit, [statements], function(err, results){ | ||
User.findOne({email: 'test@test.com}, function(err, user){ | ||
}) | ||
``` | ||
// if no statement is provided you receive tw empy arrays. | ||
// If a statement was provided the results array would be poopulated as above | ||
{ | ||
"results" : [ ], | ||
"errors" : [ ] | ||
} | ||
If no node is found node the returned object will be undefined: | ||
```js | ||
User.findOne({email: 'test@test.com, name: 'Test'}, function(err, user){ | ||
// user === undefined | ||
}); | ||
``` | ||
#### Remove a transaction | ||
There are two ways to undo a transaction that is in mid progress: let it timeout or tell Neo4j to remove it. | ||
#### Find By Id | ||
```js | ||
User.findById(id, function(err, user){ | ||
// if not a valid id user === unndefined | ||
}); | ||
``` | ||
### Get Relationships | ||
If you want to find all of the relationships from a node and the related nodes you can use the getRelationships method. | ||
The getRelationships method takes the id of the starting node, and some optional conditions and options. | ||
Conditions include: | ||
- types: single relationship type or array of types to be returned | ||
- direction: 'to' or 'from'. Leave blank for both | ||
- label: the single label to return e.g. User | ||
- nodeConditions: key:value query conditions to match data on the related nodes | ||
- relConditions: key:value query conditions to match data on the relationship | ||
The options object is the same as the find options: | ||
- limit: limit the number of results | ||
- skip: skip the x number of initial results | ||
- orderBy: sort the results e.g. orderBy: [{field: 'first', desc:true, nulls:true}] | ||
- using: define teh index to use in the lookup | ||
```js | ||
db.removeTransaction(commit, function(err){ | ||
User.getRelationships(208, {label: 'Cookies', types: 'Authorises'}, function(err, results){ | ||
// results is an object with nodes and rels arrays | ||
}) | ||
// each node includes an _id and _nodeType variable. _nodeType includes the label of the node | ||
// each rel includes _id, type, direction and data. Direction is 'to or 'from'. type is the relationship type, and data contains any data on the relationship. | ||
}); | ||
``` | ||
@@ -122,0 +182,0 @@ |
@@ -17,4 +17,4 @@ var testDatabase = require('./util/database'); | ||
before(testDatabase.refreshDb); | ||
after(testDatabase.stopDb); | ||
// before(testDatabase.refreshDb); | ||
// after(testDatabase.stopDb); | ||
it("should create a new model with no schema", function(done){ | ||
@@ -38,2 +38,19 @@ | ||
}); | ||
it("should create a model with schema methods", function(done){ | ||
var eventSpy = sinon.spy(); | ||
var schema = new Schema({ | ||
test: String | ||
}, {label: 'Test'}); | ||
schema.static('activate', function(value){ | ||
this.schema.emit('activate', value); | ||
}); | ||
var Test = Model.model('Test', schema); | ||
Test.schema.on('activate', eventSpy); | ||
Test.create({test: 'emit'}, function(err, test){ | ||
Test.activate('please'); | ||
assert(eventSpy.called, 'Event did not fire.'); | ||
assert(eventSpy.calledOnce, 'Event fired more than once'); | ||
done(); | ||
}); | ||
}); | ||
describe("model instance", function(){ | ||
@@ -124,3 +141,3 @@ before(function(done){ | ||
}); | ||
it("should create a new node with a relationship", function(done){ | ||
it("should create a new node with a relationship: indexField _id", function(done){ | ||
var user = { | ||
@@ -156,2 +173,33 @@ first_name: 'Cath', | ||
}); | ||
it("should create a new node with a relationship: indexField value", function(done){ | ||
var user = { | ||
first_name: 'Cath', | ||
last_name: 'Fee', | ||
email: 'other@gmail.com' | ||
}; | ||
var relationship = { | ||
indexField: 'first_name', | ||
indexValue: 'Mary', | ||
nodeLabel: 'User', | ||
direction: 'to', | ||
type: 'ENGAGED_TO' | ||
} | ||
eventSpy = sinon.spy(); | ||
User.schema.on('create',eventSpy); | ||
User.create(user, {relationship: relationship}, function(err, results){ | ||
should.not.exist(err); | ||
should.exist(results.node); | ||
should.exist(results.node._id); | ||
secondId = results.node._id; | ||
results.node.first_name.should.equal('Cath'); | ||
results.node.last_name.should.equal('fee'); | ||
results.node.email.should.equal('other@gmail.com'); | ||
should.exist(results.rel); | ||
results.rel.type.should.equal('ENGAGED_TO'); | ||
assert(eventSpy.called, 'Event did not fire.'); | ||
assert(eventSpy.calledOnce, 'Event fired more than once'); | ||
eventSpy.alwaysCalledWithExactly(results).should.equal(true); | ||
done(); | ||
}); | ||
}); | ||
it("should create a new relationship", function(done){ | ||
@@ -462,3 +510,3 @@ var relationship = { | ||
var publisherSchema = new Schema({ | ||
brnad: String | ||
brand: String | ||
}, {label: 'Publisher'}); | ||
@@ -539,3 +587,57 @@ | ||
}); | ||
it("should error with undefined find", function(done){ | ||
User.find({email: undefined}, function(err, results){ | ||
should.exist(err); | ||
done(); | ||
}); | ||
}); | ||
describe("relationships", function(){ | ||
before(function(done){ | ||
User.find({}, {limit: 20}, function(err, results){ | ||
var count = results.length; | ||
results.forEach(function(element){ | ||
var relationship = { | ||
from: secondId, | ||
to: element._id, | ||
type: 'FRIEND', | ||
direction: 'to' | ||
}; | ||
User.createRelationship(relationship, function(err, rels){ | ||
should.not.exist(err); | ||
results.forEach(function(element){ | ||
var relationship = { | ||
from: element._id, | ||
to: secondId, | ||
type: 'ENEMY', | ||
direction: 'to' | ||
}; | ||
User.createRelationship(relationship, function(err, rels){ | ||
should.not.exist(err); | ||
}); | ||
}); | ||
}); | ||
}); | ||
done(); | ||
}); | ||
}); | ||
it("should find relationships", function(done){ | ||
User.getRelationships(secondId, function(err, results){ | ||
results.should.be.an('object').with.property('nodes'); | ||
results.should.be.an('object').with.property('rels'); | ||
results.rels.should.be.an('array'); | ||
results.nodes.should.be.an('array'); | ||
results.rels[0].should.be.an('object').with.property('_id'); | ||
results.rels[0].should.be.an('object').with.property('type'); | ||
results.rels[0].should.be.an('object').with.property('direction'); | ||
results.rels[0].should.be.an('object').with.property('data'); | ||
results.nodes[0].should.be.an('object').with.property('_id'); | ||
results.nodes[0].should.be.an('object').with.property('_nodeType'); | ||
results.nodes[0]._nodeType.should.equal('User'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
71399
19.75%1754
16.78%183
48.78%