Sequelize: Special methods don't update the database? - Problem updating instance - javascript

I've spent a decent chunk of the day confused about the behaviour of one of my methods that should create an instance of a Model, Company, that has a Contact.
I feel like it should be simple, but it's just not outputting the contact field as I'd expect, even though it is saving it to the database correctly:
await sequelize.transaction(async (t) => {
const company = await Company.create({ name: req.body.companyName }, { transaction: t });
const contact = await Contact.create({position: req.body.position}, { transaction: t });
await company.addContact(contact, {transaction: t}); // This method does exist
await company.save(); // Thought this might help in case `addContact` didn't update the db
console.log(company.dataValues); // {id:5, name: 'test'}
console.log(contact.dataValues); // {id: 7, position: 'Legend'}
const test = await Company.findOne({where: { name: req.body.companyName }, transaction: t});
console.log(test.dataValues); // {id: 5, name: 'test'}, no contact property
console.log(company.contacts); // is this not posssible instead?
//...
I was expecting this as the output:
{
id: 5,
name: 'test',
contacts: [{ id: 7, position: 'legend' }]
}
Really can't understand why it's not working. Any help would be really appreciated at this point!
** Associations:
Company.hasMany(Contact);
Contact.belongsTo(Company);

Related

Sequelize magic method returning null

Just a bit confused as to why this magic method is returning null. It's probably very simple, but I'm using methods I wouldn't normally (bulkingCreating) and can't currently see it.
Association: Country.hasOne(Capital, { foreignKey: 'countryId' });
Populating dummy data:
const countries = await Country.bulkCreate([
{ name: 'England' },
{ name: 'Spain' },
{ name: 'France' },
{ name: 'Canada' }
]);
const capitals = await Capital.bulkCreate([
{ name: 'London' },
{ name: 'Madrid'},
{ name: 'Paris' },
{ name: 'Ottawa' }
]);
countries.forEach((country, index) => {
country.setCapital(capitals[index]);
});
const country = await Country.findOne({where: {name: 'Spain'}});
console.log(country.name, Object.keys(country.__proto__)); // Spain / magic methods
const capital = await country.getCapital();
console.log(capital); // null
The table:
Am I wrong in thinking country.getCapital() should return the relevant entry?
As you might guess setCapital should be an async function because it makes changes in DB so you need to use for instead of forEach method that does not support async callbacks:
let index = 0;
for (const country of countries) {
await country.setCapital(capitals[index]);
index += 1;
}
It would be better to create countries one by one and create capitals for them not relying on the same indexes of both collections (DB might return created records in a different order).
If you are using Sequelize 5.14+, you can do this in 1 bulkCreate using include option.
const countries = await Country.bulkCreate([
{
name: 'England',
Capital: { // This keyname should be matching with model name.
name: 'London'
}
},
{
name: 'Spain',
Capital: {
name: 'Madrid'
}
},
...
],
{
include: Capital,
returning: true // If Postgres, add this if you want the created object to be returned.
}
);

Chunk update using knex.js and MySQL

I am trying to learn how to use knex.js and as part of my learning I want to make some migration on my db, I want to update one of my columns.
I am trying to do this with chunks (I know it is not the best solution, it's only for learning purposes). I am using MySQL.
In my db I have a table that contains "email" column some of the rows end with ".com".
I am trying to update this column by omitting the ".com".
In my code, I select the relevant columns and I am using dash lib to chunk all my data.
My chunked data looks like this:
my db : (https://ibb.co/c8kKtcb)
chunk no.1
[
RowDataPacket {id: 1, email: dan#gmail.com},
RowDataPacket {id: 2, email: john#gmail},
RowDataPacket {id: 3, email: steph#gmail},
]
chunk no.2
[
RowDataPacket {id: 4, email: eric#gmail},
RowDataPacket {id: 5, email: tommy#gmail.com},
RowDataPacket {id: 6, email: bill#gmail.com},
]
chunk no.3
[
RowDataPacket {id: 7, email: loe#gmail},
RowDataPacket {id: 8, email: helen#gmail.com},
RowDataPacket {id: 9, email: niky#gmail.com},
]
…
This is my code, I tried to do the update but I figure that I am doing something wrong since its list end .update() method work with key value. Can I manipulate my data?
exports.up = async knex => {
const users = await knex("usersWeb")
.select("id", "email")
.where("email", "like", "%.com");
const userChunks = _.chunk(users, 3);
let chunckNumber = 1;
for (const chunk of userChunks) {
console.log("Chunck number: ", chunckNumber);
const k = await knex("usersWeb")
.whereIn(columnId, chunk.map(item => item.id))
.update("email": ); // this line :(
}
chunckNumber++;
}
exports.down = async knex => {};
I solve it after some time and I forgot to share my solution, so here is it:
exports.up = async knex => {
const users = await knex("usersWeb")
.select("id")
.where("email", "like", "%.com");
const userChunks = _.chunk(users, 3);
let chunckNumber = 1;
for (const chunk of userChunks) {
console.log("Chunck number: ", chunckNumber);
const tupleOfAllEmailIds = "(" + chunk.map(item => `${item.id}`).join(", ") + ")";
const query = `UPDATE "usersWeb"
SET "email" = REPLACE("email", ".com", "")
WHERE id IN ${tupleOfAllEmailIds}
`;
await knex.raw(query);
}
chunckNumber++;
}
exports.down = async knex => {};

TypeError: Cannot read property 'map' of undefined but state has been stored

I am trying to get a specific team data from the database and store it in a state. But when I map an array inside that data it returns an error. When I console log my state it returns the data below
createdAt: "2021-03-19T13:36:22.868Z"
gameEvent: "basketball"
players: Array(5)
0: {_id: "605ea59c5cdf492b48987107", name: "Jerry Ale", jerseyNumber: "12"}
1: {_id: "605ea59c5cdf492b48987108", name: "Judel Agur", jerseyNumber: "14"}
2: {_id: "605ea59c5cdf492b48987109", name: "qwe", jerseyNumber: "12"}
3: {_id: "605ea59c5cdf492b4898710a", name: "qwe", jerseyNumber: "12"}
4: {_id: "605ea59c5cdf492b4898710b", name: "qwe", jerseyNumber: "12"}
length: 5
__proto__: Array(0)
teamName: "Balilihan"
updatedAt: "2021-03-27T03:25:16.148Z"
__v: 0
_id: "6054a8d63fec5c24389624ac"
I have an useEffect to gather this;
useEffect(() => {
const getTeam = async () => {
try {
const { data } = await fetchContext.authAxios.get('get-all-teams');
setIsLoaded(true);
if (isLoaded === true) {
setCurrentTeam(data.find((team) => team._id === row._id));
}
} catch (err) {
console.log(err);
}
};
getTeam();
}, [fetchContext, row, isLoaded]);
and I map the players array in a new variable because I want a controlled inputs for my form because I am updating the data. I am using Formik by the way
let playersOfTeam = currentTeam.players.map((player, index) => [
{
name: player.name,
jerseyNumber: player.jerseyNumber,
},
]);
But when I just get a specific value like the teamName it returns the teamName and when I console log currentTeam.players it returns what I expected to get. I am confused why I get this kind of error
Your data is undefined when the component is first mounted. This is because useEffect runs after render.
So adding a null check is the solution. Personally I prefer optional chaining. Simply change to:
let playersOfTeam = currentTeam?.players?.map((player, index) => [
{
name: player.name,
jerseyNumber: player.jerseyNumber,
},
]);

Sequelize get own attributes only, ignore included instances

What I'm looking for is an instance method in Model that will return only the attributes of that model & exclude instances of any included model.
eg: Imagine I have 2 models, with a hasMany ( or any ) association:
Post {
id,
content,
user_id
}
User: {
id,
name,
}
and I have:
const userWithPosts = await User.findOne({
where: { id: 33 },
include: [{
model: Post,
as: 'posts'
}]
});
console.log(userWithPosts)
/*
{
id: 33,
name: 'John Doe',
posts: [
Post {
id: 1,
content: '..',
user_id: 33
},
Post {
id: 2,
content: '...',
user_id: 33
}
]
}
*/
I'm looking for a method, say getOwnAttributes or something like that which does:
userWithPosts.getOwnAttributes()
/*
{
id: 33,
name: 'John Doe',
}
*/
I've looked into couple of things:
userWithPosts.get({ raw: true })
userWithPosts.get({ plain: true })
userWithPosts.toJSON()
All of the above returns included instances as well.
Any existing method or workaround that can do this?
EDIT: I'm not talking about doing it at query time, but getting the value from already queried instance. Currently my work-around for this is:
const payload = _.pick(userWithPosts.toJSON(), [
...Object.keys(User.rawAttributes),
]);
You can refer to the code below to exclude attributes of Post table.
const userWithPosts = await User.findOne({
where: { id: 33 },
include: [{
model: Post,
as: 'posts',
attributes: []
}]
});
I hope it helps!

batchSize field name ignored in Field Projection

I have a user_batch collection. It contains following documents:
[{
_id: ObjectId("594baf96256597ec035df23c"),
name: "Batch 1",
batchSize: 30,
users:[]
},
{
_id: ObjectId("594baf96256597ec035df234"),
name: "Batch 2",
batchSize: 50,
users:[]
}]
In find query I want to project only name and batchSize. But when I execute find query from nodejs, I'm getting entire document in query result. Query:
db.collection('user_batch').find({}, {name: 1, batchSize: 1}).toArray((err, result) => {
if(err)
console.log(err)
else
console.log(result)
})
If I just pass {name: 1} then it will project _id and name. But if I pass batchSize then it will return entire document.
Note: I'm not facing this issue while executing this query in Mongo Shell
You are correct that the driver incorrectly interprets this as the batchSize option and ignores the projection statement.
The correct way to do this though in modern driver releases is to actually use the .project() "cursor method" instead. This is more consistent with other language driver implementations.
db.collection('collection').find()
.project({ name: 1, batchSize: 1})
.toArray();
As a full demonstration:
const mongodb = require('mongodb'),
MongoClient = mongodb.MongoClient;
(async function() {
let db;
try {
db = await MongoClient.connect('mongodb://localhost/test');
// New form uses .project() as a cursor method
let result = await db.collection('collection').find()
.project({ name: 1, batchSize: 1})
.toArray();
console.log(JSON.stringify(result,undefined,2));
// Legacy form confuses this as being a legacy "cursor option"
let other = await db.collection('collection')
.find({},{ name: 1, batchSize: 1 })
.toArray();
console.log(JSON.stringify(other,undefined,2));
} catch(e) {
console.error(e)
} finally {
db.close()
}
})()
Produces the output:
[
{
"_id": "594baf96256597ec035df23c",
"name": "Batch 1",
"batchSize": 30
},
{
"_id": "594baf96256597ec035df234",
"name": "Batch 2",
"batchSize": 50
}
]
[
{
"_id": "594baf96256597ec035df23c",
"name": "Batch 1",
"batchSize": 30,
"users": []
},
{
"_id": "594baf96256597ec035df234",
"name": "Batch 2",
"batchSize": 50,
"users": []
}
]
Where the first output form is the corrected one, using .project()
The syntax of Find has changed. Below is what I needed to know to solve this problem. This is excerpted from https://github.com/mongodb/node-mongodb-native/blob/master/CHANGES_3.0.0.md#find
Find
find and findOne no longer support the fields parameter. You can achieve the same results as
the fields parameter by using Cursor.prototype.project or by passing the projection property
in on the options object . Additionally, find does not support individual options like skip and
limit as positional parameters. You must either pass in these parameters in the options object,
or add them via Cursor methods like Cursor.prototype.skip.
2.x syntax:
const cursor = coll.find({ a: 42 }, { someField: 1 });
3.x syntax:
const cursor = coll.find({ a: 42 }).project({ someField: 1 });
/* OR */
const cursor = coll.find({ a: 42 }, { projection: { someField: 1 } });

Categories

Resources