Mongoose: presave document with reference - javascript

What is the best practice for saving a document with a reference to another collection's document if the _id of that is not immediately available?
var ModelA = new Schema({
aUniqueIdentifer: String,
...
)};
ModelA's aUniqueIdentifier is provided from another datasource, and is used by other models to identify it.
var ModelB = new Schema({
aUniqueForeignKey: type String,
aRef : {
type: mongoose.Schema.Types.ObjectID,
ref: 'ModelA'
}
)};
So I might save a modelA: modelA = new ModelA({aUniqueIdentifer: '500'});
Then to save a mobdelB, I need to populate it's aRef with the ModelA object. What is the best practice to do so? Should I do a findOne(aUniqueForeignKey) to return the object before trying to save? This doesn't seem terribly efficient.
I looked into populate, but that seems to be for existing references.

You can use the .pre method to create a method that runs before saving and then put your logic inside that. It looks like this:
ModelB.pre('save', function(next) {
// Check if id is available
// if not run another method
// run next() to exit
next();
});
This will run before anytime you save ModelB.
Hope this helps, if you add some more information I might be able to provide a more specific solution.
You could try using populate.
Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s)
http://mongoosejs.com/docs/populate.html

Related

Select collection if its array contains id in mongoose

Here's my code:
const events = await Event.find({'isFinished': false})
.where('attendants.employers').in(user._id);
Here's the model:
var eventSchema = new mongoose.Schema({
'attendants': {
'seekers': [mongoose.Schema.Types.ObjectId],
'employers': [],
},
'isFinished': {'type': Boolean, 'default': false},
});
I want to grab the events, which have the user's id in their attendants.employers array. I know I can filter them after downloading all events, but this is really inefficient.
Current code doesn't return any value. I tried flipping it around like so .where(user._id).in('attendants.employers');. But this causes node to say:
Error: in() must be used after where() when called with these arguments
Any idea how to achieve it, without downloading the data, and filtering it on the server?
Your find should look something like this:
Event.find({'isFinished': false, 'attendants.employers': user._id})
You do not need to do the where etc since in your find you list the rules and they will be AND-ed together.
You can use $in the other way around where you want to find out if a field in your document is IN a range of values.

Advice on structuring Backbone array data

I'm in the middle of a process where I'm retrieving data from a REST API for use in my Backbone.js application. At this point I don't need to do any manipulations to the data except for the fact that the data returned is an array. However, the future might bring such requests, so I've already created a Backbone model and collection for this type of data.
I've read that you could map your Array data into an object inside your Backbone collection, but I'm wondering since I already have a model, if it would be better practise to already map each element inside my Backbone model.
Since I'm not an expert in the Backbone.js framework, any links with more documentation about this section would be greatly appreciated.
UPDATE: I was actually looking for the parse method that is provided by the BackboneJS framework. By transforming the Array into an Object in the parse function I was able to solve the question.
You can use the parse method to parse any kind of transformation you'd like to do, like e.g. copying attributes, modifying attributes etc.
More information : http://backbonejs.org/#Collection-parse
Just as in the question you mentioned, this can achieved using parse, either on the Collection or the Model.
var UserModel = Backbone.Model.extend({
// String name is mapped to an object with the name property
parse: function(name) {
return {
name: name
};
}
});
var UserCollection = Backbone.Collection.extend({
model: UserModel
});
var collection = new UserCollection(['Ann', 'Joe', 'Jim', 'Bob'], {parse: true});
console.log(collection.at(0).get('name'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
In the example above, the collection is instantiated with data, in this case, parse doesn't run by default, so it needs to be set in the options, however if the collection normally gets its data from fetch, this will by default always parse.

Load includes on existing model

I'm trying to load includes on an existing model in sequelize. In express we pre check the models to see if they exist in the middleware.
So once we're in the actual "controller" we want to run some includes on that existing model that is passed in.
req.models.item.incude([
{model: Post, as: 'posts'}
])
Is there any way to accomplish this?
EDIT:
I know we can do something like this.
return req.models.item.getThing()
.then(function (thing) {
req.models.item.thing = thing;
return req.models.item;
});
But:
My expansions for includes are a dynamic property that come via url parameters, so they are not know ahead of time.
It I return the above you will not see the "thing" in the response. I need it nicely built as part of the original instance.
Something like a .with('thing', 'other.thing'); notation would be nice. Or in the case of sequelize .with({include: ...}); or .include([{model: ...}]);
If the variable req.models.item is already an Instance but without its other related instances ("includes"), then you could include them using something like the following code:
Item.findAll({
where: req.models.item.where(),
include: [{
model: SomeAssociateModel,
}]
})
.then(function(itemWithAssoc) {
// itemWithAssoc is an Instance for the same DB record as item, but with its associations
});
See here for some documentation. See here for a script demo'ing this.
Update: Given the instance, how do I just get the associated models?
To do this just use the automatically generated "getAssociation" getter functions, e.g.:
function find_associations_of_instance(instance) {
return instance.getDetails();
}
I've updated the script to include this as an example. For more information on these functions, see the SequelizeJS docs.

Referencing an object with an unknown model-type with Mongoose

Using Mongoose is it possible to have a field that references another object, when the model/type of that document is unknown?
For example, I have the models: Photos, Comments, Submissions, Posts, etc., and I would like to have a Like model that refers back to them:
var Like = new Mongoose.Schema({
// What would the value of `ref` be, should it just be left out?
target: { type: Schema.Types.ObjectId, ref: '*' }
});
From what I understand, ref needs to be a Model. I could leave it out all together, but would I still get the benefit of the Mongoose's populate method that way?
There are two approaches you can take.
1. Pass in the value of ref when you call populate
Based on the section Populating across Databases. When you call populate, you can specify the model you want to use.
Like.find().populate({
path: 'target',
model: 'Photo'
})
This requires that you know the model you want before you populate.
2. Store the value of ref together with the target
Based on the section Dynamic References.
You need to first adjust the target to something similar to the following:
var Like = new Mongoose.Schema({
target: {
kind: String,
item: {
type: Schema.Types.ObjectId,
refPath: 'target.kind'
}
}
});
target.kind is the value of "ref" that will be used for populate, and target.item is the ObjectId. We use refPath instead of ref for dynamic references.
Then, when you call populate, you will instead do something like:
Like.find().populate('target.item')
Note that we populate 'target.item' as opposed to just 'target'.

Mongoose.js: How to Implement Tree Structure via Population

I'm working on implementing a tree structure (similar to this one in the Mongo docs) using Mongoose 3.x, but I'm unsure of the best way to encapsulate all the logic for loading a specific node with its siblings and ancestors generally, and specifically how to best work with the population functionality where the ref is in the same collection as the ref-er.
For some context, the tree I'm working with is one in which nodes are not edited but new children might be added at any time to any node. So far I've got this working fine with a set of model methods that load objects after the initial find, but it seems there should be a better way to easily load a single branch with all the parent and sibling data I need with a single command in the controller, and encapsulate all the relevant population in some convenient find method on the model.
The basic Schema I'm trying to work with, then, might be something like this (also available here: https://gist.github.com/3889616):
// Sub-document to store parent ref along with it's value (a form of caching)
var Parent = new Schema({
id: ObjectId
, text: String
});
// Main tree-node element schema
var Branch = new Schema({
text: {
type: String
, required: true }
, date: {type: Date, default: Date.now }
, trail: [Parent]
, parentBranchId: ObjectId
, parentBranch: { type: Schema.Types.ObjectId, ref: 'Branch' }
, _children: [{type: Schema.Types.ObjectId, ref: 'Branch'}]
// These two have been commented out because I have no clue how to best implement
// , _priorSiblings: { type: Schema.Types.ObjectId, ref: 'Branch' }
// , _followingSiblings: { type: Schema.Types.ObjectId, ref: 'Branch' }
});
My hope would then be to be able to load a branch w/ the relevant related data via something like the following code, though at this point I'm pretty much lost and could be a good deal off base:
req.app.models.Branch
.findById(req.param("id"))
.populate("parentBranch")
.populate("parentBranch._children")
.exec(...)
Ultimately, I'd love to have something I could abstract into a "tree" plugin for Mongoose, but I think I've got to get this architected correctly first. Any ideas?
FWIW, at the end of the day, the data I really need for each branch is parent, next sibling, previous sibling (both in terms of creation time) and all children of parent.
Thanks in advance!
I know this question is old, but have you looked into the mongoose-tree module? https://github.com/franck34/mongoose-tree
It has a pretty good API for handling relationships between objects IMO.

Categories

Resources