Iterate Mongoose Properties in Jade - node.js

I'm trying to output a list of properties from a Mongoose object, but I get a lot of Javascript helper functions too. I'm wondering if there's a clean way to just output my Mongoose schema properties.
My Jade display code looks like:
h4 Legacy data
ul
- each val, key in d.old
li= key + ": " + val
And my Mongoose schema definition is
Entry = new Schema({
old : {
submitter : String,
table : String,
wordid : Number
}
});
But when the page is rendered, there are a bunch of other Javascript properties and functions that get outputted at the same time. e.g.
_scope: [object Object]
toObject: function () { return this.get(path); }
wordid: 2035
...
Is there an easy way to iterate just through the properties from my schema?
I could use a specified list but I was wondering if there was a nicer way.
Actually, how would I write the specified way? In ruby I know I could do [ 'wordid', 'submitter' ].each but is there an equivalent in Jade?

You're encountering the object's prototype properties. You can filter them out with .hasOwnProperty
- each val, key in d.old
- if(d.old.hasOwnProperty(key))
li= key + ": " + val

Remember that you can also use the method toJSON on the document (mongoose doc of Document#toJSON) to get a clean JSON object that can be used in your templates (without worrying about mongoose document's internals and methods). In fact, the toObject method you mentioned is similar to toJSON, you might wanna check it out.
For example:
doc = EntryModel({old: {submitter: "s", table: "tableS", wordid: "666"}})
console.log(b.toJSON())
// outputs:
{
"_id": "51fea037434b242816000002",
"old": {
"submitter": "s",
"table": "tableS",
"wordid": 666
}
}
// Is a plain JSON object without any other property or method

Related

DynamoDB update inside an array of objects (nodejs)

I noticed that DynamoDB can add and remove items from an array but how do you search for an specific item inside an object if you want to update that one specifically?
For example:
In MongoDB you can search for someitem.$.subitem and update that specific item.
Is there a way on how to do this with DynamoDB?
Item: {
someitem: [
{
subitem: "id",
somevalue: "something"
}
]
}
I would say this is basic functionality but seems not easy to find (or even unsupported)
AWS does not permit to modify it in a single update request more info was found in the following answers:
updating-a-json-array-in-aws-dynamodb.
The solution that they propose is to change the schema from array to {}, or to implement a custom functions and iterate through each array and find your new id to update, so to speak to programatically update your json and then insert whole object.
TableName : 'tablename',
Key : { id: id},
ReturnValues : 'ALL_NEW',
UpdateExpression : 'set someitem['+`index`+'].somevalue = :reply_content',
ExpressionAttributeValues : { ':reply_content' : updateddata }
array element edit via array index

Replace key-value pair in an object with Google Apps Script

I am making a call to an API for a commercial product using Apps Script. Unfortunately, the resulting object has several key-value pairs that contain the id from a linked table.
I can easily get the standard values and have written code to find the related name value and add it to the object. I would prefer to add the name in the same location as the original id. But, when I add a new key, it is added to the end of the object.
I want the name in the same location as id so when I insert it into a sheet, the columns will still be in order.
This is my object:
var json = {id: 4730183,name: "A A", customer_source_id:123, company: "NE Company"};
This is my desired object after replacing the id with the name:
var json = {id: 4730183,name: "A A", source:"CRM", company: "NE Company"};
Basically, I want to find customer_source_id in the object and replace it with source.
I can't use indexOf and splice because the object is not an array.
What is the best way to do this? Do I have to convert it to an array first and then back again?
A quick answer would be:
var obj = {id: 4730183,name: "A A", customer_source_id:123, company: "NE Company"};
var json = JSON.stringify(obj);
json = json.replace("customer_source_id","source")
The better answer is:
#Waqar Ahmed is correct. JavaScript objects are unordered. In your example "var json" is an object not JSON. You can make it JSON with JSON.stringify(json). But once the JSON is parsed into an object it again becomes unordered. You should not use it to store ordered data.
I am not sure if it is efficient, but you can iterate through the keys and build a new json object like this:
var newjson = {};
for(var key in json){
if(key === 'customer_source_id'){
newjson.source = [NEW VALUE TO DISPLAY];
}else{
newjson[key] = json[key];
}
}
json = newjson;
But, like #Waqar and #Spencer said, the object is not used for ordered data.
You can do his only in java script array. Not in JSON. JSON is meant to be addressed by keys, not by index.Change your json to
var json ={id: 4730183,name: "A A", customer_source_id:null, items : [] company: "ESI"};
Now you can insert items using push method of array.
json.items.push('My Item');

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.

Mongoose: what's up with "_doc"?

It seems Mongoose is doing something really funky internally.
var Foo = new mongoose.model('Foo', new mongoose.Schema({a: String, b: Number}));
var foo = new Foo({a: 'test'; b: 42});
var obj = {c: 1};
foo.goo = obj; // simple object assignment. obj should be
// passed by reference to foo.goo. recall goo
// is not defined in the Foo model schema
console.log(foo.goo === obj); // comparison directly after the assignment
// => false, doesn't behave like normal JS object
Essentially, any time you try to deal with properties of a Mongoose model that aren't
a) defined in the model's schema or
b) defined as the same type (array, obj, ..) ... the model doesn't even behave like a normal Javascript object.
Switching line 4 to foo._doc.goo = obj makes the console output true.
edit: trying to reproduce weirdness
example 1:
// Customer has a property 'name', but no property 'text'
// I do this because I need to transform my data slightly before sending it
// to client.
models.Customer.find({}, function(err, data) {
for (var i=0, len=data.length; i<len; ++i) {
data[i] = data[i]._doc; // if I don't do this, returned data
// has no 'text' property
data[i].text = data[i].name;
}
res.json({success: err, response:data});
});
_doc exist on the mongoose object.
Because mongooseModel.findOne returns the model itself, the model has structure (protected fields).
When you try to print the object with console.log it gives you only the data from the database, because console.log will print the object public fields.
If you try something like JSON.stringify then you get to see inside the mongoose model object. (_doc, state ...)
In the case where you want to add more fields in the object and it's not working
const car = model.findOne({_id:'1'})
car.someNewProp = true // this will not work
If later you set the property to the object car and you didn't specify in the Model Schema before then Mongoose model is validating if this field exists and if it's the valid type.
If the validation fails then the property will not be set.
Update
Maybe I misunderstood your original question, but now it looks like the nature of your question changed, so the below information isn't relevant, but I'm leaving it. :)
I tested your code and it works fine for me. Mongoose doesn't execute any special code when you set properties that aren't part of the schema (or a few other special properties). JavaScript currently doesn't support calling code for properties that don't yet exist (so Mongoose can't get in the way of the set of the goo property for example).
So, when you set the property:
foo.goo = { c: 1 };
Mongoose isn't involved. If your console.log was something other than the code you displayed, I could see that it might report incorrectly.
Additionally, when you send the results back as JSON, JSON.stringify is being called, which calls toString on your Mongoose Model. When that happens, Mongoose only uses the properties defined on the schema. So, no additional properties are being sent back by default. You've changed the nature of the data array though to directly point at the Mongoose data, so it avoids that problem.
Details about normal behavior
When you set the property goo using Mongoose, quite a few things happen. Mongoose creates property getters/setters via the Object.defineProperty (some docs). So, when you set the goo property, which you've defined as a [String], a few things happen:
Mongoose code is called prior to the value being set onto the object instance (unlike a simple JavaScript object)
Mongoose creates an array (optionally) to store the data (a MongooseArray) which will contain the array data. In the example you provided, since you didn't pass an array, it will be created.
Mongoose will attempt to cast your data to the right type
It will call toString on the data passed as part of the cast.
So, the results are that the document now contains an array with a toString version of the object you passed.
If you checked the contents of the goo property, you'd see that it's now an array with a single element, which is a string that contains [object Object]. If you'd picked a more basic type or matched the destination property storage type, you would see that a basic equality check would have worked.
you can use toJSON() instead of _doc
Try using lean
By default, Mongoose queries return an instance of the Mongoose Document class. Documents are much heavier than vanilla JavaScript objects, because they have a lot of internal state for change tracking. Enabling the lean option tells Mongoose to skip instantiating a full Mongoose document and just give you the POJO.
https://mongoosejs.com/docs/tutorials/lean.html
Had same problem. Instead of updating my model.
const car = model.findOne({_id:'1'})
let temp = JSON.stringify(car);
let objCar = JSON.parse(temp);
objCar.color = 'Red'; //now add any property you want
this solves my problem
I was stuck on this today... Drove me nuts. Not sure if the below is a good solution (and OP has mentioned it too), but this is how I overcame this issue.
My car object:
cars = [{"make" : "Toyota"}, {"make" : "Kia"}];
Action:
console.log("1. Cars before the color: " + car);
cars.forEach(function(car){
car.colour = "Black"; //color is NOT defined in the model.
});
console.log("2. Cars after the color: " + car);
Problematic console output:
1. Cars before the color: [{"make" : "Toyota"}, {"make" : "Kia"}];
2. Cars after the color: [{"make" : "Toyota"}, {"make" : "Kia"}]; //No change! No new colour properties :(
If you try to pass in this property that was undefined in the model, via doc (e.g. car._doc.color = "black"), it will work (this colour property will be assigned to each car), but you can't seem to access it via EJS (frontend) for some reason.
Solution:
(Again, not sure if this is the best way... but it worked for me): Add in this new property (colour) in the car model.
var carSchema = mongoose.Schema({
make: String,
color: String //New property.
})
With the model redefined, everything worked as normal / expected (no _doc 'hacks' needed etc.) and I lived another day; hope it helps someone else.
There is some weirdness with Mongoose models and you have to check that Mongoose doesn't already have a model created in it's models array.
Here is my solution:
import mongoose from 'mongoose';
createModel = (modelName="foo", schemaDef, schemaOptions = {})=> {
const { Schema } = mongoose;
const schema = Schema(schemaDef, schemaOptions);
const Model = mongoose.models[modelName] || mongoose.model(modelName, schema);
return Model;
}
I use my own mongoose model class and base class for my models. I made this and it should work for you.
For those using spread(...) and/ can't see a solution, here's an example of #entesar's answer
Instead of spread or ._doc in:
import User from "./models/user";
...
async function createUser(req, res) {
const user = await User.create(req.body);
res.status(201).json({
message: "user created",
data: {
...user // OR user._doc,
token: "xxxxxxxx",
},
});
}
...
Use this
import User from "./models/user";
...
async function createUser(req, res) {
const user = await User.create(req.body);
res.status(201).json({
message: "user created",
data: {
...user.toJSON(),
token: "xxxxxxxx",
},
});
}
...
Ps: took me a while to understand the answer.
You should add .lean() on the find to have it skip all the Model "magic".

Access mongoose non-schema values in Jade

I have a really weird problem in Jade where I cannot access the values that aren't defined in the Schema.
I'm using strict:false on my schema and saving values to it. My data looks like this:
{
"title" : "This is a title in the schema",
"notInSchema" : "This will never show up"
}
This works:
h1= post.title
This doesn't work:
h1= post.notInSchema
If I dump all my data into the template, I can see both pieces of data:
pre= JSON.stringify(options,null,'\t') //{"title" : "This is a title in the schema", "notInSchema" : "This will never show up"}
If I add notInSchema to my schema, it shows up. How can I do this without adding it?
Instead of passing the raw Mongoose document to Jade, pass its serialized version instead:
res.render('yourtemplate', {
post : post.toJSON() // .toJSON() is also called by JSON.stringify()
});
I believe Mongoose only creates accessors on a document for fields that are in the schema. Any other fields, even though they are stored in the database, don't get one so can't be accessed directly.
The documentation seems to suggest something similar:
NOTE: Any key/val set on the instance that does not exist in your
schema is always ignored, regardless of schema option.
EDIT: since you're dealing with result sets, you need to call toJSON on each document in it. The easiest way to do so is using map (hope I get the CF syntax right):
res.render "admin",
title : "Admin Dashboard"
results : results
users : results.users.map (user) ->
user.toJSON()
messages: req.flash() || {}
Although that would still leave results 'unprocessed'. Alternatively, you could leave the mapping to the separate steps in your async.series. For instance:
Company
.find()
.exec (err,companies)->
next(null,companies.map (company) ->
company.toJSON()
)
Or use toJSON in your template on any object that you need to access those "unschema'd" properties for.
I use:
model.find({Branch:branch},function (err, docs){
if (err) res.send(err)
res.render('index',
{tree: tree,
articulos: JSON.parse(JSON.stringify(docs))
})})
});

Resources