🚀 Big News: Socket Acquires Coana to Bring Reachability Analysis to Every Appsec Team.Learn more
Socket
DemoInstallSign in
Socket

node2neo-model

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

node2neo-model - npm Package Compare versions

Comparing version

to
0.0.3

7

CHANGELOG.md

@@ -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

14

lib/index.js

@@ -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 @@

@@ -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;
};

@@ -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",

@@ -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();
});
});
});
});
});