How to use DataLoader with Mongoose - javascript

I'm trying to build the following use case of DataLoader together with Mongoose:
export const PurchaseOrderType = new GraphQLObjectType({
name: "PurchaseOrder",
description: "PurchaseOrder",
interfaces: () => [NodeInterface],
isTypeOf: value => value instanceof PurchaseOrderModel,
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLID),
resolve: obj => dbIdToNodeId(obj._id, "PurchaseOrder")
},
name: {
type: new GraphQLNonNull(GraphQLString)
},
customer: {
type: CustomerType,
resolve: (source, args, context) => {
return context.customerLoader.load(source.customer_id);
}
}
})
});
export default () => {
return graphqlHTTP((req, res, graphQLParams) => {
return {
schema: schema,
graphiql: true,
pretty: true,
context: {
customerLoader: customerGetByIdsLoader()
},
formatError: error => ({
message: error.message,
locations: error.locations,
stack: error.stack,
path: error.path
})
};
});
};
export const customerGetByIdsLoader = () =>
new DataLoader(ids => {
return customerGetByIds(ids);
});
export const customerGetByIds = async ids => {
let result = await Customer.find({ _id: { $in: ids }, deletedAt: null }).exec();
let rows = ids.map(id => {
let found = result.find(item => {
return item.id.equals(id);
});
return found ? found : null; << === found always undefined
});
return rows;
};
I'm facing the following problems when loading several PurchaseOrders:
A single customer_id is being called more than once in the ids parameter of the DataLoader. So an example id 5cee853eae92f6021f297f45 is being called on several requests to my loader, in successive calls. That suggests that the cache is not working properly.
My found variable when processing the read result is always being set to false, even comparing the right ids.

You can use findOne
export const customerGetByIds = async ids => {
let result = await Customer.find({ _id: { $in: ids }, deletedAt: null }).exec();
const rows = []
let promiseAll = ids.map(async (id) => {
let found = result.filter(item => item.id.toString() === id.toSring());
if(found) {
rows.push(found[0])
return found[0]
}
return null;
});
await Promise.all(promiseAll);
return rows;
};

Related

recursive function to retrieve nested category in nodejs and mongodb

I have category with below schema for tree structure data:
const mongoose = require("mongoose")
const CategorySchema = new mongoose.Schema(
{
name: {type:String, required: true},
parent_id: { type: mongoose.Schema.Types.ObjectId, default: null},
slug: { type: String, required: true, unique: true}
}
);
module.exports = mongoose.model("Category", CategorySchema)
Router api call to get all category:
//GET ALL Category
router.get("/", async (req,res) => {
try {
const categories = await Category.find({});
if (!categories)
{
return [];
}
return nestedCategories(categories);
} catch (err) {
console.log(err);
}
})
Below is the recursive function:
const nestedCategories = (categories, parentId = null) => {
const categoryList = [];
let category;
if (parentId == null) {
category = categories.filter(cat => cat.parent_id == null);
} else {
category = categories.filter(cat => String(cat.parent_id) == String(parentId));
}
for (let cate of category) {
categoryList.push({
_id: cate._id,
name: cate.name,
slug: cate.slug,
children: nestedCategories(categories, cate._id)
})
}
return categoryList;
}
module.exports = { nestedCategories }
Using postman, I have requested a api call to get router function, it causes infinite loop. Do I need to add break point in recursive function?

Sequelize - Foreign keys are always null

Description
I'm starting to learn how to use Sequelize and I've run into an issue with foreign key relationships for one of my tables. I have three tables: Users, Projects, Times. Users and Projects have a Many to One relationship with Times.
Time.belongsTo(User)
Time.belongsTo(Project)
User.hasMany(Time)
Project.hasMany(Time)
After I create an entry in the Times table and then fetch it the resulting record always has Nulls for the two foreign keys.
db.Time.findAll().then(result => {
res.status(200).send(JSON.stringify(result))
})
Model
const {Sequelize, DataTypes} = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const User = sequelize.define('User', {
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false
}
});
const Project = sequelize.define('Project', {
name: {
type: DataTypes.STRING,
allowNull: false
}
});
const Time = sequelize.define('Time', {
description: {
type: DataTypes.STRING,
allowNull: false
},
start: {
type: DataTypes.STRING,
allowNull: false
},
stop: {
type: DataTypes.STRING,
allowNull: false
}
});
console.log('Setting up Models')
Time.belongsTo(User)
Time.belongsTo(Project)
User.hasMany(Time)
Project.hasMany(Time)
const db = {
"sequelize": sequelize,
"User": User,
"Project": Project,
"Time": Time
}
exports.User = User;
exports.Project = Project;
exports.Time = Time;
exports.db = db;
Populating with Default Data
const {data} = require('./data')
const {db} = require('../data/models')
const dbHelper = {
connectDB: () => {
return new Promise((resolve, reject) => {
db.sequelize.authenticate().then(() => {
resolve(db)
}).catch((e) => {
reject(e);
})
})
},
hydrateDB: (db) => {
return new Promise((resolve, reject) => {
try {
db.sequelize.sync().then(() => {
hydrateUser(db).then(() => {
hydrateProject(db).then(() => {
hydrateTime(db).then(() => {
resolve()
})
})
})
})
} catch (e) {
reject(e)
}
})
}
}
const hydrateUser = (db) => {
return new Promise(resolve => {
data.user.forEach((datum) => {
db.User.create(datum).then(() => {
resolve()
})
})
})
}
const hydrateProject = (db) => {
return new Promise(resolve => {
data.project.forEach((datum) => {
db.Project.create(datum).then(() => {
resolve()
})
})
})
}
const hydrateTime = (db) => {
return new Promise(resolve => {
data.time.forEach((datum) => {
db.Time.create({
description: datum.description,
start: datum.start,
stop: datum.stop,
userId: 1,
projectId: 1
}).then(() => {
resolve()
})
})
})
}
exports.dbHelper = dbHelper;
You indicated fields in camel case here:
userId: 1,
projectId: 1
And actual fields created for Time are in pascal case as you showed in the screenshot.
So just correct names:
db.Time.create({
description: datum.description,
start: datum.start,
stop: datum.stop,
UserId: 1,
ProjectId: 1
}).then(() => {
resolve()
})

Access array values after knex SELECT

I have the following code snippet in an Electron app to retrieve a given column in a sqlite3 database:
database.js
var database = require("knex")({
client: "sqlite3",
connection: {
filename: path.join(__dirname, 'db/food.db')
},
useNullAsDefault: true
});
const fetchItems = (colName) => {
let itemList = database.select(colName).from('food')
itemList.then(() => {
// console.log(itemList);
return itemList;
}).catch((err) => {
console.log(err)
return [];
})
}
When I try accessing the array itemList, I got a message undefined. When I try printing it out to console, it shows lengthy info of the database object as following:
Builder [object] {
client: Client_SQLite3 {
config: { client: 'sqlite3', connection: [Object], useNullAsDefault: true },
logger: Logger {
_inspectionDepth: 5,
_enableColors: true,
_debug: undefined,
_warn: undefined,
_error: undefined,
_deprecate: undefined
},
connectionSettings: {
filename: '/home/.../db/food.db'
},
connectionConfigExpirationChecker: null,
driver: {
Database: [Function: Database],
Statement: [Function: Statement],
Backup: [Function: Backup],
OPEN_READONLY: 1,
...
How do I go about accessing the array (i.e. column) of values? Thanks.
Update: here is how I call this fetchItems function in main.js.
const database = require('./database');
ipcMain.on("showItemsButtonClicked", function () {
let itemList = database.fetchItems('food_name');
itemList.then(() => {
mainWindow.webContents.send("itemsRetrieved", itemList);
})
console.log(itemList);
});
I think you are using the wrong syntax.
const fetchItems = (colName) => {
let itemList = database.select(colName).from('food').then((res) => {
// console.log(res);
return res;
}).catch((err) => {
console.log(err)
return [];
})
return itemList
}
Using async await:
const fetchItems = async(colName) => {
try {
let itemList = await database.select(colName).from('food')
return itemList
} catch(err) {
console.log(err)
return [];
}
}

MongoDB inserting data only if it don't exist (in Javascript)

I'm creating a web scraper and I need to store my data in MongoDB. I scrape some data and store it in a variable call item. I also don't to add a data to the database if it already exist. so here is my resultAnalysis.mjs file that is supposed to do that. What should I change in this code, because it don't insert data and also throw error :
TypeError: Cannot destructure property 'lotNumber' of 'item' as it is undefined.
at file:///Users/AlainMolleyres/Desktop/scraper_V1/resultsAnalysis.mjs:48:11
at runMicrotasks (<anonymous>)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
Here is my code
resultsAnalysis.mjs
mongoose
.connect(mongoURI, { useNewUrlParser: true })
.then(() => console.log("MongoDB connected"))
.catch((err) => console.error(err));
export const compareAndSaveResults = (item) => {
try {
const Schema = mongoose.Schema;
const lotSchema = new Schema({
lotNumber: {},
artistName: {},
artworkNameOriginal: {},
pictureUrl: {},
artworkDate: {},
signed: {},
titled: {},
technic: {},
literature: {},
provenance: {},
dimension: {},
lowEstimation: {},
highEstimation: {},
currency: {},
soldPrice: {},
lotUrl: {},
});
const lot = mongoose.model("lot", lotSchema);
lot
.find({}, function (err, lotList) {
return lotList;
})
.then((lotList) => {
if (lotList == "") {
console.log(`A new data was created:\n${JSON.stringify(item)}`);
const newLot = new lot(item);
return newLot.save().catch((err) => console.log(err));
}
const {
lotNumber,
artistName,
artworkNameOriginal,
pictureUrl,
artworkDate,
signed,
titled,
technic,
literature,
provenance,
lowEstimation,
highEstimation,
currency,
soldPrice,
dimensionInCm,
lotUrl,
} = item;
const dbId = lotList[0]._id;
const dbArtworkNameOriginal = lotList[0].artworkNameOriginal;
const dbLotUrl = newsList[0].lotUrl;
let catchDifference = false;
if (dbArtworkNameOriginal !== artworkNameOriginal) {
catchDifference = true;
} else {
dbLotUrl.forEach((elem, i) => {
if (elem !== lotUrl[i]) catchDifference = true;
});
}
if (catchDifference) {
console.log("A new evidence was found, updating database...");
mongoose.set("useFindAndModify", false);
return News.findOneAndUpdate({ _id: dbId }, item);
}
console.log("File is equal to page, no lot to report");
})
.then(() => {
mongoose.disconnect();
})
.catch((err) => console.log(err));
} catch (err) {
console.error(err);
}
};
``

eslint arrow-body-style issue

I configured my eslint so it uses the arrow-body-style as needed:
arrow-body-style: ["error", "as-needed"]
But I'm getting an error below for some reason.
router.get('/', (req, res, next) => {
Product.find({})
.select('name price _id')
.then(items => {
const response = {
count: items.length,
products: items.map(item => { // eslint points the error here
return {
name: item.name,
price: item.price,
_id: item._id,
request: {
type: 'GET',
url: `http://localhost:3000/products/${item._id}`
}
};
})
};
res.status(200).json(response);
})
.catch(error => {
console.log(error);
res.status(500).json({ message: 'Server error' });
});
});
How exactly am I supposed to re-write my code?
Using arrow-body-style: ["error", "as-needed"] configuration is redundant as it is the default behaviour. You don't need setting it again as it is already set as the default form.
as-needed
Examples of incorrect code for this rule with the default "as-needed" option:
/*eslint arrow-body-style: ["error", "as-needed"]*/
/*eslint-env es6*/
let foo = () => {
return 0;
};
let foo = () => {
return {
bar: {
foo: 1,
bar: 2,
}
};
};
Examples of correct code for this rule with the default "as-needed" option:
/*eslint arrow-body-style: ["error", "as-needed"]*/
/*eslint-env es6*/
let foo = () => 0;
let foo = (retv, name) => {
retv[name] = true;
return retv;
};
let foo = () => ({
bar: {
foo: 1,
bar: 2,
}
});
let foo = () => { bar(); };
let foo = () => {};
let foo = () => { /* do nothing */ };
let foo = () => {
// do nothing.
};
let foo = () => ({ bar: 0 });
ESLint Docs on arrow-body
In your code sample it should be this way:
router.get('/', (req, res, next) => {
Product.find({})
.select('name price _id')
.then(items => {
const response = {
count: items.length,
products: items.map(item => ({ // no more errors
name: item.name,
price: item.price,
_id: item._id,
request: {
type: 'GET',
url: `http://localhost:3000/products/${item._id}`
});
})
};
res.status(200).json(response);
})
.catch(error => {
console.log(error);
res.status(500).json({ message: 'Server error' });
});
});
Since you're simply returning a plain object, there's no need for the extra pair of braces and return. Wrapping the object in parentheses ({ ... }), works as that is implicitly returned.
Try to omit return keyword and wrap the result into parenthesis:
products: items.map(item => ({
name: item.name,
price: item.price,
_id: item._id,
request: {
type: 'GET',
url: `http://localhost:3000/products/${item._id}`
}
}))

Categories