What I'm trying to do is to get rows in between two dates by using Sequelize ORM in Node.js. I'm using PostgreSQL. The problem is that the request that I'm making is interpreted incorrectly by Sequelize.
Here is the code that I'm using to make request
const dbresp = await Table.findAll({
attributes: [...],
where: {
...
createdAt: {
$between: [new Date(Date(startDate)), new Date(Date(endDate))],
// same effect
// $lte: new Date(startDate),
// $gte: new Date(endDate),
},
},
logging: console.log,
raw: true,
order: [['createdAt', 'ASC']],
// limit: count,
});
By logging raw SQL request it is obvious that request is incorrect
SELECT ...
FROM "table" AS "table"
WHERE "table"."createdAt" = '2019-02-05 21:00:00.000 +00:00'
"table"."createdAt" = '2019-02-05 21:00:00.000 +00:00'
ORDER BY "table"."createdAt" ASC;
What is a proper way to make such a request? Should I use a raw query?
I've googled this issue but no StackOverflow nither GitHub did help.
Ok, IDK what causes this issue but I fixed it by using Op object of Sequelize like this.
const Op = require('./models').Sequelize.Op;
const dbresp = await Table.findAll({
attributes: [...],
where: {
...
createdAt: {
[Op.between]: [startDate, endDate],
},
},
logging: console.log,
raw: true,
order: [['createdAt', 'ASC']],
// limit: count,
});
Seems like $between operator does not work
Try using the code shared below:
[Op.between]: [date1, date2]
Or the raw query:
SELECT ...
FROM table t
WHERE t.createdAt BETWEEN '2019-02-05' AND '2019-02-07'
ORDER BY t.createdAt ASC;
This should work.
Sequelize allows you to create aliases.
https://sequelize.org/v5/manual/querying.html
const Sequelize = require('sequelize');
const op = Sequelize.Op;
const operatorsAliases = {
$between: op.between, //create an alias for Op.between
}
const connection = new Sequelize(db, user, pass, { operatorsAliases })
const dbresp = await Table.findAll({
attributes: [...],
where: {
...
createdAt: {
$between: [new Date(Date(startDate)), new Date(Date(endDate))],
// same effect
// $lte: new Date(startDate),
// $gte: new Date(endDate),
},
},
logging: console.log,
raw: true,
order: [['createdAt', 'ASC']],
// limit: count,
});
Related
I have a company model which looks like this:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const CompanySchema = new Schema(
{
companyName: String,
settings: {
type: {
priceVisible: Boolean,
allowPickupAddressAddition: Boolean,
paymentMethodsAvailable: [
{ type: Schema.Types.ObjectId, ref: "PaymentMethod" },
],
},
},
}
);
const Company = mongoose.model("Company", CompanySchema);
module.exports = Company;
And I want to populate the values store in paymentMethodsAvailable array. Here is relevant controller code:
const company = await Company.findOne({ _id: id }).populate([
{
path: "settings",
populate: [{path: "paymentMethodsAvailable"}]
},
]);
But this doesn't work as expected. I can see that it might be trying to populate settings object, and fails there. Is there a way in which I can tell mongoose to populate settings.paymentMethodsAvailable ?
Try this
const company = await Company.findOne({ _id: id }).populate(
"settings.paymentMethodsAvailable"
);
You can find more examples in the documentation. I was using this section as a reference https://mongoosejs.com/docs/populate.html#populating-maps
Mongoose provides clear syntax
The following code will work fine
const company = await Company.findOne({ _id: id }).populate(
"settings.type.paymentMethodsAvailable"
);
if(!company) {
// if there is no company with that id
// this acully not error it's simply
// return `null` to tell you company not found.
res.status(404).send()
}
Also: you can go further and populate specific fields inside settings.paymentMethodsAvailable
const company = await Company.findOne({ _id: id }).populate(
"settings.type.paymentMethodsAvailable",
"field1 filed2 filed3"
);
I'm currently working on an API and I need the users to have at least 4 fields of number which will increase over time. Some of them might be 15000/hour, some of them 500/hour, some will be negative like -8000/hour. How should I handle such data storage using Mongoose? What kind of properties should these user model fields have? If you can provide me a simple Mongoose model for such data storage, I would be happy.
Should I have something in back-end just to increase (or decrease) these fields of users? Or does MongoDB/Mongoose have something to provide this feture? Also, how should I show these fields increasing on the web page? Should I always get the increased fields of user every few seconds? Or should I just use JavaScript on front-end?
Thanks in advance.
It would depending on what you would want to achieve with your data, at the end of the day.
To keep track of all changing logs, persisted in its respective array, then bucket pattern would best solve your problem [bucket pattern explanation][1]
[1]: https://www.mongodb.com/blog/post/building-with-patterns-the-bucket-pattern
, else just ordinary field manipulation should solve your problem.
Schema design for implementation of both, [bucket pattern] and [field manipulation] are shown as follows:
If you plan to keep track of all your changing logs, then bucket pattern is your best bet, your schema could look like this:
const mongoose = require('mongoose')
const { Schema } = mongoose
const numberLogSchema1 = new Schema({
field1: [
{type:Number}
],
field2: [
{type:Number}
],
field3: [
{type:Number}
],
field4: [
{type:Number}
]
})
module.exports = mongoose.model('numberLogs1',numberLogSchema1)
And its corresponding would look like this:
router.post('/numberLog', async (req, res) => {
try {
const saveNumberLog = await numberLog1.updateOne(
{ _id: numberCollectionIdhere },
{
$push: {
field1: req.body.fieldLogValue
}
})
res.json(saveNumberLog)
}catch (err) {
res.json({
message: err
})
}
})
Else if you just want to manipulate field values, at specific intervals from the frontend, using a javascript timer which could also be persisted to the database and fetched on a page Reload, your schema could look like this:
const mongoose = require('mongoose')
const { Schema } = mongoose
const numberLogSchema2 = new Schema({
field1: {
type: Number,
required: true,
default: 0
},
field2: {
type: Number,
required: true,
default: 0
},
field3: {
type: Number,
required: true,
default: 0
},
field4: {
type: Number,
required: true,
default: 0
}
})
module.exports = mongoose.model('numberLogs2',numberLogSchema2)
And its corresponding route paths, could look like this:
//if you intend to just increase the logvalue hourly
//without keeping track of it previous value then we use $inc
router.post('/numberLog2', async (req, res) => {
try {
const saveNumberLog2 = await numberLog2.updateOne(
{ _id: numberCollectionIdhere },
{
$inc: {
field1: req.body.fieldLogValue
}
})
res.json(saveNumberLog2)
}catch (err) {
res.json({
message: err
})
}
})
I want to write a virtual (get) method for my MongoDb collection (Parts) which needs to access a different schema: I want it to assert if a document is 'obsolete' according to a timestamp available in a different (Globals) collection:
const partsSchema = new Schema({
...
updatedAt: {
type: Date,
},
...
}, {
toObject: { virtuals: true },
toJSON: { virtuals: true },
});
partsSchema.virtual('obsolete').get(async function() {
const timestamp = await Globals.findOne({ key: 'obsolescenceTimestamp' }).exec();
return this.updatedAt < timestamp.value;
});
But when I do a find, I always get a {} in the obsolete field, and not a boolean value...
const p = await parts.find();
...
"obsolete": {},
...
Is there some way to accomplish my goal?
You can do this, but there are a few obstacles you need to hurdle. As #Mohammad Yaser Ahmadi points out, these getters are best suited for synchronous operations, but you can use them in the way you're using them in your example.
So let's consider what's happening here:
partsSchema.virtual('obsolete').get(async function() {
const timestamp = await Globals.findOne({ key: 'obsolescenceTimestamp' }).exec();
return this.updatedAt < timestamp.value;
});
Since the obsolete getter is an async function, you will always get a Promise in the obsolete field when you query your parts collection. In other words, when you do this:
const p = await parts.find();
You will get this:
...
"obsolete": Promise { <pending> },
...
So besides getting the query results for parts.find(), you also need to resolve the obsolete field to get that true or false result.
Here is how I would write your code:
partsSchema.virtual('obsolete').get(async function() {
const Globals = mongoose.model('name_of_globals_schema');
const timestamp = await Globals.findOne({ key: 'obsolescenceTimestamp' });
return this.updatedAt < timestamp.value;
});
Then when querying it...
parts.findOne({_id: '5f76aee6d1922877dd769da9'})
.then(async part => {
const obsolete = await part.obsolete;
console.log("If obsolete:", obsolete);
})
I am modularizing my schema for a GraphQL API and trying to merge the resolvers without using any 3rd party libraries.
Is there a simple way to do this without Lodash.merge() or equivalent?
The Apollo Documentation says to use a library such as Lodash to merge() modularized resolvers. (http://dev.apollodata.com/tools/graphql-tools/generate-schema.html#modularizing)
The problem seems to be that by their nature, the resolvers contain functions as properties, so they seem to be omitted when I access them via Object.assign() or even JSON.stringify().
If I console.log them, I see: {"Query":{},"Mutation":{}}
Here is what one of the resolvers looks like:
const productResolvers = {
Query: {
myProducts: (root, { userId }, context) => {
return [
{ id: 1, amount: 100, expiry: '12625383984343', created: '12625383984343' },
{ id: 2, amount: 200, expiry: '12561351347311', created: '12625383984343' },
{ id: 3, amount: 200, expiry: '11346347378333', created: '12625383984343' },
{ id: 4, amount: 350, expiry: '23456234523453', created: '12625383984343' },
];
},
},
Mutation: {
addProduct: (root, { userId }, context) => {
return { id: 350, amount: 100, expiry: '12625383984343', created: '12625383984343' };
},
}
};
Let's assume there is another one virtually identical called widgetResolvers.
Here is a fully functional block of code:
export const schema = makeExecutableSchema({
typeDefs: [queries, mutations, productSchema, widgetSchema],
resolvers
});
Here is what I'm trying to achieve:
export const schema = makeExecutableSchema({
typeDefs: [queries, mutations, productSchema, widgetSchema],
resolvers: Object.assign({}, productResolvers, widgetResolvers)
});
I haven't loaded in ability to use rest spread yet (https://babeljs.io/docs/plugins/transform-object-rest-spread/). I suspect it won't work for the same reason Object.assign() doesn't work.
Oh, and here is why I suspect this merge doesn't work: Why doesn't JSON.stringify display object properties that are functions?
If you're using Object.assign(), your Query and Mutation properties shouldn't end up empty, but you will run into an issue because, unlike lodash's merge(), it's not recursive. Object.assign() only compares the "direct" properties of the objects it's passed -- overriding properties of previous sources as it moves through the list.
Because Query and Mutation are properties of the objects being passed, each subsequent resolver override the previous object's Query and Mutation, with the resulting object only holding the Query and Mutation properties of the last object passed into Object.assign().
It's a lot less neat, but if you're bent on avoiding importing lodash, you could get the expected behavior this way:
const productResolver = {
Query: { ... ✂ ... },
Mutation: { ... ✂ ... }
}
const widgetResolver = {
Query: { ... ✂ ... },
Mutation: { ... ✂ ... }
}
const resolvers = {
Query: Object.assign({}, widgetResolver.Query, productResolver.Query),
Mutation: Object.assign({}, widgetResolver.Mutation, productResolver.Mutation)
}
Got type resolvers too? No problem:
const Widget = { ... ✂ ... }
const Product = { ... ✂ ... }
const resolvers = Object.assign(
{
Query: Object.assign({}, widgetResolver.Query, productResolver.Query),
Mutation: Object.assign({}, widgetResolver.Mutation, productResolver.Mutation)
},
Widget,
Product)
A model.js file contains this model :
exports.Conversations = db.sequelize.define('conversations', {
room_id: {
type: db.Sequelize.STRING
},
user_id: {
type: db.Sequelize.STRING
},
friend_id: {
type: db.Sequelize.STRING
},
}, {
timestamps: true,
createdAt:'created_at',
updatedAt:'updated_at',
deletedAt:'deleted_at',
freezeTableName: true // Model tableName will be the same as the model name
});
In query.js I have the following function :
exports.checkRoom = function(user_id,friend_id) {
models.Conversations.findOne({ where: { $or: [{user_id: user_id , friend_id: friend_id}, {user_id: friend_id , friend_id: user_id}] }} ).then(function(conversation) {
return conversation;
});
}
equivalent to:
SELECT
"id", "room_id", "user_id", "friend_id", "created_at", "updated_at"
FROM
"conversations" AS "conversations"
WHERE
(("conversations"."user_id" = '127' AND "conversations"."friend_id" = '124')
OR ("conversations"."user_id" = '124' AND "conversations"."friend_id" = '127'))
LIMIT 1;
When I do a call on that function in my cluster.js
var conversation = query.checkRoom(data.userId,data.friendId));
I get that conversation is undefined.
Found a couple of solutions to catch the object Promise but didnt worked.
Looking forward to your answers.
EDIT
Managed to do that but when calling the query I want to add that answer to a var so I can use it later on. If now i`m doing something like var
conversationId = query.checkRoom(data.userId, data.friendId).then(function(conversation) { return conversation.dataValues.id; })
I get that my var conversationId is [object Promise] .
How can I get and use that Promise outside .then() function ?
You're trying to use checkRoom() as a synchronous function, which it isn't (like most functions dealing with I/O in Node).
Instead, you should return the promise returned by findOne() and handle the resolving (and rejection) of that promise in your calling code:
// query.js
exports.checkRoom = function(user_id, friend_id) {
return models.Conversations.findOne({ where: { $or: [{user_id: user_id , friend_id: friend_id}, {user_id: friend_id , friend_id: user_id}] }} );
}
// calling code
query.checkRoom(data.userId, data.friendId).then(function(conversation) {
// do something with the database result
...
})