Skip to content
Gilbert edited this page Apr 15, 2016 · 3 revisions

(code dump)

The Database Connection

lib/db.js - This gives us a promisified way to make queries to the database.

var Bluebird = require('bluebird')

var db = require('seraph')({
  server: 'http://localhost:7474',
  user: 'neo4j', // <-- Change these according to your setup
  pass: 'dev'    // <--
})

// Promisify seraph API
Bluebird.promisifyAll(db, { suffix: '_p' })
Bluebird.promisifyAll(db.node, { suffix: '_p' })
Bluebird.promisifyAll(db.rel, { suffix: '_p' })
Bluebird.promisifyAll(db.index, { suffix: '_p' })
Bluebird.promisifyAll(db.constraints, { suffix: '_p' })
Bluebird.promisifyAll(db.constraints.uniqueness, { suffix: '_p' })
module.exports = db

db.deleteEverything = function () {
  if (process.env.NODE_ENV === 'production') return;

  var query = `
    MATCH (n)
    DETACH DELETE n
    RETURN 0
  `
  return db.queryRaw_p(query)
}

The Model "Class"

server/lib/model.js - This will cover common db operations for a node type. Change to your liking :)

var util = require('util')
var db = require('./db.js')

exports.node = function (nodeName, extras) {
  var nodeNameLower = nodeName.toLowerCase()

  var Model = {

    all: function () {
      return db.nodesWithLabel_p(nodeName)
    },

    findBy: function (attrs) {
      return db.find_p(attrs, nodeName)
        .then( exports.firstResult(Model) )
    },

    create: save,

    update: function (attrs) {
      if (attrs.id === null && attrs.id === undefined) {
        throw new InvalidArgument("You must provide an id to update.")
      }
      return save(attrs, 'update')
    },

    updateOrCreateBy: function (attrName, attrs) {

      return Model.findBy({ [attrName]: attrs[attrName] })
        .then(
          modelData => Model.update( Object.assign(modelData, attrs) )
        )
        .catch( Model.NotFound, save.papp(attrs, 'create') )
    }
  }

  //
  // Common Errors
  //
  Model.NotFound = function NotFound(details) {
    Error.captureStackTrace(this, this.constructor)
    this.name = 'NotFound'
    this.message = nodeNameLower + '_not_found'
    if (details) this.details = details
  }
  util.inherits(Model.NotFound, Error)

  Model.InvalidArgument = function InvalidArgument (message) {
    Error.captureStackTrace(this, this.constructor)
    this.name = 'InvalidArgument'
    this.message = message
  }
  util.inherits(Model.InvalidArgument, Error)

  return Object.assign(Model, extras)



  function save (attrs, type) {

    if (attrs.id !== null && attrs.id !== undefined) {
      var cleanAttrs = Model.cleanAttrs( attrs )
      cleanAttrs.updated_at = new Date()

      return db.call_p(
        db.operation( 'node/' + attrs.id + '/properties', 'PUT', cleanAttrs )
      )
      .return( Object.assign(cleanAttrs, { id: attrs.id }) )
    }
    else {
      attrs.created_at = new Date()
      return db.save_p( attrs, nodeName )
    }
  }
}

exports.firstResult = function (Model) {
  return function (models) {
    return (models.length === 0) ? Promise.reject(new Model.NotFound()) : models[0]
  }
}

The Model Itself

server/models/user.js - An example of a User model.

var db = require('../lib/db')
var Model = require('../lib/mode')


var User = module.exports = Model.node('User', 'users', {

  signIn: function (username, password) {
    return User.findBy({ username: username })
      .then(function(user) {
        // In a real app the user's password would be encrypted
        if (user.password === password) {
          return user
        }
        else {
          return Promise.reject(new User.InvalidCredentials())
        }
      })
  },

  // Some queries will require custom Cypher
  mutualFriends: function (userId_1, userId_2) {
    var query = `
      MATCH
        (a:User)-[:FRIEND_OF]-(mutual:User)-[:FRIEND_OF]-(b:User)
      WHERE
        id(a) = { userId_1 } AND id(b) = { userId_2 }
      RETURN mutual
    `
    return db.query_p(query, { userId_1: userId_1, userId_2: userId_2 })
  },

})

// Custom error specific to User model
User.InvalidCredentials = function InvalidCredentials() {
  Error.captureStackTrace(this, this.constructor)
  this.name = 'InvalidCredentials'
  this.message = modelName + ': not found.'
}
util.inherits(User.InvalidCredentials, Error)