Populate with inherited document in Mongoose - node.js

I am trying to create a database schema for the following model:
I am not sure what the better way to represent this in a MongoDb would be, but since I am using Mongoose and there is a plugin for inheritance, I am trying the following:
var mongoose = require('mongoose')
, extend = require('mongoose-schema-extend')
, Schema = mongoose.Schema
, ObjectId = mongoose.Schema.Types.ObjectId
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function callback () {
//Mashup and Item are bound (itemSchema is a sub-doc)
var itemSchema = new Schema({
pos: { top: Number, left: Number }
, size: { width: Number, height: Number }
, component: { type: ObjectId, ref: 'Component' }
})
var mashupSchema = new Schema({
name: String
, desc: String
, size: { width: Number, height: Number }
, items: [itemSchema]
})
var componentSchema = new Schema({
name: String
, desc: String
}, { discriminatorKey : '_type' })
var imageComponentSchema = componentSchema.extend({
url: String
})
var textComponentSchema = componentSchema.extend({
text: String
})
var htmlComponentSchema = componentSchema.extend({
html: String
})
var webComponentSchema = componentSchema.extend({
page: { type: ObjectId, ref: 'Page' }
, selector: { type: ObjectId, ref: 'Selector' }
})
var pageSchema = new Schema({
name: String
, desc: String
, url: String
, active: { type: Boolean, default: false }
, webComponents: [{ type: ObjectId, ref: 'WebComponent' }]
})
var selectorSchema = new Schema({
desc: String
, url: String
, cssPath: String
})
///MODELS
var Mashup = db.model("Mashup", mashupSchema)
var Component = db.model("Component", componentSchema)
var ImageComponent = db.model("ImageComponent", imageComponentSchema)
var TextComponent = db.model("TextComponent", textComponentSchema)
var HtmlComponent = db.model("HtmlComponent", htmlComponentSchema)
var WebComponent = db.model("WebComponent", webComponentSchema)
var Page = db.model("Page", pageSchema)
var Selector = db.model("Selector", selectorSchema)
//CREATE
//a new empty mashup
//var aMashup = new Mashup({ name: "Test" });
Mashup.create({ name: "Test" }, function (err, mashup) {
if (err) return
console.log("Saved: empty mashup")
//mashup saved, create a webComponent
var aWebComponent = new WebComponent({ name: "Map", desc: "A map" })
//create a page
var aPage = new Page({ name: "Maps", desc: "Google Maps", url: "http://maps.google.com" })
aPage.webComponents.push(aWebComponent)
aWebComponent.page = aPage
//create a selector
var aSelector = new Selector({desc: "Just the map", url: "maps.google.com", cssPath: "#map" })
aWebComponent.selector = aSelector
//save the component
aWebComponent.save(function(err) {
if (err) return
console.log("Saved: WebComponent")
aPage.save(function(err) {
if (err) return
console.log("Saved: the Page")
aSelector.save(function(err) {
if (err) return
console.log("Saved: the Selector")
//finally add the item with the new component
var item = { pos: { top:6, left:10 }, size: { width:100, height:100}, component: aWebComponent }
mashup.items.push(item)
mashup.save(function (err) {
if (err) return
console.log("Saved: mashup with item (WebComponent with Page and Selector)")
//POPULATE
Mashup
.find({})
.populate("items.component")
.exec(function (err, mashup) {
if (err) console.log(err)
console.log(mashup);
})
})
})
})
})
})
});
This is a use case scenario, where a user creates a Mashup and then adds a new Item to it by creating a new WebComponent. I need that Item class because each different mashup should be able to have "instances" (i.e. the Items) of existing Components.
Now, I am new to Mongoose and I am sure things could be done differently. Any suggestion here is welcome. However, when I try to query the Mashups populating the results, the output I get is:
Saved: empty mashup
Saved: WebComponent
Saved: the Page
Saved: the Selector
Saved: mashup with item (WebComponent with Page and Selector)
[ { __v: 1,
_id: 520a8aae3c1052f723000002,
name: 'Test',
items:
[ { component: null,
_id: 520a8aaf3c1052f723000006,
size: [Object],
pos: [Object] } ],
size: {} } ]
component should be populated but it is not. I guess this is because it expects a Componentwhile it gets a WebComponent. How do I fix this? Should I stop trying with inheritance? What other ways are there to create a DB schema for this model?

Doh.. changing
var componentSchema = new Schema({
name: String
, desc: String
}, { discriminatorKey : '_type' })
to
var componentSchema = new Schema({
name: String
, desc: String
}, { collection : 'components', discriminatorKey : '_type' })
Fixes the issue. Not sure why.

Related

Mongoose post save hook, modifiedPaths() is always empty

Our aim is to have a post hook in place where we can track changed fields.
Model file:
const VariationSchema = new Schema({
_id: {
type: String,
},
title: {
type: String,
},
desc: {
type: String,
},
});
VariationSchema.post('save', async function (doc) {
console.log(doc.modifiedPaths());
});
const VariationModel = mongoose.model('variation', VariationSchema);
module.exports = {
VariationModel,
VariationSchema,
};
Service file:
const variationDocument = await VariationModel.findById(variationId).select({});
variationDocument.desc = (Math.random() + 1).toString(36).substring(7);
await variationDocument.save();
return variationDocument.toJSON();
No matter what we do, doc.modifiedPaths() is always empty. Please help

Function not returning anything in nodejs

I am creating a web app's backend , where different posts are stored and their categories are also stored. Since each category has its own properties (like description , color etc. ) , I've created a new category schema and storing the category reference in the article.
Here's my post route:
Code_1
Code_2
// CREATE ROUTE : adding New Article
app.post("/addnew", upload.single('image'), function(req,res){
//get data from form and add to news array
var title = req.body.title;
var image = req.body.image
if (req.file){
var imgURL = req.file.path;
}
var description = req.body.description;
var category = req.body.category;
var tag = req.body.tag;
// Handling the category entered by user
function checkCategory(name , color, totalArticles, desc){Category.find({name: category}, (err, foundCategory) => {
if(err){
console.log(err)
} else {
console.log("found category in starting of checkCategory function : " , foundCategory)
if (foundCategory[0]){
console.log("Category" + foundCategory + "already exists...")
return foundCategory
} else {
// var name = req.body.name
// var color = req.body.color
// var totalArticles = req.body.totalArticles
// var desc = req.body.desc
var category = {name: name , color : color , totalArticles: totalArticles || 0 , desc : desc }
Category.create(category, (err, newCategory) => {
if (err){
console.log(err)
} else {
console.log("New category Created : " , newCategory)
// category = newCategory
return newCategory
}
})
}
}
})
}
console.log("??????????????? category returned", category)
var nyaArticle= {title: title, imgURL: imgURL, description: description};
// create a new Article and save to db
Article.create(nyaArticle,function(err,newArticle){
if(err){
console.log(err);
} else {
// redirect back to main page
console.log("new article created")
console.log(newArticle)
category = checkCategory(req.body.name, req.body.color, req.body.totalArticles, req.body.desc)
console.log("checkCategory Returned :::::" , category)
newArticle.category.push(category)
newArticle.save()
res.redirect("/");
}
})
The function checkCategory checks if the category already exists or else it will create a new one .
But according to these logs , my function is not returning the category created , however the category is successfully created in DB and also can be seen in Logs
Articles App has started on port 3000
DB Connected...: cluster0-shard-00-00-ktzf1.mongodb.net
??????????????? category returned undefined
new article created
{
category: [],
hits: 0,
tag: [],
comments: [],
_id: 60be0fe92a8b88a8fcea71dc,
title: 'TESTING',
description: 'TESTING',
created: 2021-06-07T12:24:09.563Z,
__v: 0
}
checkCategory Returned ::::: undefined
found category in starting of checkCategory function : []
New category Created : {
totalArticles: 444,
_id: 60be0fea2a8b88a8fcea71dd,
name: 'TESTING',
color: 'RED ALERT',
desc: 'THiS TESTING',
__v: 0
}
due to this null is getting stored in my category in
DB
Am I using the right approach or should I follow some other approach, any help is much welcomed.
The categorySchema looks like this :
var categorySchema = new mongoose.Schema({ name: String, color: String, totalArticles: { type:Number, default: 0 }, desc : String });
ArticleSchema:
var newSchema = new mongoose.Schema({
title: String,
imgURL: String, //{type: String, default: "https://source.unsplash.com/1600x1080/?news"},
description: String,
// category: String,
category: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Category"
}
],
hits : {
type: Number ,
default : 0
},
tag: [
{type: String}
],
created: {type: Date, default: Date.now},
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment"
}
]
});
You are assigning var category inside the callback function returned from your checkCategory, and that var is only available inside that callback.
Besides, you code have several other problems, like Category.find({name: category}) which will never return anything. (it should be {name:name}).
In general, you'll be much better off using async\await, (coupled with a try\catch, if you like):
async function checkCategory(name, color, totalArticles, desc) {
try {
let category = await Category.findOne({ name });
if (category) {
console.log(`Category ${category.name} already exists...`);
return category;
}
else {
let newCategory = await Category.create({ name: name, color: color, totalArticles: totalArticles || 0, desc: desc });
console.log("New category Created : ", newCategory);
return newCategory;
}
} catch (error) {
console.log(err)
}
}
And in your router function:
app.post("/addnew", upload.single('image'), async function(req,res){
let {name, color, totalArticles, desc} = req.body;
let category = await checkCategory(name, color, totalArticles, desc);
let newArticle = await Article.create({ title: title, imgURL: imgURL, description: description, category: [category] });
res.redirect("/");
}

POST array of objects using req.body parser

I am trying to post a simple request which includes array of objects. I have created a model and passing the data as per the model.
I am having trouble accessing body parameters as it contains array of data.
I am able to store line item data by req.body.tasks[0]
which is not a standrad way of storing details in mongodb.
I am looking for a standrad way of storing array of data in mongodb
Controller:
let createBug = (req, res) => {
console.log(req.body.tasks[0].subtask[0].description)
for (var key in req.body) {
if (req.body.hasOwnProperty(key)) {
item = req.body[key];
console.log(item);
}
}
const createBug = new listModel({
title: req.body.title,
tasks: [{
title: req.body.tasks[0].title,
description: req.body.tasks[0].description,
subtask: [{
description: req.body.tasks[0].subtask[0].description
}]
}]
}).save((error, data) => {
if (data) {
let apiResponse = response.generate(false, null, 201, data);
res.status(201).send(apiResponse);
} else {
let apiResponse = response.generate(true, error, 404, null);
res.status(404).send(apiResponse);
}
});
};
body:
{
"title":"sample title",
"tasks":[{
"title": "task 1",
"description":"task1 description",
"subtask":[{
"description":"task3 description"
}]
}]
}
Model:
const mongoose = require("mongoose");
const mySchema = mongoose.Schema;
let subtask = new mySchema({
description: String
})
let taskdata = new mySchema({
title: String,
description: String,
subtask: [subtask]
});
let listSchema = new mySchema({
title: {
type: String,
require: true,
},
tasks: [taskdata],
owner: {
type: mongoose.Schema.Types.ObjectId,
ref: "users",
}
});
module.exports = mongoose.model("list", listSchema);
I think you're overcomplicating things here a little bit. The request body exactly matches the model definitions, so you can simply pass the req.body object to your mongoose model:
const createBug = new listModel(req.body).save((error, data) => { ... }

GraphQL Resolver for Interface on same Mongoose collection

I'm creating a GraphQL server that uses Mongoose and GraphQLInterfaceType. I have a GraphQLInterfaceType of Books and sub types of SchoolBooksType and ColoringBookType. in my Mongoose Schema I specified that both SchoolBooks and ColoringBooks are to be stored in the same books collection
const coloringSchema = new Schema({
title: String,//Interface
pages: String
});
module.exports = mongoose.model("ColoringBook", coloringSchema , "books");
const schoolSchema = new Schema({
title: String, //Interface
subject: String
});
module.exports = mongoose.model("SchoolBook", schoolSchema , "books");
Here is one of my types
const SchoolBookType = new GraphQLObjectType({
name: "SchoolBook",
interfaces: [BooksInterface],
isTypeOf: obj => obj instanceof SchoolBook,
fields: () => ({
title: { type: GraphQLString },
subject: { type: GraphQLString }
})
});
Here is my query: But I don't know what to return, if I need to combine the two collections into the same array?
books: {
type: new GraphQLList(BooksInterface),
resolve() {
return SchoolBook.find({}) //<---- What to return?
}
}
Here is my query:
{
books{
title
... on ColoringBook{
pages
}
... on SchoolBook{
subject
}
}
}
Any help would be great, Thank you.
I guess you can use an async resolver, and concat both queries.
resolve: async () => {
const schoolBooks = SchoolBook.find({}).exec()
const coloringBooks = ColoringBook.find({}).exec()
const [sbooks, cbooks] = await Promise.all([schoolBooks, coloringBooks])
return [...sbooks, ...cbooks]
}

How to filter saved 'saved search' search on type?

I am attempting to load a list of saved searches where the type is 'customer'. I can load all the searches but in order for me to determine if the saved search is a customer saved search, I need to call search.load() on each result and then check the searchType. I am running into governance issues.
I get an error (Invalid search filter) with the filter below.
How do I filter the search for customer saved searches?
require(['N/search', 'N/record'],
function GetSavedSearches(search, record) {
var records = new Array();
var typeFilter = search.createFilter({
name: 'Type',
operator: search.Operator.ANYOF,
values: 'customer'
});
var mySearch = search.create({
type: search.Type.SAVED_SEARCH,
columns: [{
name: 'internalid'
}, {
name: 'ID'
}, {
name: 'Title'
}, {
name: 'Owner'
}, {
name: 'Access'
}],
filters: typeFilter
});
mySearch.run().each(processRecord);
function processRecord(result) {
var columns = result.columns();
var searchID = result.getValue({
name: 'ID'
});
try {
var loadedSearch = search.load({
id: searchID
});
var searchName = result.getValue({
name: 'Title'
});
var searchType = loadedSearch.searchType;
var searchAccess = loadedSearch.isPublic;
if (searchType == "customer") {
var record = {
SS_ID: result.id,
'Title': searchName,
'Type': searchType,
'Public': searchAccess
};
records.push(record);
}
}
catch (ex) {
// an error occurs when trying to load a search that is actually an update.
}
var x = 0;
return true;
}
});
You have to define the filter as this:
var typeFilter = search.createFilter({
name: 'recordtype',
operator: search.Operator.ANYOF,
values: 'Customer'
});

Resources