Skip to content
This repository was archived by the owner on Feb 10, 2022. It is now read-only.

Commit 2aad859

Browse files
author
vdemedes
committed
added population to queries
1 parent d8f3b81 commit 2aad859

File tree

4 files changed

+161
-21
lines changed

4 files changed

+161
-21
lines changed

Readme.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,51 @@ posts = yield Post.where('position').gt(5).find();
104104
posts = yield Post.gt('position', 5).find();
105105
```
106106

107+
#### Find and populate
108+
109+
Let's say you have such Post document:
110+
```json
111+
{
112+
"_id": ObjectId("5234d25244e937489c000004"),
113+
"title": "Great title",
114+
"body": "Great body",
115+
"comments": [
116+
ObjectId("5234d25244e937489c000005"),
117+
ObjectId("5234d25244e937489c000006"),
118+
ObjectId("5234d25244e937489c000007")
119+
]
120+
}
121+
```
122+
123+
And you need to fetch each Comment document from *comments* field:
124+
125+
```javascript
126+
var post = yield Post.findOne();
127+
var comments = post.get('comments');
128+
129+
var index = 0;
130+
var commentId;
131+
132+
while (commentId = comments[index]) {
133+
comments[index] = yield Comment.findById(commentId);
134+
135+
index++;
136+
}
137+
```
138+
139+
With populating, you don't need to write all that and instead do this:
140+
141+
```javascript
142+
// .populate() tells query to populate comments field
143+
// with documents fetched using Comment model
144+
var post = yield Post.populate('comments', Comment).findOne();
145+
var comments = post.get('comments');
146+
147+
// comments is an array of Comment models now
148+
```
149+
150+
**Note**: When you will try to save a document with populated fields, they will be reverted back to _id's.
151+
107152
#### Find one
108153

109154
Finds only one document. Returns either Model instance (Post, in these examples) or undefined.

lib/mongorito.js

Lines changed: 76 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,9 @@ Function.prototype.around = function (action) {
6666
var Model;
6767

6868
var InstanceMethods = {
69-
initialize: function (attrs) {
69+
initialize: function (attrs, options) {
7070
this.attributes = attrs || {};
71+
this.options = options || {};
7172
this.collection = Mongorito.collection(this.collection);
7273
this.configure();
7374
},
@@ -146,18 +147,30 @@ var InstanceMethods = {
146147
var id = this.get('_id');
147148
var fn = id ? this.update : this.create;
148149

149-
yield this.runHooks('before', 'save');
150+
// revert populated documents to _id's
151+
var populate = this.options.populate;
152+
for (var key in populate) {
153+
var value = this.get(key);
154+
155+
if (value instanceof Array) {
156+
value = value.map(function (doc) {
157+
return doc.get('_id');
158+
});
159+
} else {
160+
value = value.get('_id');
161+
}
162+
163+
this.set(key, value);
164+
}
150165

166+
yield this.runHooks('before', 'save');
151167
var result = yield fn.call(this);
152-
153168
yield this.runHooks('after', 'save');
154169

155170
return result;
156171
},
157172

158173
create: function *() {
159-
yield this.runHooks('before', 'create');
160-
161174
var collection = this.collection;
162175
var attrs = this.attributes;
163176

@@ -167,8 +180,9 @@ var InstanceMethods = {
167180
updated_at: timestamp
168181
});
169182

170-
var doc = yield collection.insert(attrs);
183+
yield this.runHooks('before', 'create');
171184

185+
var doc = yield collection.insert(attrs);
172186
this.set('_id', doc._id);
173187

174188
yield this.runHooks('after', 'create');
@@ -177,30 +191,26 @@ var InstanceMethods = {
177191
},
178192

179193
update: function *() {
180-
yield this.runHooks('before', 'update');
181-
182194
var collection = this.collection;
183195
var attrs = this.attributes;
184196

185197
var timestamp = Math.round(new Date().getTime() / 1000);
186198
this.set('updated_at', timestamp);
187199

200+
yield this.runHooks('before', 'update');
188201
yield collection.updateById(attrs._id, attrs);
189-
190202
yield this.runHooks('after', 'update');
191203

192204
return this;
193205
},
194206

195207
remove: function *() {
196-
yield this.runHooks('before', 'remove');
197-
198208
var collection = this.collection;
199209

210+
yield this.runHooks('before', 'remove');
200211
yield collection.remove({
201212
_id: this.get('_id')
202213
});
203-
204214
yield this.runHooks('after', 'remove');
205215

206216
return this;
@@ -266,7 +276,11 @@ var StaticMethods = {
266276
}
267277
};
268278

269-
['where', 'limit', 'skip', 'sort', 'exists', 'lt', 'lte', 'gt', 'gte', 'in', 'nin', 'and', 'or', 'ne', 'nor'].forEach(function (method) {
279+
[
280+
'where', 'limit', 'skip', 'sort', 'exists',
281+
'lt', 'lte', 'gt', 'gte', 'in', 'nin',
282+
'and', 'or', 'ne', 'nor', 'populate'
283+
].forEach(function (method) {
270284
StaticMethods[method] = function () {
271285
var collection = this.collection();
272286
var model = this;
@@ -291,7 +305,7 @@ var Query = Class.extend({
291305
this.collection = collection;
292306
this.model = model;
293307
this.query = {};
294-
this.options = {};
308+
this.options = { populate: {} };
295309
this.lastKey = key;
296310
},
297311

@@ -301,7 +315,7 @@ var Query = Class.extend({
301315
for (key in conditions) {
302316
this.where(key, conditions[key]);
303317
}
304-
} else {
318+
} else if (typeof arguments[0] == 'string') {
305319
if (!value) {
306320
this.lastKey = key;
307321
return this;
@@ -358,6 +372,12 @@ var Query = Class.extend({
358372
return this;
359373
},
360374

375+
populate: function (key, model) {
376+
this.options.populate[key] = model;
377+
378+
return this;
379+
},
380+
361381
count: function *(query) {
362382
this.where(query);
363383

@@ -374,12 +394,49 @@ var Query = Class.extend({
374394

375395
var collection = this.collection;
376396
var model = this.model;
397+
var options = this.options;
377398

378-
var docs = yield collection.find(this.query, this.options);
399+
var docs = yield collection.find(this.query, options);
379400

380-
return docs.map(function (doc) {
381-
return new model(doc);
382-
});
401+
var index = 0;
402+
var doc;
403+
404+
while (doc = docs[index++]) {
405+
// options.populate is a key-model pair object
406+
for (var key in options.populate) {
407+
// model to use when populating the field
408+
var model = options.populate[key];
409+
410+
var value = doc[key];
411+
412+
// if value is an array of IDs, loop through it
413+
if (value instanceof Array) {
414+
var subdocs = value.map(function (id) {
415+
return model.findById(id.toString());
416+
});
417+
418+
value = yield subdocs;
419+
} else {
420+
value = yield model.findById(value);
421+
}
422+
423+
// replace previous ID with actual documents
424+
doc[key] = value;
425+
}
426+
427+
// index - 1, because index here is already an index of the next document
428+
docs[index - 1] = new model(doc, {
429+
populate: options.populate
430+
});
431+
}
432+
433+
return docs;
434+
},
435+
436+
findOne: function *(query) {
437+
var docs = yield this.find(query);
438+
439+
return docs[0];
383440
},
384441

385442
remove: function *(query) {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mongorito",
3-
"version": "0.4.3",
3+
"version": "0.4.4",
44
"description": "ES6 generator-based MongoDB ODM. It rocks.",
55
"author": "Vadim Demedes <vdemedes@gmail.com>",
66
"dependencies": {

test/mongorito.test.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Function.prototype.isGenerator = function () {
3636
});
3737

3838
describe ('Mongorito', function () {
39-
var Post;
39+
var Post, Comment;
4040

4141
before (function () {
4242
Mongorito.connect('localhost/mongorito_test');
@@ -46,10 +46,15 @@ describe ('Mongorito', function () {
4646
Post = Model.extend({
4747
collection: 'posts'
4848
});
49+
50+
Comment = Model.extend({
51+
collection: 'comments'
52+
});
4953
});
5054

5155
beforeEach (function *() {
5256
yield Post.remove();
57+
yield Comment.remove();
5358
});
5459

5560
describe ('Model', function () {
@@ -361,6 +366,39 @@ describe ('Mongorito', function () {
361366
var posts = yield Post.where('index').in([4, 5]).find();
362367
posts.length.should.equal(2);
363368
});
369+
370+
it ('should populate the response', function *() {
371+
var n = 3;
372+
var comments = [];
373+
374+
while (n--) {
375+
var data = commentFixture();
376+
var comment = new Comment(data);
377+
yield comment.save();
378+
379+
comments.push(comment);
380+
}
381+
382+
var data = postFixture({
383+
comments: comments.map(function (comment) { return comment.get('_id'); })
384+
});
385+
var post = new Post(data);
386+
yield post.save();
387+
388+
var createdPost = yield Post.populate('comments', Comment).findOne();
389+
createdPost.get('comments').forEach(function (comment, index) {
390+
comment.get('_id').toString().should.equal(comments[index].get('_id').toString());
391+
});
392+
393+
// now confirm that populated documents
394+
// don't get saved to database
395+
yield createdPost.save();
396+
397+
createdPost = yield Post.findOne();
398+
createdPost.get('comments').forEach(function (id, index) {
399+
id.toString().should.equal(comments[index].get('_id').toString());
400+
});
401+
});
364402
});
365403

366404
describe ('Hooks', function () {

0 commit comments

Comments
 (0)