Mongoose.js: How to Implement Tree Structure via Population - javascript

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.

Related

Is it better to store objects containing information in a separate .js file or in my database?

I'm creating an RPG game web app and would like to store predetermined information about skills and traits. I.e. an array of objects defining things like "Battle-hardened gives +2 strength" and "Ruthless past gives -1 social".
I'm creating this using Express and NodeJS for my application, Mongoose & MongoDB for my database, and HTML as the front end. I am relatively new to this, so apologies if this is a simple question!
My initial thought was to create a Mongoose Schema for relevant attributes, i.e.:
const attributeSchema = new Schema({
type: String, //i.e. trait, skill, background
description: String, //flavortext
modifiers: {
stats: {
strength: Number,
social: Number,
//etc...
},
skills: {
melee: Number,
ballistics: Number,
//etc...
}
}
});
Then, I would create a .js file to run which creates and saves all of the information to MongoDB via the above schema, thereby "seeding" the database. If I ever need to add new traits/skills (e.g. in the future I introduce a new trait "Smelly Armpits" or something that causes -3 social), I would have to create new scripts to add those. ...unless there's a better way of doing this that I haven't learned!
My other idea was to just put this all in a static .js file as one object that I could require when necessary or as an app.locals variable. E.g.
const attributes = {
traits: [
{
name: 'Coffee Breath',
description: 'Try brushing your teeth sometime!',
modifiers: [{social: -1}]
},
{
name: 'Echoes of the Void',
description: 'The voices, they whisper to you...',
modifiers: [{sanity: -999}, {intelligence: +2}]
}
],
//etc...
}
Please let me know if there is a recommended way to do this, or if there is something I am missing/could be doing better!
For a simple project, storing such things in a json file is probably a better idea.
Given your characters don't inherit properties from each other, setting up MongoDB is not the wisest choice in my opinion.
Additionally, often times setting up Mongoose and a database takes effort and is overly complicated for a given use case.

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.

Mongoose: presave document with reference

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

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'.

How does one store "pointers" to nested nodes in an immutable tree in React/Javascript?

I have a immutable nested tree (mori, immutable-js et al) consisting of arbitrary nodes, think file browser. The tree gets rendered by React. If a component representing a node receives focus, I'd like to:
Show an input field on the node component to change e.g. the node name.
Show a global panel that contains UI components to edit additional properties of the currently focused node, depending on the type of the selected node.
A simplistic state object could look like this:
{
root: {
type: 'folder'
name: 'Root',
focused: false,
children: [
{
type: 'text',
name: 'Essay',
focused: false
},
{
type: 'folder',
name: 'Images',
focused: false,
children: [
{
type: 'image',
name: 'Paris',
focused: true
}
]
}
]
},
currentlyFocusedNode: <Reference to Paris node>
}
Now, how would I keep a valid reference to the currently focused node? If I stored a Paris node reference at currentlyFocusedNode, it would be out of sync as soon as any other part of the app modifies the node (e.g. the inline name input from above). I thought about storing the path to the focused node, which I could use to retrieve the current reference:
currentlyFocusedNode: ['root', 'children', 0, 'children', 0]
But this seems very shaky to me as well, because even a simple node move operation on the tree could leave this path pointing to the wrong or even a non-existing node.
How are those kind of things handled with immutable data structures? Or am I not thinking "immutable" enough at all?
Your question does not have an answer as asked. And it seems you had a sense of that when you said
Or am I not thinking "immutable" enough at all?
First you say you are using an immutable data structure. Meaning it can't be changed.
Then you say:
If I stored a Paris node reference at currentlyFocusedNode, it would
be out of sync as soon as any other part of the app modifies the node
With immutable data structures things don't get modified. What you have is an old version of the data and a new version of the data. Neither will ever change.
So the question should really be something like:
How do I identify when 2 nodes represent the same data?
Answer is use an ID.
Another good question might be:
Given a reference to an old node, tree, and newData, how can I update the tree and keep track of the node?
This depends largely on how the rest of the code works and what parts you have access to. E.g. you could have something like this:
function updateNodeInTree(nodeId, newData, inTree){
oldNode = findNode(nodeId, inTree);
newNode = merge(oldNode, newData);
newTree = merge(inTree, {path: {to: newNode}});
return [newTree, newNode];
}
But if you don't have access to where the tree is updated, you may have to settle for:
newTree = updateTree(oldTree, someData);
newNode = findNode(nodeId, newTree);
Give each node a unique ID. Maintain a focused_id value.
Any component rendering parts of a node can check if
this.props.node.id === this.props.focused_id
and render appropriately.
This is where Flux architecture pattern comes in.
I would handle it like this:
Save the root and currentlyFocusedNode in a TreeStore where currentlyFocusedNode either saves null or references an object in the tree.
When a node is clicked, an event is dispatched (using AppDisptacher) with eventType: 'nodeClicked' and payload containing the object (reference) which was clicked.
TreeStore listens to AppDispatcher and makes changes to currentlyFocusedNode
React component listens to changes in TreeStore and re-renders whenever there is a change.

Categories

Resources