Approach to read data from mongodb using reactive mongo irrespective of the structure - reactivemongo

I'm using reactivemongo.
While reading document from mongodb I have written code specific to the structure.
Structure
{
"name" : "test",
"age" : 3
}
For reading this I am using code like :
val cursor = collection.find(query).cursor[BSONDocument]
cursor.enumerate.apply(Iteratee.foreach { doc =>
var name: BSONDocument = doc.getAs[String]("name").get
var age: BSONDocument = doc.getAs[Int]("age").get
}
So now if later on, BSON structure gets changed like:
{
"name" : {
firstName : "fname",
lastName : "lname"
},
"age" : 3
}
So now I have to change my code for reading it
val cursor = collection.find(query).cursor[BSONDocument]
cursor.enumerate.apply(Iteratee.foreach { doc =>
var name: BSONDocument = doc.getAs[BSONDocument]("name").get
var fname : String = name.getAs[String]("firstName").get
var lname : String = name.getAs[String]("lastName").get
var age: BSONDocument = doc.getAs[Int]("age").get
}
I want to keep the data, which is currently using the old structure (i.e "name" as string) and insert new data using the new structure (i.e "name" as BSONDocument).
While reading document with old structure an exception "None.get" is thrown, because as per the read method "name" should be a BSONDocument.
What should be my approach to handle this issue??

This is a typical migration issue. And there are several ways to deal with migration. Typical solutions are:
Migrate all existing documents in the database to fit your new format (either using the mongo shell, scripts or you can even include it in your application on startup.). There is currently no "auto migration" plugin for mongo available that does the job for you (I am working on one, but it is not yet finished).
Make your application code aware of different schema versions. This means writing your BSONDocument parsing logic in a way that it accepts both versions.

Related

mongoose - have optional additional implicit fields

I have a schema with a field in which I can store anything : new Schema({settings : {}}).
I have a database with
I want to keep this ability to add data without adding new fields, but for some of the fields have default values if they are not present.
I can do the following :
new Schema({
settings : {
key : { type : String, default: "abc" }
// I want to be able to add data that contains more than just "key"
}
});
I just want to make sure that when requesting the data from this schema, I will still get all the data, and not just the keys explicitly defined ?
It seems to work, but I want to make sure that I can still :
read all the data
still write arbitrary data (ie. not necessarily defined in the schema)
Are there rules on mongo/mongoose that would prevent me from doing one of these two things (I'm very unsure for the writing part) ? If there is such a "feature", how can it be done ?
Note : I saw this question. Correct me if I am wrong, but the fields are not implicit (like in the first case with {}), and have to be defined (it's actually the opposite question).
Edit : I now saw also this question that addresses my concerns (even if the accepted solution sounds more like a workaround to me). But in my case I already have data stored so (1 - disable strict) would mean writing a lot of validation code to be safe (because a lot of keys, this is the biggest collection of the app), and (2 - mixed schemas) would require to migrate the data of this specific sub-element... In short : I would still welcome a solution to my particular problem.
I think you will want to build your own custom validation here rather than rely on the defauly schema type validation methods. Luckily, mongoose has a facility for this:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/test');
var testSchema = new Schema({
settings: {}
});
var Test = mongoose.model( 'Test', testSchema, "test" );
testSchema.path('settings').validate(function(value) {
return Object.keys(value).indexOf("key") != -1;
},'Error "settings" must contain "key" as "settings.key"');
//var test = new Test({ settings: { "key": "something" } });
var test = new Test({ settings: { } });
test.save(function(err) {
try {
if (err) throw err;
console.log(test);
} catch (e) {
console.log(e);
}
});
So basically, I have set up the validate function there for the "settings" path in the schema to look for the presence of "key" with it's own object. Where that "key" does not exist, an exception is reported in the errors.
Like any such errors, it will be returned within the err object when you .save() the object, thus blocking the write. It can be then be acted on to handle the error however you want, with the message that was defined reported.
So that is a self contained "test" where you can alternately uncomment the valid data for the object and successfully save that object without errors being reported.
Alternately you can do a "pre save" to fill in some default data:
testSchema.pre("save",function(next) {
this.settings = (this.settings) ? this.settings : {};
if (Object.keys(this.settings).indexOf("key") == -1)
this.setting.key = "abc";
next();
});
Which fills in a default if it is not already there.
Try to use like this way
new Schema({
settings : {}
});
var modelObj = new myModel();
modelObj.settings.key = "keyval";
modelObj.settings.key1 = "keyval";
modelObj.settings.key2 = "keyval";
modelObj.settings.key3 = "keyval";
modelObj.save(function(err){
//handle
});

Finding a document, converting it into an object, and identifying it with a variable in Mongoose

Background: I have a mongo database named test with a collection called collection. There is a single document in test.collection:
{ "_id" : ObjectId("64e4a6f9d1d7ba45250dc2c1"), "key" : "value"}
Question: Using Mongoose, what is a way to grab the lone document found in test.collection, convert it into a javascript object, and to identify it with the variable object? For example, we should have that
console.log(object.key)
returns "value".
EDIT : I have tried the following, which didn't work:
var Schema = mongoose.Schema;
var Model = db.model('Model', mongoose.Schema({}),'collection');
var doc = Model.findOne();
console.log(doc.key); // doesn't show "value" as expected
Do it this way (as stated on my comment):
Model.find(function (err, docs) {
if (err) return console.error(err);
console.log(docs[0].key);
});
I also recommend taking another look to the docs, it's always good to refresh the basic concepts.

PATCH method to MongoDB using Node

I want to create a PATCH method for my API but there is something I don't understand. Imagine I have the following document in my MongoDB database:
{
_id : ObjectId(1234...),
name : "bob",
age : 30
}
Now I would like to update this document but I don't know what keys my API will receive. So imagine I make a request in order to change the age but also add a last_name.
The request result would be like this:
{
_id : ObjectId(1234...),
name : "bob",
last_name : "smith",
age : 44
}
The main problem here is that I don't know the arguments I will receive.
My goal is to update the values of the existing keys and add the keys that are not in the document.
Any idea?
Thanks
You want to use the $set operator.
What this does is only updates the keys that are sent in the update query. Without $set, it will overwrite the whole object, which is obviously not what you want.
app.patch('/user/:id', function (req, res) {
var updateObject = req.body; // {last_name : "smith", age: 44}
var id = req.params.id;
db.users.update({_id : ObjectId(id)}, {$set: updateObject});
});
I'm assuming a couple things here:
You are using express.
You are using either the mongodb driver or mongojs npm module

Modelling reference to embedding document using Mongoose

I am modelling two types of events (events and subevents) in a MongoDB like this:
var EventSchema = mongoose.Schema({
'name' : String,
'subEvent' : [ SubeventSchema ]
});
var SubeventSchema = mongoose.Schema({
'name' : String
});
Now when I query a subevent I want to be able to also retrieve data about its corresponding superevent, so that some example data retrieved using Mongoose population feature could look like this:
EventModel.findOne({
name : 'Festival'
})
.populate('subEvent')
.execute(function (err, evt) { return evt; });
{
name : 'Festival',
subEvent: [
{ name : 'First Concert' },
{ name : 'Second Concert' }
]
}
EventModel.findOne({
'subEvent.name' : 'FirstConcert'
}, {
'subEvent.$' : 1
})
.populate('superEvent') // This will not work, this is the actual problem of my question
.execute(function (err, subevt) { return subevt; });
{
name: 'First Concert',
superEvent: {
name: 'Festival'
}
}
A solution I can think of is not to embed but to reference like this:
var EventSchema = mongoose.Schema({
'name' : String,
'subEvent' : [ {
'type' : mongoose.Schema.Types.ObjectId,
'ref' : 'SubeventSchema'
} ]
});
var SubeventSchema = mongoose.Schema({
'name' : String,
'superEvent' : {
'type' : mongoose.Schema.Types.ObjectId,
'ref' : 'EventSchema'
}
});
I am looking for a solution based on the first example using embedded subevents, though. Can this be achieved and in case yes, how?
I think your mental model of document embedding isn't correct. The major misunderstanding (and this is very common) is that you "query a subevent" (query an embedded document). According to your current Event schema, a Subevent is just a document embedded in an Event document. The embedded SubEvent is not a top-level document; it's not a member of any collection in MongoDB. Therefore, you don't query for it. You query for Events (which are the actual collection-level documents in your schema) whose subEvents have certain properties. E.g. one way people translate the query
db.events.find({ "subEvent" : { "name" : "First Concert" } })
into plain English is as "find all the subevents with the name "First Concert". This is wrong. The right translation is "find all events that have at least one subevent whose name is "First Concert" (the "at least one" part depends on knowledge that subEvent is an array).
Coming back to the specific question, you can hopefully see now that trying to do a populate of a "superevent" on a subevent makes no sense. Your queries return events. The optimal schema, be it subevents embedded in events, one- or two-way references between events and subevents documents in separate collections, or events denormalized into the constituent subevent documents, cannot be determined from the information in the question because the use case is not specified.
Perhaps this is a situation where you need to modify your thinking rather than the schema itself. Mongoose .populate() supports the basic ideas of MongoDB "projection", or more commonly referred to as "field selection". So rather than try to model around this, just select the fields you want to populate.
So your second schema form is perfectly valid, just change how you populate:
EventModel.find({}).populate("subEvent", "name").execute(function(err,docs) {
// "subevent" array items only contain "name" now
});
This is actually covered in the Mongoose documentation under the "populate" section.

How do I get this nested schema working in Mongoose?

So I'm trying to figure out how to how to save multiple commands in a command list but everything I've tried hasn't worked. This is how I have it set up so far but when it saves, it saves in the format of
"command_list" : [ { "action" : "goto,goto", "target" : "http://www.google.com,http://www.cnn.com" } ]
when I really want something like
"command_list" : [ "command" : { "action" : "goto", "target" : "http://www.google.com" },
"command" : { "action" : "goto", "target" : "http://www.cnn.com" } ]
where there are multiple commands. So far my app.js is storing the data like this
var configSample = new Configurations({
command_list_size: request.body.command_list_size,
command_list: [ {action: request.body.action, target: request.body.target}]
});
and the model looks like this
var mongoose = require("mongoose");
var command = mongoose.Schema({
action: String,
target: String
});
var configSchema = mongoose.Schema({
command_list_size: Number,
command_list: [command]
});
module.exports = mongoose.model('Configurations', configSchema);
So how do I get that nesting action going? Thanks!
It looks like you're not packing the data right when you send it to the server. If you use the following:
command_list: [ {action: request.body.action, target: request.body.target}]
it's going to grab all of the actions and lump them together and do the same with the targets. You'd be better off sending an array to your server with the documents already nested in them.
The other option would be to parse the data to pull out the elements once you receive it on your server, but I think it'd be easier to just package it right in the first place.
ADDITION:
If you wanted to split what you have, you could use the String.split() method and rebuild the object:
// not certain the chaining will work like this, but you get the idea. It works
// on the string values you'll receive
var actions = response.body.action.split(',');
var targets = response.body.target.split(',');
// the Underscore library provides some good tools to manipulate what we have
// combined the actions and targets arrays
var combinedData = _.zip(actions, targets);
// go through the combinedData array and create an object with the correct keys
var commandList = _.map(combinedData, function(value) {
return _.object(["action", "target"], value)
});
There may be a better way to create the new object, but this does the trick.
EDIT:
I created a question about trying to refactor the above code here.

Resources