Skip to content

Instantly share code, notes, and snippets.

@francisbrito
Last active January 26, 2016 15:52
Show Gist options
  • Save francisbrito/3212d067e6ea1a33a6a9 to your computer and use it in GitHub Desktop.
Save francisbrito/3212d067e6ea1a33a6a9 to your computer and use it in GitHub Desktop.
WIP: Proof of Concept for a MongoDB data mapper.
{
"presets": ['es2015-node4']
}
const mongodb = require('mongodb');
const utilities = require('underscore');
const DEFAULT_PAGINATION = {skip: 0, limit: 0};
const DEFAULT_DESTROY_OPTIONS = {
forcefully: false,
};
const MONGODB_DATA_MAPPER_PROTOTYPE = {
// Life-cycle methods.
*initialize(options = {}) {
if (propertiesAreMissing(['dbUri', 'factory', 'collectionName'], options)) {
throw new Error('Missing `dbUri`, `factory` or `collectionName`');
}
const {dbUri, factory, collectionName} = options;
if (!isValidConnectionUri(dbUri)) throw new Error('`dbUri` is malformed.');
if (!isValidFactoryFunction(factory)) throw new Error('`factory` is invalid.');
if (!isValidCollectionName(collectionName)) throw new Error('`collectionName` is invalid.');
const db = yield mongodb.MongoClient.connect(dbUri);
this.db = db;
this.collection = db.collection(collectionName);
this.factory = this.createEntity = factory;
},
*destroy({forcefully} = DEFAULT_DESTROY_OPTIONS) {
yield this.db.close(forcefully);
},
// CRUD methods.
*save(entity) {
if (!this.factory.isFactoryOf(entity))
throw new Error(`Can only save instances of ${this.factory.name}`);
// Maps `this.factory`'s ID to MongoDB's ID.
const entityId = entity.id;
const entityWithoutId = utilities.omit(entity.toJSON(), 'id');
const mongoDbEntity = Object.assign({}, entityWithoutId, {_id: entityId});
yield this.collection.insert(mongoDbEntity);
},
*find(query = {}, projection = {}, sorting = {}, pagination = DEFAULT_PAGINATION) {},
*update(id, properties) {},
*remove(id) {},
};
module.exports = MONGODB_DATA_MAPPER_PROTOTYPE;
/**
* Helpers
*/
function propertiesAreMissing(properties, obj) {
return properties.map(p => !utilities.has(obj, p)).reduce((p, c) => p && c);
}
function isValidConnectionUri(uri) {
// NOTE: For now, a valid connection URI is a string starting with `mongodb://`.
return utilities.isString(uri) && uri.startsWith('mongodb://');
}
function isValidFactoryFunction(factory) {
return utilities.isFunction(factory) && factory.isFactoryOf && utilities.isFunction(factory.isFactoryOf);
}
function isValidCollectionName(collectionName) {
return utilities.isString(collectionName);
}
const isFunction = require('underscore').isFunction;
const isValidUuid = require('validator').isUUID;
const generateUuid = require('uuid').v4;
const TODO_PROTOTYPE = {
toJSON() {
const {id, done, text} = this;
return {
id,
done,
text,
};
},
markAsDone() {
this.done = true;
},
};
function Todo(properties) {
// Allow calling this constructor without the `new` keyword.
if (!(this instanceof Todo)) return new Todo(properties);
const id = generateUuid();
const {done, text} = properties;
this.id = id;
this.done = !!done;
this.text = text || '';
}
Todo.isFactoryOf = (properties) => {
const {id} = properties;
if (!hasFields(['id', 'done', 'text'], properties)) return false;
if (!isValidUuid(id)) return false;
if (!hasValidToJsonMethod(properties)) return false;
return true
};
Object.setPrototypeOf(Todo.prototype, TODO_PROTOTYPE);
exports.Todo = Todo;
/**
* Helpers
*/
function hasFields(fields, obj) {
return fields.map(f => f in obj).reduce((p, c) => p && c);
}
function hasValidToJsonMethod(obj) {
return obj.toJSON && isFunction(obj.toJSON); /* && obj.toJSON === TODO_PROTOTYPE.toJSON // limits extensability (?) */
}
{
"name": "data-mapper",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"babel-preset-es2015-node4": "^2.0.3",
"bluebird": "^3.1.4",
"co": "^4.6.0",
"mongodb": "^2.1.4",
"underscore": "^1.8.3",
"validator": "^4.5.1",
"uuid": "^2.0.1"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
const Promise = require('bluebird');
const toCoroutine = require('co');
const Todo = require('./models').Todo;
const MongoDbDataMapper = require('./');
Promise
.resolve(toCoroutine(function* () {
const todoDataMapper = Object.create(MongoDbDataMapper);
yield todoDataMapper.initialize({
dbUri: 'mongodb://localhost/test',
factory: Todo,
collectionName: 'todos',
});
const myNewTodo = new Todo({
text: 'Test Data-Mapper concept.'
});
yield todoDataMapper.save(myNewTodo);
yield todoDataMapper.destroy();
}));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment