-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
feat(collection): add colleciton level document mapping/unmapping #1698
Conversation
/ping @mbroadst thoughts? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good implementation. Only a few items I noted below.
Additionally, can you make sure to update the jsdoc descriptions for any relevant methods that this applies to?
As for the naming conventions, I am partial to map/unmap
.
@@ -0,0 +1,170 @@ | |||
'use strict'; | |||
var test = require('./shared').assert; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for writing tests!
-
Since we are testing against Node >= 4, please use ES6 syntax where possible. That includes
const
where possible,let
otherwise.class
instead of raw functions and prototypes.- template strings (
` `
) instead of string concatenation
-
instead of using our old assert library (which we mostly keep around for legacy reason), please use
chai.expect
for your assertions.
lib/collection.js
Outdated
const cursor = this.s.topology.cursor(this.s.namespace, findCommand, newOptions); | ||
|
||
// add a map function to the cursor if "deserialize" option is set | ||
if (this.s.options.deserialize && typeof this.s.options.deserialize === 'function') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this if
condition can be condensed into just typeof this.s.options.deserialize === 'function'
.
lib/collection.js
Outdated
@@ -2253,6 +2253,11 @@ var findAndModify = function(self, query, sort, doc, options, callback) { | |||
// Execute the command | |||
self.s.db.command(queryObject, finalOptions, function(err, result) { | |||
if (err) return handleCallback(callback, err, null); | |||
|
|||
if (result && result.value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like above, we can condense the if
statement and function call to:
if (result && result.value && typeof self.s.options.deserialize === 'function') {
result.value = self.s.options.deserialize(result.value);
}
And we can eliminate the use of deserializeDocument
}); | ||
|
||
client.connect(function(err, client) { | ||
var db = client.db(configuration.db); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line should be switched with the next one as follows:
client.connect(function(err, client) {
expect(err).to.eql(null);
const db = client.db(configuration.db);
...
If there is an error, client
will be null|undefined
, and the test will error out with the message TypeError: Cannot read property 'db' of undefined
. By switching the lines, the error message will be clearer.
@daprahamian Okay, I'd happily make these changes. And It's good news to be told to use more modern syntax. Are the drivers going to be rewritten using ES6 classes? It hurt my brain sometimes navigating through this code. And I'm definitely down with map / unmap. I'd be for serialize/unserialize if it was BSON specific, but currently, it's too hard to get this into BSON. I just ended up committing to what I had already prototyped. Another option I thought of, which is to be like another one of the drivers (I don't have time to look at which one at the moment), but it can look something like this: const collection = db.collection('users');
const mappedCollection = collection.asMapCollection(mapFn, unmapFn);
// ... or
const mappedCollection = db.mappedCollection('users', mapFn, unmapFn); And I can create a new MappedCollection class. I think for the most part, I'd just have to wrap most functions. What are your thoughts or preferences? |
@j we're currently taking a piecemeal approach to rewriting the driver in ES6; when we touch a file or do a major refactor, we try to bring it as up to date as possible. There are already some portions of the code rewritten with ES6 classes, and we will likely continue in the future. I'd hold off on the additional helpers you mentioned for right now, since it's basically sugar for |
@daprahamian sounds good, I'll try to get the unmap portion done by EOD tomorrow (hopefully today if I get time), and do more refactoring. Can you think of other areas I didn't touch that can possibly be mapped to a class on the top of your head? I want to make sure if you have "map/unmap" set, it's mapping everywhere a document ever gets returned (minus aggregations). |
@j I can't think of any other places that need the mapping. |
@daprahamian should I stick to await/async (in tests) or keep it vanilla callback style? |
@j Do not use async/await. Our tests are run against Node 4, Node 6, Node 8, and Node 10, and async/await can't be parsed by Node 6 or Node 4. Other than that, you can use either callbacks or Promises. |
@daprahamian I added unmap as well. I kind of want to add an option on the collection method so that you can opt out of the collection mapping if you want. I ended up doing an "instanceof" check on the unmap function as you can see in the tests to allow me to sort of over-ride it. I have to head out, so I'll get back on it tomorrow. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, just a few styling issues.
lib/collection.js
Outdated
doc._id = self.s.pkFactory.createPk(); | ||
} | ||
|
||
return unmap !== false ? unmap(doc) : doc; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can just be an implicit boolean return unmap ? unmap(doc) : doc
lib/collection.js
Outdated
} | ||
|
||
return docs.map(function(doc) { | ||
if (forceServerObjectId !== true && doc._id === void 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets make this if (forceServerObjectId !== true && doc._id == null) {
. I'd like to remove as much void 0
from the codebase as possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank goodnes ;)
lib/collection.js
Outdated
|
||
// no need to modify the docs if server sets the ObjectId | ||
// and unmap collection option is unset | ||
if (forceServerObjectId === true && unmap === false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (forceServerObjectId === true && !unmap)
} | ||
|
||
getFullName() { | ||
return this.firstName + ' ' + this.lastName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make this a string template:
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
collection.findOne({}, function(err, doc) { | ||
expect(err).to.be.null; | ||
|
||
expect(doc).to.eql({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When testing against objects, we prefer to use .to.deep.equal
instead of to.eql
. It is clearer what is going on here.
@daprahamian I added Let me know what you think. |
Now that I think about it, should I remove the ability to over-ride on the collection method level? If someone wants an unmapped collection, they can do: // collection with mapping
const mappedCollection = db.collection('users', {
map: /* ... */,
unmap: /* ... */,
});
// collection without mapping if they want to do things with raw documents
const collection = db.collection('users'); |
@daprahamian I went ahead and removed the unmap option from the cursor methods since you can just create another unmapped collection. I can bring it back. If these changes all look good, I can clean up the commits into one and be good to go! |
@daprahamian i have some time to finalize any changes today if you feel there are any, otherwise I can squash the commits and clean it up. Let me know. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 LGTM. Squash the commits and I can add it into the next beta release.
🤘 done! |
When the beta version that contains this is pushed, I'll work on top of it with my typescript mapping library and get a real feel of it. :) |
@daprahamian sorry forgot to make the mention. |
@j we unfortunately have to back this change out of the 3.1.0-beta branch before our next release. With mongodb/js-bson#253 and other approaches to document-class mapping on the table, we'd like to have further discussion before committing to any one API approach. My plan right now is as follows:
|
Reverts #1698. With mongodb/js-bson#253 and other approaches to document-class mapping on the table, we'd like to have further discussion before committing to any one API approach. Reopens NODE-1450
Reverts #1698. With mongodb/js-bson#253 and other approaches to document-class mapping on the table, we'd like to have further discussion before committing to any one API approach. Reopens NODE-1450
Reverts #1698. With mongodb/js-bson#253 and other approaches to document-class mapping on the table, we'd like to have further discussion before committing to any one API approach. Reopens NODE-1450
This is really cool! We use something similar but it's sometimes a pain to maintain. Love this implementation @j <3 |
@julien-c it’s coming! We just wanted a little more time to make a measured decision about it, sorry for the wait 😄 |
@daprahamian @mbroadst Quick question... I was working on top of the beta branch for a while and everything got borked and didn't realize it's because my feature was removed. I saw that PR in BSSON, which is an OK start? I just can't get the "target" option in the collection commands to be passed down to the BSON deserialize. Also, that PR didn't serialize either, so that might be a good one to add. I'd like to help with this, but I'm finding it a pain to figure out how to pass a client option down to the BSON methods. Any clue where I should look? Code bounces around quite a lot and I want to put it in the right place. PS. After working on top of my merged/reverted feature, everything was working pretty well :) |
@j we are not actively working on the feature, but will be focusing on it next quarter (read: later this summer). We will make sure to keep you apprised, as we want feedback from those who it will be useful to, but in the meantime I recommend making an intermediary module to monkey patch your support into the driver. Sorry for the delay, but we want to make sure this is done right the first time, as we are on the hook for maintaining the feature in the future. |
Sounds good. The PR right now in js-bson, he's using firstBatch and nextBatch to determine that it's a document. Is this the best way or are there other thoughts on how you'll want to do the class mapping? (mongodb/js-bson#253) |
Reverts mongodb#1698. With mongodb/js-bson#253 and other approaches to document-class mapping on the table, we'd like to have further discussion before committing to any one API approach. Reopens NODE-1450
This is a WIP to add mapping / unmapping on a collection level.
This can give document-mapper libraries a quicker way to map documents so that they don't have to wrap every collection's methods. (i.e. type-mongo-mapper)
Features to be implemented:
Basic Example:
Discussion: https://jira.mongodb.org/projects/NODE/issues/NODE-1450?filter=allissues
Related: https://github.com/mongodb/js-bson/issues/211
Also, my heart wants this to belong in the
bson
library, but right now BSON serialization seems to be handed over any javascript object instead of just the documents, so it's (de)serializing more than it should? (I'm unsure of how the internals work exactly). I know the C# BSON driver has advanced class mapping, so that's what made me believe this type of thing can go there. And I believe the serialization option above can be done through a toBSON() function on the class., but it'd still be nice to be more explicit on the collection level and let mapping libraries handle it for you.