many-to-many relationships between tables - javascript

I have a database library. It has 2 tables: Reader and Book. The tables are related to each other by belongToMany. I need to implement a method that gives a book to the user. As I understand it, this data is stored in a link table, but for some reason I don’t have it? How to give a book to a reader?
Data already exists in Reader and Book tables
My models:
const Book = sequelize.define('Book', {
id: {type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true},
title: {type: DataTypes.STRING, allowNull: false},
author: {type: DataTypes.STRING, allowNull: false},
vendorCode: {type: DataTypes.INTEGER, allowNull: false},
year: {type: DataTypes.DATEONLY, allowNull: false},
numberOfCopies: {type: DataTypes.INTEGER}
});
const Reader = sequelize.define('Reader', {
id: {type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true},
fullName: {type: DataTypes.STRING, allowNull: false},
birth: {type: DataTypes.DATEONLY, allowNull: false}
});
const ReaderBook = sequelize.define('ReaderBook', {
});
Reader.belongsToMany(Book, {through: 'ReaderBook'});
Book.belongsToMany(Reader, {through: 'ReaderBook'});
module.exports = {Book, Reader, ReaderBook};
My method:
async issueBook(req, res) {
try {
const {idBook, idReader} = req.body;
const book = await Book.findByPk(idBook);
const reader = await Reader.findByPk(idReader);
if (!reader || !book) {
return res.status(400).json({message: 'Reader does not exist'});
}
await reader.addBook(book, {through: ReaderBook}); //addBook method doesn't exist why?
console.log(readerBook);
return res.json("Test");
} catch (e) {
console.log(e.message);
return res.status(400).json({message: 'Error issuing a book to a reader'});
}
};

First of all, try to pass ReaderBook into belongsToMany relation on creation. Replace it's string name with actual object you've created.
Reader.belongsToMany(Book, {through: ReaderBook });
// no qoutes here ^ ^
Book.belongsToMany(Reader, {through: ReaderBook });
To be more clear you can simplify your code.
As you mentioned you have a Reader entity:
const Reader = sequelize.define('Reader', { ... });
And Book entity:
const Book = sequelize.define('Book', { ... });
You could ommit manual creation of junction table by just passing name of the table as string value.
Reader.belongsToMany(Book, { through: 'ReaderBook' });
Book.belongsToMany(Reader, { through: 'ReaderBook' });
To apply the models to database now we have to call sync on sequelize:
await sequelize.sync({ alter: true });
As you see there's no necessity to define 3d table at all.
Now 'giving' book to a reader would be even easier.
const book = await Book.create();
const reader = await Reader.create();
//or find them by primary keys or whatever else
const book = await Book.findByPk(keyId);
const reader = await Reader.findByPk(readerId);
//here's simple adding book to the reader
await reader.addBook(book);
That's all, you don't even need to pass any 'through' parameters.

Related

Is there a way to set 'expires' option in Sequelize timestamps just like Mongoose?

const sequelize = require("../config/sequelize");
const Sequelize = require("sequelize");
const Token = sequelize.define('token', {
token: {
type: Sequelize.STRING,
allowNull: false
},
createdAt: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.NOW
expires: 43200
}
})
Can I add expires option like in the above code in Sequelize?
#ayindesamuel why don't you create the tokenExpires key directly inside your sequelize model? Also, including a third object allows you to turn on timestamps (createdAt and updatedAt) these columns will be automatically added by sequelize. So you don't have to manually create the Key.
const sequelize = require("../config/sequelize");
const Sequelize = require("sequelize");
const Token = sequelize.define('token', {
token: {
type: Sequelize.STRING,
allowNull: false
},
tokenExpires : {
type: Sequelize.DATE,
defaultValue: Date.now() + 43200
}
}, { timestamps : true })

How to modify virtual fields from sequelize findAll result?

I have looked everywhere and couldn't find any clear answers for this.
I have a complex findAll() with many inclusions and each with their own virtual fields.
What I want is to modify the virtual fields of the result, however as it is returning the model instance trying to access the virtual fields returns undefined as they are not in the result yet.
I have tried 'raw: true' but this removes all virtual fields and as my data has nested tables which also have their own virtual fields which I need, I cannot do that.
Example models
var Book = sequelize.define('Book', {
id: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
title: {
type: DataTypes.STRING
},
author: {
type: DataTypes.STRING
}
//....other columns,
myField: {
type: DataTypes.Virtual,
get() {
return this.getDataValue('title:') + this.getDataValue('author');
})
Getting the data
model.Book.findAll({
limit: 100
})
.then((result) => {
const newBook = result.map(row => {
return {...row, myField: 'setMyOwnValueHere'}
}
return newBook
}
Get model data first : get
model.Book.findAll({
limit: 100
}).then(result => {
const books = result.map(row => {
//this returns all values of the instance,
//also invoking virtual getters
const book = row.get();
book.myField = 'setMyOwnValueHere';
return book;
});
return books;
});

Mongoose not executing inner query with Promise mechanism

I'm new to the async/await world and trying to experiment a bit with Mongoose + MongoDB + Node.JS
I have this piece of code
exports.updateBrandPreferences = async (req,res) => {
var userID = req.body.playerID;
var newBrands = req.body.brandList;
console.log("Ricevuto la seguente lista: " + newBrands);
async.each(newBrands, function(brand,error) {
Brand.findOneAndUpdate({'name': brand},{$addToSet: {peopleInterested: userID}}, {new:true}).exec().then((results) => {
console.log(results);
User.findOneAndUpdate({_id: userId},{$addToSet: {interestedBrands: results._id}}, {new:true}).exec().then((risultato)=> {
console.log(risultato);
return risultato;
}).catch((err) => {
return "error";
});
return results;
}).catch((err) => {
return "error";
});
});
return res.status(200).json({"message": "OK"});
};
Taking some elements from the request, my objective is to associate the specified user with a list of some brands of interest. While the first query works (so Brands do now have new users inside them to symbolize their interest), this doesn't work for users as the second query doesn't get executed.
What am I missing? Schemas are the following:
Brand :
var BrandSchema = new Schema({
name: {type: String, required: true, unique: true},
peopleInterested: Array,
}, {
collection: 'brands',
retainKeyOrder: true,
timestamps: true,
}).plugin(mongoosePaginate);
User:
var UserSchema = new Schema({
isAdmin: {type: Boolean, default: false},
name: String,
surname: String,
email: { type: String, lowercase: true, required: true, trim: true, unique: true, dropDubs: true },
password: { type: String, required: true },
salt: { type: String },
verified: { type: Boolean, default: false },
bio: {
type: { type: String, enum: [0,1] }, // 0='Squadra', 1='Giocatore'
birthday: String,
height: Number,
number: Number,
role: { type: String, enum: [0,1,2,3] }, // 0='Playmaker', 1='Ala', 2='Guardia', 3='Centro'
team: String,
city: String,
fiscalCode: {type: String, maxlength:16}
},
newsletter: {type: Boolean, default: false},
lastCheckin: {type: mongoose.Schema.Types.ObjectId, ref: 'Checkin'},
follows: [{type: mongoose.Schema.Types.ObjectId, ref: 'Structure'}],
interestedBrands: Array,
score: { type: Number, default: 0 },
profilePicture: String,
lastLogin: {type: Date},
facebook: {
id: String,
accessToken: String,
profileImage : String
}
}, {
collection: 'users',
retainKeyOrder: true,
timestamps: true,
}).plugin(mongoosePaginate);
This is the same problem as this one or this one. async function, async library and plain promises are mixed together. Promises aren't chained correctly inside callbacks.
async function (it is syntactic sugar for promises) and async library (async.each) solve similar tasks, they shouldn't be mixed unless proven otherwise. async library was a predecessor to native promises, it is callback-based and may result in callback hell; something that promises are supposed to help with (they are callback-based but provide a useful pattern for chaining).
Promises inside async function are supposed to be processed in series with await and loop statement (for..of), in parallel with await and Promise.all.
Express is unaware of promises. As explained in this answer, all rejections inside middleware should be handled.
It should be:
exports.updateBrandPreferences = async (req, res, next) => {
try {
var userID = req.body.playerID;
var newBrands = req.body.brandList;
await Promise.all(newBrands.map(async (brand) => {
await Brand.findOneAndUpdate({'name': brand},{$addToSet: {peopleInterested: userID}}, {new:true});
await User.findOneAndUpdate({_id: userId},{$addToSet: {interestedBrands: results._id}}, {new:true});
}));
return res.status(200).json({"message": "OK"});
} catch (err) {
next(err);
}
};

Connect mongoose-array-values to a unique ID

This may seem like a vague question, but I'm going to try to explain the best I can. As a side note, I'm quite new to using mongoose :)
I have a mongoose-schema storing different values for each user, like so...
let userSchema = mongoose.Schema({
user: { type: String, required: true, unique: true },
pass: { type: String, required: true },
files: [{ type: String, required: false }],
});
The "files"-key contains an array of values, lets say for example:
userSchema.files = [value1, value2, value3]
And I want each value to be connected to some kind of ID, so that when I call the specified ID, I get the specified value. Just for demonstrating purposes, it could look something like this:
userSchema.files = [{value:value1, id: id1},
{value:value2, id: id2},
{value:value3, id: id3}]
Then I want to find the specified id, and return it's "value"-key in a request:
router.route("/home/:id")
.get(restrict, function(req, res) {
User.findOne({ user: req.session.Auth.username }, function(error, data) {
data.files.forEach(function(file) {
if (file.id === req.params.id) {
response.render("../home", file.value)
}
}
});
});
How can I do this? Tried pushing an object to files, but that didn't work as expected. Read something about ObjectId, but couldn't quite understand it. Any tips?
I think you simply need to create a separate model for File and connect it to your User model using the 'ref' keyword :
let fileSchema = mongoose.Schema({
_id : Number,
value : String
});
let userSchema = mongoose.Schema({
user: { type: String, required: true, unique: true },
pass: { type: String, required: true },
files: [{ type: Number, ref: 'File' }]
});
let User = mongoose.model('User', userSchema);
let File = mongoose.model('File', fileSchema);
let f1 = new File({ _id: 1, value: 'File 1'});
let f2 = new File({ _id: 2, value: 'File 2'});
let f3 = new File({ _id: 3, value: 'File 3'});
let user1 = new User({user:'chuck', pass:'norris'});
user1.files.push(f1);
user1.files.push(f2);
user1.files.push(f3);
user1.save(function(err){ });
Now to get the data back:
User
.findOne({ user: 'chuck' })
.populate('files') // only works if we pushed refs to children
.exec(function (err, user) {
if (err) return handleError(err);
console.log(user);
//you can now loop through user.files and compare _id
user.files.forEach(function(file) {
if (file._id === req.params.id) {
response.render("../home", file.value)
}
}
});
You can read about mongoose reference population here: http://mongoosejs.com/docs/populate.html

How to save document based on user id using mongoose?

I am trying to save template based on user id , How can i make sure when template save it save with user id _id ? i added reference to the templateSchema for User.
user.model.js
var UserSchema = new mongoose.Schema({
_id: { type: String, required: true, index: {unique: true}},
firstName: String,
lastName: String,
type: String,
groups:[{type: String, ref: 'Group', required: false}]
},
{
toObject: {
virtuals: true
},
toJSON: {
virtuals: true
}
});
export default mongoose.model('User', UserSchema);
template.model.js
var User = require('../user/user.model.js');
var TemplateSchema = new mongoose.Schema({
_id: { type: String, required: true},
name: String,
id: String,
appliesTo: [],
properties: [],
createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User'}
});
export default mongoose.model('Templates', TemplateSchema);
template.controller.js
var eTemplate = require('./template.model');
export function create(req, res) {
console.log(req.body);
eTemplate.createAsync(req.body)
.then(responseWithResult(res, 201))
.catch(handleError(res));
}
Mongoose has two built-in functions that are called before (pre) and after (post) you save a document. My advice is to make use of them. Here is an example of my code in which I search for an sequence number before saving the user document. You can do the same: When you save the template, make a request for the user id to the database (Or vice-versa). You can even save one, get the id and save the other.
Bellow follows my code for the sequence and the user.
var UserSchema = new Schema({
username: { type: String, required: true, unique: true },
id: { type: String },
...
});
UserSchema.pre('save', function(next) {
let doc = this;
let id = 'userSeq'
Sequence.findByIdAndUpdate(id, { $inc : {nextSId : 1} }, function(error,data) {
if(error)
next(error)
doc.id = data.nextSId-1;
next();
})
});
I hope my answer was useful for you. Just a remark, pre and post are not called in the event of updates for the document.

Categories