Skip to content

Commit

Permalink
Merge pull request #25 from holidayextras/mmiller42-multiple-resource…
Browse files Browse the repository at this point in the history
…s-per-db

Multiple resources per database
  • Loading branch information
pmcnr-hx committed May 13, 2016
2 parents a6f1d3c + 9432051 commit 0a5068c
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 43 deletions.
25 changes: 14 additions & 11 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
2015-06-29 - Initial release
2015-12-10 - Pagination
2015-12-10 - Sorting
2015-12-10 - More efficient inclusions
2015-12-10 - v1.0.0
2015-12-11 - Foreign relations
2015-12-11 - v1.0.1
2015-12-15 - Bump Sequelize to prevent promise warnings + use asCallback() on promises
2015-12-15 - v1.0.2
2015-12-15 - Enable connections to remote databases
2015-12-15 - v1.0.3
- 2016-05-13 - v1.1.0
- 2016-05-13 - Support `jsonapi-server` v1.2
- 2016-05-13 - Multiple resources per database
- 2015-12-15 - v1.0.3
- 2015-12-15 - Enable connections to remote databases
- 2015-12-15 - v1.0.2
- 2015-12-15 - Bump Sequelize to prevent promise warnings + use asCallback() on promises
- 2015-12-11 - v1.0.1
- 2015-12-11 - Foreign relations
- 2015-12-10 - v1.0.0
- 2015-12-10 - More efficient inclusions
- 2015-12-10 - Sorting
- 2015-12-10 - Pagination
- 2015-06-29 - Initial release
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ jsonApi.define({
dialect: "mysql",
host: "localhost",
port: 3306,
database: "jsonapi", // If not provided, defaults to the name of the resource
username: "root",
password: null,
logging: false
Expand All @@ -52,8 +53,8 @@ jsonApi.define({
Getting this data store to production isn't too bad...

1. Bring up your relational database stack.
2. Create the databases - one database per resources, named identically. A `people` resource will need a `people` database.
3. Create the database tables. You can call `(new RelationalDbStore()).populate()` to have this module attempt to create the require tables. If you enable debugging via `DEBUG=jsonApi:store:*` you'll see the create-table statements - you can target a local database, call poplate(), grab the queries, review them and finally run them against your production stack manually.
2. Create the database(s).
3. Create the database tables. You can call `(new RelationalDbStore()).populate()` to have this module attempt to create the require tables. If you enable debugging via `DEBUG=jsonApi:store:*` you'll see the create-table statements - you can target a local database, call populate(), grab the queries, review them and finally run them against your production stack manually.
3. Deploy your code.
4. Celebrate.

Expand Down
1 change: 1 addition & 0 deletions example/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.children[2].exports = function() {
port: 3306,
username: "root",
password: null,
database: "jsonapi-relationaldb",
logging: null
});

Expand Down
54 changes: 45 additions & 9 deletions lib/sqlHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// http://docs.sequelizejs.com/en/latest/
var Sequelize = require("sequelize");
var async = require("async");
var crypto = require("crypto");
var debug = require("debug")("jsonApi:store:relationaldb");
var Joi = require("joi");
var _ = {
pick: require("lodash.pick"),
assign: require("lodash.assign"),
Expand All @@ -13,26 +15,45 @@ var SqlStore = module.exports = function SqlStore(config) {
this.config = config;
};

SqlStore._sequelizeInstances = Object.create(null);

/**
Handlers readiness status. This should be set to `true` once all handlers are ready to process requests.
*/
SqlStore.prototype.ready = false;

/**
initialise gets invoked once for each resource that uses this hander.
In this instance, we're allocating an array in our in-memory data store.
In this instance, we're instantiating a Sequelize instance and building models.
*/
SqlStore.prototype.initialise = function(resourceConfig) {
var self = this;
self.resourceConfig = resourceConfig;

self.sequelize = new Sequelize(resourceConfig.resource, self.config.username, self.config.password, {
var database = self.config.database || resourceConfig.resource;
var sequelizeArgs = [database, self.config.username, self.config.password, {
dialect: self.config.dialect,
host: self.config.host,
port: self.config.port,
logging: self.config.logging || require("debug")("jsonApi:store:relationaldb:sequelize"),
freezeTableName: true
});
}];

// To prevent too many open connections, we will store all Sequelize instances in a hash map.
// Index the hash map by a hash of the entire config object. If the same config is passed again,
// reuse the existing Sequelize connection resource instead of opening a new one.

var md5sum = crypto.createHash('md5');
var instanceId = md5sum.update(JSON.stringify(sequelizeArgs)).digest('hex');
var instances = SqlStore._sequelizeInstances;

if (!instances[instanceId]) {
var sequelize = Object.create(Sequelize.prototype);
Sequelize.apply(sequelize, sequelizeArgs);
instances[instanceId] = sequelize;
}

self.sequelize = instances[instanceId];

self._buildModels();

Expand All @@ -41,13 +62,28 @@ SqlStore.prototype.initialise = function(resourceConfig) {

SqlStore.prototype.populate = function(callback) {
var self = this;
self.sequelize.sync({ force: true }).asCallback(function(err) {
if (err) throw err;

async.map(self.resourceConfig.examples, function(exampleJson, asyncCallback) {
self.create({ request: { type: self.resourceConfig.resource } }, exampleJson, asyncCallback);
}, callback);
});
var tasks = [
function(cb) {
self.baseModel.sync().asCallback(cb);
},
function(cb) {
async.each(self.relationArray, function(model, ecb) {
model.sync().asCallback(ecb);
}, cb);
},
function(cb) {
async.each(self.resourceConfig.examples, function(exampleJson, ecb) {
var validation = Joi.validate(exampleJson, self.resourceConfig.attributes);
if (validation.error) {
return asyncCallback(validation.error);
}
self.create({ request: { type: self.resourceConfig.resource } }, validation.value, ecb);
}, cb);
}
];

async.series(tasks, callback);
};

SqlStore.prototype._buildModels = function() {
Expand Down
32 changes: 17 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jsonapi-store-relationaldb",
"version": "1.0.3",
"version": "1.1.0",
"description": "Relational data store for jsonapi-server.",
"keywords": [
"json:api",
Expand All @@ -24,27 +24,29 @@
"dependencies": {
"async": "1.5.0",
"debug": "2.2.0",
"lodash.assign": "3.2.0",
"lodash.omit": "3.1.0",
"lodash.pick": "3.1.0",
"sequelize": "3.14.2"
"joi": "6.10.1",
"lodash.assign": "4.0.8",
"lodash.omit": "4.2.1",
"lodash.pick": "4.2.0",
"sequelize": "3.23.0"
},
"devDependencies": {
"mocha": "2.2.5",
"mysql": "2.9.0",
"eslint": "0.24.1",
"blanket": "1.1.7",
"mocha-lcov-reporter": "0.0.2",
"coveralls": "2.11.2",
"blanket": "1.1.9",
"coveralls": "2.11.9",
"eslint": "2.9.0",
"jsonapi-server": "1.2.0",
"mocha": "2.4.5",
"mocha-lcov-reporter": "1.2.0",
"mocha-performance": "0.1.1",
"mysql": "2.10.2",
"plato": "1.5.0",
"mocha-performance": "0.1.0",
"jsonapi-server": "1.0.2"
"v8-profiler": "5.6.4"
},
"scripts": {
"test": "/bin/sh setupDatabase.sh && ./node_modules/mocha/bin/mocha --timeout 20000 -R spec ./test/*.js",
"test": "./setupDatabase.sh jsonapi-relationaldb-test && ./node_modules/mocha/bin/mocha --timeout 20000 -R spec ./test/*.js",
"start": "node example/server.js",
"coveralls": "./node_modules/mocha/bin/mocha --require blanket --reporter mocha-lcov-reporter ./test/*.js | ./node_modules/coveralls/bin/coveralls.js",
"coverage": "./node_modules/mocha/bin/mocha --timeout 20000 --require blanket --reporter html-cov ./test/*.js > coverage.html",
"coverage": "./node_modules/mocha/bin/mocha --timeout 20000 --require blanket --reporter html-cov ./test/*.js > coverage.html || true",
"complexity": "./node_modules/plato/bin/plato -r -d complexity lib",
"performance": "node --allow-natives-syntax --harmony ./node_modules/mocha/bin/_mocha --reporter mocha-performance ./test/*.js",
"lint": "./node_modules/.bin/eslint ./example/*.js ./lib/* ./test/*.js --quiet && echo '✔ All good!'"
Expand Down
8 changes: 3 additions & 5 deletions setupDatabase.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#!/bin/bash -x

for database in articles photos comments tags people
do
mysql -u root -e"DROP DATABASE IF EXISTS $database"
mysql -u root -e"CREATE DATABASE $database"
done
DB=$1
mysql -u root -e "DROP DATABASE IF EXISTS \`$DB\`"
mysql -u root -e "CREATE DATABASE \`$DB\`"
3 changes: 2 additions & 1 deletion test/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.children[2].exports = function() {
port: 3306,
username: "root",
password: null,
database: "jsonapi-relationaldb-test",
logging: false
});

Expand All @@ -37,7 +38,7 @@ articles.onCreate.created = articles.onCreate.created.allow(null);
// Before starting the test suite, load all example resouces, aka
// the test fixtures, into the databases
before(function(done) {
async.map(instances, function(dbStore, callback) {
async.each(instances, function(dbStore, callback) {
dbStore.populate(callback);
}, done);
});

0 comments on commit 0a5068c

Please sign in to comment.