I have a table in my database called users. In this table I only store user ID, username and password. Now, in another table called user_meta, I have the following columns: id, uid, meta_key, meta_value. I'm trying to find a way for Bookshelf to automatically load all records in user_meta where uid == userid, and store them as model.meta[meta_key] = meta_value. Sadly, I haven't been able to find a way to make this possible.
If it is possible at all, the 2nd step would be to also save all values in model.meta back on update / insert, inserting records where meta_key doesn't exist for that user ID yet, and updating where it does.
Try to set the associations (relations) between the models:
var User = bookshelf.Model.extend({
tableName: 'users',
meta: function() {
return this.hasMany(Meta);
}
});
var Meta = bookshelf.Model.extend({
tableName: 'user_meta',
user: function() {
return this.belongsTo(User);
}
});
http://bookshelfjs.org/#one-to-many
Related
I am using Sequelize in my node js server. I am ending up with validation errors because my code tries to write the record twice instead of creating it once and then updating it since it's already in DB (Postgresql).
This is the flow I use when the request runs:
const latitude = req.body.latitude;
var metrics = await models.user_car_metrics.findOne({ where: { user_id: userId, car_id: carId } })
if (metrics) {
metrics.latitude = latitude;
.....
} else {
metrics = models.user_car_metrics.build({
user_id: userId,
car_id: carId,
latitude: latitude
....
});
}
var savedMetrics = await metrics();
return res.status(201).json(savedMetrics);
At times, if the client calls the endpoint very fast twice or more the endpoint above tries to save two new rows in user_car_metrics, with the same user_id and car_id, both FK on tables user and car.
I have a constraint:
ALTER TABLE user_car_metrics DROP CONSTRAINT IF EXISTS user_id_car_id_unique, ADD CONSTRAINT user_id_car_id_unique UNIQUE (car_id, user_id);
Point is, there can only be one entry for a given user_id and car_id pair.
Because of that, I started seeing validation issues and after looking into it and adding logs I realize the code above adds duplicates in the table (without the constraint). If the constraint is there, I get validation errors when the code above tries to insert the duplicate record.
Question is, how do I avoid this problem? How do I structure the code so that it won't try to create duplicate records. Is there a way to serialize this?
If you have a unique constraint then you can use upsert to either insert or update the record depending on whether you have a record with the same primary key value or column values that are in the unique constraint.
await models.user_car_metrics.upsert({
user_id: userId,
car_id: carId,
latitude: latitude
....
})
See upsert
PostgreSQL - Implemented with ON CONFLICT DO UPDATE. If update data contains PK field, then PK is selected as the default conflict key. Otherwise, first unique constraint/index will be selected, which can satisfy conflict key requirements.
Okay so, I'm displaying a friends table with Sequelize in Nodejs, Everything goes to plan but I run into a problem. For the friends table i store the user ids and then access the user data with those ids then display it onto ejs. I want to access another table using the current tables data.
Heres the code to access the Friends table, I made it a function so i can access it from ejs
let getFriends = async (id) => {
const project = await database.Friends.findAndCountAll({ where: { fid: id } });
return project.rows
}
you can access another table using include.
new code will be
let getFriends = async (id) => {
const project = await database.Friends.findAndCountAll({ where: { fid: id },
include:[{
model:model.User,as:'user'}]
});
return project;
}
I am creating two models, the first one is User and the second one is Portfolio. When a user is creating a Portfolio (where a user can only have one Portfolio), I want it to have reference to the user who is creating it, and every time that user's data is fetched, I want it to also fetching their portfolio data if any.
I am trying to use hasOne to create portfolio_id inside User tables, with the skeleton generated using sequelize init command, but it is not working. I cannot find a column the name protfolio_id if I don't put it inside the user migration file. Is that how it is supposed to be?
How should I design the models? Should I include the portfolio_id in User tables and include user_id in Portfolio table, or is there a best way to do it?
And which associations method should I use, hasOne or belongsTo?
First of all make sure that you are calling Model.associate for each model. This will run queries for all the relationships.
You can define the relationships in the associate method as follows:
// user.js (User Model definition)
module.exports = (sequelize, dataTypes) => {
const { STRING } = dataTypes
const User = sequelize.define("user", {
username: { type: STRING }
})
User.associate = models => {
User.hasOne(models.Portfolio, { foreignKey: "userId" }) // If only one portfolio per user
User.hasMany(models.Portfolio) // if many portfolios per user
}
return User
}
// portfolio.js (Portfolio Model definition)
module.exports = (sequelize, dataTypes) => {
const { STRING } = dataTypes
const Portfolio = sequelize.define("portfolio", {
portfolioName: { type: STRING }
})
Portfolio.associate = models => {
Portfolio.belongsTo(models.User, { foreignKey: "userId" })
}
return Portfolio
}
hasOne stores the foreignKey in the target model. So this relationship will add a foreign key userId to the Portfolio model.
belongsTo stores the key in the current model and references the primary key of the target model. In this case the Portfolio.belongsTo will add userId in the Portfolio model which will reference the primary key of User model.
Notice how both these relationships do the same thing, they add userId to the Portfolio model. Its better to define this in both models for your last use case:
I want it to have reference to the user who is creating it, and every time that user's data is fetched, I want it to also fetching their portfolio data if any.
Accessing related models:
In sequelize fetching a related model together with the main model is called Eager Loading. Read more about it here.
Now for your use case if you want to fetch the portfolio if any, while fetching user, do the following:
var userWithPortfolio = await User.findAll({include: [models.Portfolio]};
// Or you may also use include: {all: true} to include all related models.
var userWithPortfolio = await User.findAll({include: {all: true}};
/*
Output:
userWithPortfolio = {
username: "xyz",
portfolio: {
portfolioName: "xyz"
}
}
*/
Im currently having an issue where i want to specify to the bookshelf model which field to use to make the data relation.
it seems to always use the id (primary key) of the model table; as far as i've found out it's only possible to set column for the relation but not which to use from model.
for example:
var StopsWithCustomer = bookshelf.Model.extend({
tableName: 'stops',
customers: function () {
return this.hasOne(customerWithStop, 'id');
}
});
it has to match on customerWithStop on the column id but it has to use the column customer_id from 'stops' to make this relation; is there any way to specify this?
Besides tableName Bookshelf.js also provides an idAttribute property. That will allow you to override Bookshelf.js' id default.
Note the second argument of the relationship (like your hasOne()) is the foreign key, not the target's primary key.
Example:
var Language = bookshelf.Model.extend({
tableName: 'languages',
idAttribute: 'languageid'
});
var Post = bookshelf.Model.extend({
tableName: 'posts',
idAttribute: 'postid',
Language: function() {
return this.belongsTo(Language,'languageid');
}
});
I'm using BookshelfJS. I have two models users and posts. Obviously, the relationship here is many to many. So I have a pivot table post_user.
Now, given a user_id, I want to find all the posts for that user. So far, I've managed to do this using knex:
knex.select()
.from('post_user')
.where('user_id', user_id)
.then(function (pivots) {
// Now loop through pivots and return all the posts
// Using pivot.post_id
pivots.map(function(pivot) {})
});
Is there a cleaner way of doing this?
You'll want to define the many-to-many relationship in your Bookshelf models. Something like this:
var User = bookshelf.Model.extend({
tableName: 'users',
posts: function() {
return this.belongsToMany(Post);
}
});
var Post = bookshelf.Model.extend({
tableName: 'posts',
users: function() {
return this.belongsToMany(User);
}
});
By convention, the pivot table Bookshelf would be using is posts_users (table names combined with _, starting from the table that is alphabetically first). It should contain a user_id and a post_id (and a coposite PK of those two). If you don't wish to rename the pivot table, see the documentation for belongsToMany for instructions on how to define the table and ids of your pivot table.
After this, you can query your model with Bookshelf:
return new User().where('id', user_id)
.fetch({withRelated: 'posts'})
.then(function (user) {
var postCollection = user.get('posts');
// do something with the posts
});
See also the documentation for fetch.