Context
So we have migrated from Parse.com to an hosted MongoDB database. Now I have to write a script that queries our database directly (not using Parse).
I'm using nodejs / mongoose and am able to retrieve these documents.
Problem
Here is my schema so far:
var StorySchema = new mongoose.Schema({
_id: String,
genre: String
});
var ActivitySchema = new mongoose.Schema({
_id: String,
action: String,
_p_story: String /* Also tried: { type: mongoose.Schema.Types.ObjectId, ref: 'Story' } and { type: String, ref: 'Story' }*/,
});
I would like to write a query that fetches theses documents with the related Story (stored as a pointer).
Activity
.find({
action: 'read',
})
.exec(function(error, activities) {
activities.forEach(function(activity) {
// I would like to use activity._p_story or whatever the mean to access the story here
});
});
Question
Is there a way to have the fetched activities populated with their story, given that the _p_story field contains Story$ before the object id?
Thanks!
One option I have been looking at is the ability to create a custom data type for each pointer. The unfortunate side is Parse treats these as 'belongsTo' relationships and but does not store the 'hasMany' relationship that Mongoose wants for populate(). But once this is in place you can easily do loops to get the relational data. Not ideal but works and is what populate is really doing under the hood anyways.
PointerTypeClass.js -> This would work for populating the opposite direction.
var Pointer = function(mongoose) {
function PointerId(key, options) {
mongoose.SchemaType.call(this, key, options, 'PointerId');
}
PointerId.prototype = Object.create(mongoose.SchemaType.prototype);
PointerId.prototype.cast = function(val) {
return 'Pointer$' + val;
}
return PointerId;
}
module.exports = Pointer;
Also be sure mongoose knows about the new type by doing mongoose.Schema.Types.PointerId = require('./types/PointerTypeClass')(mongoose);
Lastly. If you are willing to write some cloudcode you could create the array of ids for your populate to know about the objects. Basically in your Object.beforeSave you would update the array of the id for the relationship. Hope this helps.
Related
So I've decided to use graphql as my query engine along side with mongodb. So I created my schemas and everything looks great, BUT, one of my schemas contains a list of Strings, for instance:
exports.default = new gql.GraphQLInputObjectType({
name: 'myModel',
fields: {
type: { type: gql.GraphQLString },
workingDays: { type: new gql.GraphQLList(GraphQLString) }
}
});
So in the workingDays list I have 50 elements, and I'd like to change one of them, is there a way to do that with Graphql?
It just so happens to be a string type inside but, it could be an object as well.
Thanks.
You can add a new mutation that encodes this functionality.
For example updateWorkingDays(modelId: ID!, index: Int!, workDay: String) that updates the working day of model modelId at index to the new workDay.
In my application I have a User Collection. Many of my other collections have an Author (an author contains ONLY the user._id and the user.name), for example my Post Collection. Since I normally only need the _id and the name to display e.g. my posts on the UI.
This works fine, and seems like a good approach, since now everytime I deal with posts I don`t have to load the whole user Object from the database - I can only load my post.author.userId/post.author.name.
Now my problem: A user changes his or her name. Obviously all my Author Objects scattered around in my database still have the old author.
Questions:
is my approuch solid, or should I only reference the userId everywhere I need it?
If I'd go for this solution I'd remove my Author Model and would need to make a User database call everytime I want to display the current Users`s name.
If I leave my Author as is, what would be a good way to implement a solution for situations like the user.name change?
I could write a service which checks every model which has Authors of the current user._id and updates them of course, but this sounds very tedious. Although I'm not sure there's a better solution.
Any pro tipps on how I should deal with problems like this in the future?
Yes, sometime database are good to recorded at modular style. But You shouldn't do separating collection for user/author such as
At that time if you use mongoose as driver you can use populate to get user schema data.
Example, I modeling user, author, post that.
var UserSchema = new mongoose.Schema({
type: { type: String, default: "user", enum: ["user", "author"], required: true },
name: { type: String },
// Author specific values
joinedAt: { type: Date }
});
var User = mongoose.model("User", UserSchema);
var PostSchema = new mongoose.Schema({
author: { type: mongoose.Scheam.Types.ObjectId, ref: "User" },
content: { type: String }
});
var Post = mongoose.model("Post", PostSchema);
In this style, Post are separated model and have to save like that. Something like if you want to query a post including author's name, you can use populate at mongoose.
Post.findOne().populate("author").exce(function(err, post) {
if(err)
// do error handling
if(post){
console.log(post.author.type) // author
}
});
One solution is save only id in Author collection, using Ref on the User collection, and populate each time to get user's name from the User collection.
var User = {
name: String,
//other fields
}
var Author = {
userId: {
type: String,
ref: "User"
}
}
Another solution is when updating name in User collection, update all names in Author collection.
I think first solution will be better.
I have a product model, it has many fields. Some of them are dedicated to front-end application, ex:
var GameSchema = new Schema({
likes: {
type: [{
type: Schema.ObjectId,
ref: 'User'
}]
},
likes_count: {
type: Number
}
});
I don't need likes_count field in Db, but controller returns only fields that model have, so i add likes_count field to db model
exports.some_method = function(req, res){
var game = req.game;
game.likes_count = game.likes.length
res.json(game);
}
Is there a way to add extra data to db model when sending request without having them in db?
Please note, problem is not in likes_count field itself, i have different models, but the point is having extra data on db model.
For those who still interested, mongo_db mongoose(#robertklep) has virtual fields, that can be used as temporary data field, that doesn't exist in database
GameSchema.virtual('likes_count').get(function () {
return this.likes.length;
});
And note, your model must have permission for virtuals like this, so that you can use it inside controllers
var UserSchema = new Schema({
username: {
type: String
}
}, {
toObject: { virtuals: true },
toJSON: { virtuals: true }
});
"Is there a way to add extra data to db model when sending request without having them in db?"
You may be able to do so from a driver's perspective and I'll leave that to those who know abut such things. Check out the following post Mapping a private backing field with MongoDB C#.
I can answer from the MongoDB engine & server processes aspect; if you are looking for a way to flag a field in the JSON document to make it private when sent to the actual CRUD request the MongoDB engine receives then no.
However, you could intercept the JSON prior to the actual CRUD request and transform it. The JSON you are generating is not inserted until you execute one of the INSERT, Modify, or Update statements. The pseudo steps would be to generate a JSON document, send it to a broker\wrapper etc in front of MongoDB, and then transform it by removing the fields in question, then send the new object as a CRUD request to the MongoDB engine.
I have the following schema in my Node js / express app where each warehouse can optionally have a parent warehouse. I wrote the code to save warehouse, but can't figure out how to retrieve any warehouse (which has a parent) and get its parent warehouse name...so was wondering if there is any way I can do that in one call? Thanks
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var warehouseSchema = new Schema({
name: { type: String, required: true },
parentID: { type: String, ref: 'Warehouse' }
});
module.exports = mongoose.model('Warehouse', warehouseSchema);
Just as Philipp said, you can't do it with a single MongoDB auery.
But you can do it with single Mongoose command, using its Query Population feature:
Warehouse.findById(warehouse_id).populate('parentID').exec(function(err, doc) {
if (err) {
// something get wrong
} else {
// doc.parentID is a parrent Warehouse here
}
})
Internally, Mongoose will make two separate queries to MongoDB (one for the child document, and then another for its parent), bu for you it'll look like a single command.
MongoDB can't do JOINs on the database. As long as the parent is a reference in the warehouse object, you can not retrieve both with one query. You first have to query the warehouse(s) and then resolve the reference(s) with a second query.
To avoid the second query you might want to look into embedding the required information about the parent-document in the child-document. Duplicate information in MongoDB isn't as abnormal as in a relational database.
I'm having troubles while trying to handle arrays of objectIds (for further population in mongoosejs models), I hope you can help me. here is the context:
I have a little nodejs / angularjs app where I have products and providers. The products have a property providers which is an array of objectId's, referencing providers. Here is the schema:
var productSchema = new Schema({
name : { type: String, required: true, trim: true},
providers : [{ type: Schema.Types.ObjectId, ref: 'provider' }]
});
The user is able to query the list of providers through the GUI. Then he can select which provider are available for each product (checkboxes). Internally it means that I am pushing the hex strings corresponding to the providers _id into the array providers of my angular product model, then the request is sent to the nodejs/express server.
Here is the problem, I've been trying to turn the hex strings into objectId in many ways, I just can't find the right way. My last attempt was with the "presave" function of mongoose. Here the current version.
productSchema.pre('save', function (next) {
var err = new Error('There was an error when trying to add the providers id as objectId'),
i = 0;
for (i = 0; i < this.providers.length; i++) {
this.providers[i] = mongoose.Types.ObjectId(this.providers[i].toString())
}
next(err);
});
Any help is welcome, I thought mongoose would just handle everything alone. What's the normal way to do this? (the pre save was just an idea)
Thanks for reading