Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deep populate #105

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ For a short tutorial on using Camo, check out [this](http://stackabuse.com/getti
### Connect to the Database
Before using any document methods, you must first connect to your underlying database. All supported databases have their own unique URI string used for connecting. The URI string usually describes the network location or file location of the database. However, some databases support more than just network or file locations. NeDB, for example, supports storing data in-memory, which can be specified to Camo via `nedb://memory`. See below for details:

- MongoDB:
- MongoDB:
- Format: mongodb://[username:password@]host[:port][/db-name]
- Example: `var uri = 'mongodb://scott:abc123@localhost:27017/animals';`
- NeDB:
Expand Down Expand Up @@ -275,15 +275,23 @@ Dog.findOne({ name: 'Lassie' }).then(function(l) {

`.findOne()` currently accepts the following option:

- `populate`: Boolean value to load all or no references. Pass an array of field names to only populate the specified references
- `populate`: Boolean value to load all or no references. Pass an array of field names to only populate the specified references or an Object to deeply populate specified references
- `Person.findOne({name: 'Billy'}, {populate: true})` populates all references in `Person` object
- `Person.findOne({name: 'Billy'}, {populate: ['address', 'spouse']})` populates only 'address' and 'spouse' in `Person` object
- `Person.findOne({name: 'Billy'}, {populate: ['address', 'spouse']})` populates only 'address' and 'spouse' in `Person` object, but not its references.
- `Person.findOne({name: 'Billy'}, {populate: {'address':false, 'spouse':false}})` populates only 'address' and 'spouse' in `Person` object, but not its references.
- `Person.findOne({name: 'Billy'}, {populate: {'address':true, 'spouse':true}})` populates only 'address' and 'spouse' in `Person` object, and all its references.
- `Person.findOne({name: 'Billy'}, {populate: {'address': {'city':true}}})` populates only 'address' in `Person` object, and 'city' in `Address` object, and all of the `City` references.
- `Person.findOne({name: 'Billy'}, {populate: {'address': ['city']}})` populates only 'address' in `Person` object, and 'city' in `Address` object, but none of the `City` references.

`.find()` currently accepts the following options:

- `populate`: Boolean value to load all or no references. Pass an array of field names to only populate the specified references
- `populate`: Boolean value to load all or no references. Pass an array of field names to only populate the specified references or an Object to deeply populate specified references
- `Person.find({lastName: 'Smith'}, {populate: true})` populates all references in `Person` object
- `Person.find({lastName: 'Smith'}, {populate: ['address', 'spouse']})` populates only 'address' and 'spouse' in `Person` object
- `Person.findOne({lastName: 'Smith'}, {populate: ['address', 'spouse']})` populates only 'address' and 'spouse' in `Person` object, but not its references.
- `Person.findOne({lastName: 'Smith'}, {populate: {'address':false, 'spouse':false}})` populates only 'address' and 'spouse' in `Person` object, but not its references.
- `Person.findOne({lastName: 'Smith'}, {populate: {'address':true, 'spouse':true}})` populates only 'address' and 'spouse' in `Person` object, and all its references.
- `Person.findOne({lastName: 'Smith'}, {populate: {'address': {'city':true}}})` populates only 'address' in `Person` object, and 'city' in `Address` object, and all of the `City` references.
- `Person.findOne({lastName: 'Smith'}, {populate: {'address': ['city']}})` populates only 'address' in `Person` object, and 'city' in `Address` object, but none of the `City` references.
- `sort`: Sort the documents by the given field(s)
- `Person.find({}, {sort: '-age'})` sorts by age in descending order
- `Person.find({}, {sort: ['age', 'name']})` sorts by ascending age and then name, alphabetically
Expand Down
24 changes: 15 additions & 9 deletions lib/base-document.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const isValidType = require('./validate').isValidType;
const isEmptyValue = require('./validate').isEmptyValue;
const isInChoices = require('./validate').isInChoices;
const isArray = require('./validate').isArray;
const isObject = require('./validate').isObject;
const isDocument = require('./validate').isDocument;
const isEmbeddedDocument = require('./validate').isEmbeddedDocument;
const isString = require('./validate').isString;
Expand All @@ -24,7 +25,7 @@ const normalizeType = function(property) {
} else if (isSupportedType(property)) {
typeDeclaration.type = property;
} else {
throw new Error('Unsupported type or bad variable. ' +
throw new Error('Unsupported type or bad variable. ' +
'Remember, non-persisted objects must start with an underscore (_). Got:', property);
}

Expand Down Expand Up @@ -239,10 +240,10 @@ class BaseDocument {

_.keys(that._schema).forEach(function(key) {
let value = that[key];

if (that._schema[key].type === Date && isDate(value)) {
that[key] = new Date(value);
} else if (value !== null && value !== undefined &&
} else if (value !== null && value !== undefined &&
value.documentClass && value.documentClass() === 'embedded') {
// TODO: This should probably be in Document, not BaseDocument
value.canonicalize();
Expand Down Expand Up @@ -281,7 +282,7 @@ class BaseDocument {
return instance;
}

// TODO: Should probably move some of this to
// TODO: Should probably move some of this to
// Embedded and Document classes since Base shouldn't
// need to know about child classes
static _fromData(datas) {
Expand Down Expand Up @@ -311,7 +312,7 @@ class BaseDocument {
if (type.documentClass && type.documentClass() === 'embedded') {
// Initialize EmbeddedDocument
instance[key] = type._fromData(value);
} else if (isArray(type) && type.length > 0 &&
} else if (isArray(type) && type.length > 0 &&
type[0].documentClass && type[0].documentClass() === 'embedded') {
// Initialize array of EmbeddedDocuments
instance[key] = [];
Expand Down Expand Up @@ -346,7 +347,7 @@ class BaseDocument {
*
* TODO : EMBEDDED
* @param {Array|Document} docs
* @param {Array} fields
* @param {Boolean|Array|Object} fields
* @returns {Promise}
*/
static populate(docs, fields) {
Expand All @@ -372,7 +373,8 @@ class BaseDocument {

_.keys(anInstance._schema).forEach(function(key) {
// Only populate specified fields
if (isArray(fields) && fields.indexOf(key) < 0) {
if ((isArray(fields) && fields.indexOf(key) < 0) ||
(isObject(fields) && fields.constructor === Object && !fields.hasOwnProperty(key))) {
return;
}

Expand Down Expand Up @@ -441,8 +443,12 @@ class BaseDocument {
type = anInstance._schema[key].type;
}

let deepPopulate = false;
if(isObject(fields) && fields.constructor === Object)
deepPopulate = fields[key];

// Bulk load dereferences
let p = type.find({ '_id': { $in: keyIds } }, { populate: false })
let p = type.find({ '_id': { $in: keyIds } }, { populate: deepPopulate })
.then(function(dereferences) {
// Assign each dereferenced object to parent

Expand Down Expand Up @@ -594,4 +600,4 @@ class BaseDocument {
}
}

module.exports = BaseDocument;
module.exports = BaseDocument;
17 changes: 10 additions & 7 deletions lib/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const DB = require('./clients').getClient;
const BaseDocument = require('./base-document');
const isSupportedType = require('./validate').isSupportedType;
const isArray = require('./validate').isArray;
const isObject = require('./validate').isObject;
const isReferenceable = require('./validate').isReferenceable;
const isEmbeddedDocument = require('./validate').isEmbeddedDocument;
const isString = require('./validate').isString;
Expand Down Expand Up @@ -156,7 +157,7 @@ class Document extends BaseDocument {
*/
delete() {
const that = this;

let preDeletePromises = that._getHookPromises('preDelete');

return Promise.all(preDeletePromises).then(function() {
Expand Down Expand Up @@ -191,7 +192,7 @@ class Document extends BaseDocument {
if (query === undefined || query === null) {
query = {};
}

return DB().deleteMany(this.collectionName(), query);
}

Expand Down Expand Up @@ -226,7 +227,8 @@ class Document extends BaseDocument {
}

let doc = that._fromData(data);
if (populate === true || (isArray(populate) && populate.length > 0)) {
if (populate === true || (isArray(populate) && populate.length > 0) ||
(isObject(populate) && populate.constructor === Object && !_.isEmpty(populate))) {
return that.populate(doc, populate);
}

Expand Down Expand Up @@ -354,7 +356,8 @@ class Document extends BaseDocument {
let docs = that._fromData(datas);

if (options.populate === true ||
(isArray(options.populate) && options.populate.length > 0)) {
(isArray(options.populate) && options.populate.length > 0) ||
(isObject(options.populate) && options.populate.constructor === Object && !_.isEmpty(options.populate))) {
return that.populate(docs, options.populate);
}

Expand Down Expand Up @@ -413,7 +416,7 @@ class Document extends BaseDocument {
instancesArray[i]._id = null;
}
}*/

return instances;
}

Expand All @@ -425,7 +428,7 @@ class Document extends BaseDocument {
static clearCollection() {
return DB().clearCollection(this.collectionName());
}

}

module.exports = Document;
module.exports = Document;
Loading