Sequelize - How To Design One To One Association? - node.js

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"
}
}
*/

Related

How can I dynamically generate Mongoose discriminators (at runtime?)

TL;DR: Is there a safe way to dynamically define a mongoose discriminator at runtime?
I have an app with a MongoDB collection where users have some control over the underlying schema.
I could add one or two fixed, required fields and just use mongoose.Mixed for the remainder that users can change, but I'd like to make use of Mongoose's validation and discriminators if I can.
So, what I've got is a second collection Grid where the users can define the shape they'd like their data to take, and in my main model Record, I've added a function to dynamically generate a discriminator from the definition in the second collection.
The code for my Record model looks like this:
const mongoose = require("mongoose")
const recordSchema = new mongoose.Schema({
fields: {
type: Array,
required: true
}
}, {
discriminatorKey: "grid"
})
const Record = mongoose.model("Record", recordSchema)
module.exports = grid => {
// Generate a mongoose-compatible schema from the grid's field definitions
const schema = grid.fields.map(field => {
if(field.type === "string") return { [field.name]: String }
if(field.type === "number") return { [field.name]: Number }
if(field.type === "checkbox") return { [field.name]: Boolean }
return { [field.name]: mongoose.Mixed }
})
return Record.discriminator(grid._id, new mongoose.Schema(schema))
}
This is inside an Express app, and I use the model in my middleware handlers something like this:
async (req, res) => {
const grid = await Grid.findById(req.params.id)
const Record = await GenerateRecordModel(grid)
const records = await Record.find({})
res.json({
...grid,
records
})
}
This works great on the first request, but after that I get an error Discriminator with name “ ” already exists.
I guess this is because only one discriminator with its name per model can exist.
I could give every discriminator a unique name whenever the function is called:
return Record.discriminator(uuidv4(), new mongoose.Schema(schema), grid._id)
But I imagine that this isn't a good idea because discriminators seem to persist beyond the lifetime of the request, so am I laying the groundwork for a memory leak?
I can see two ways forward:
COMPLICATED? Define all discriminators when the app boots up, rather than just when a HTTP request comes in, and write piles of extra logic to handle the user creating, updating or deleting the definitions over in the Grid collection.
SIMPLER? Abandon using discriminators, just use mongoose.Mixed so anything goes as far as mongoose is concerned, and write any validation myself.
Any ideas?

Automatically include referenced model in Sequelize on each GET

So I have user model and admin model and they're associated as user n:1 admin. The code defining the user model as follows:
// users.model.ts
const users = sequelize.define('users', {
...
adminId: {
field: 'admin_id',
type: DataTypes.BIGINT,
allowNull: true
},
...
});
(users as any).associate = function associate(models: any) {
models.users.belongsTo(models.admins);
};
return users;
and the admin model:
// admins.model.ts
const admins = sequelizeClient.define('admins', {
...
});
(admins as any).associate = function associate(models: any) {
models.admins.hasOne(models.users);
};
return admins;
Is it possible to implement some rule in the association, or some Sequelize hook f.e. afterGet that will automatically fetch the referenced record?
I would like to get the admin object as a property of the user object when I query just the User model, f.e. when I call User.findOne(123) it will have the object of the referenced admin record included. Basically telling Sequelize to always do the JOIN when getting the user record. Is that possible in Sequelize or I'll have to write logic separately?
Eventually I figured out that this is done through scopes (docs here and here).
I added this to the users.model.ts:
/*
* When called as `model.scope('<scope_name>').<method>` it will override the default behaviour of Sequelize and
* will add to the query whatever is requested in the scope definition.
*/
users.addScope('includeAdmin', {
include: [{
attributes: ['id', 'name'],
model: sequelize.models.admins,
as: 'admin'
}]
});
Eventually, I will make the following call: User.scope('includeAdmin').findOne(123), at which point Sequelize will automatically JOIN the admins model.
By default the admin entity's properties will be returned as such in the user object:
{
"admin.id": ...,
"admin.name": ...
}
So, if you want to have them as a nested admin object, then you must add nest: true property in the call, as follows: User.scope('includeAdmin').findOne(123, {nest: true})
If I want to make this behaviour default and not call .scope('...'), then when you declare the scope in the .addScope() function, call it 'defaultScope'.
That's why associations are used, to get things. To get the relation as attribute you can use eager loading:
const awesomeCaptain = await Captain.findOne({
where: {
name: "Jack Sparrow"
},
include: Ship
});
// Now the ship comes with it
console.log('Name:', awesomeCaptain.name);
console.log('Skill Level:', awesomeCaptain.skillLevel);
console.log('Ship Name:', awesomeCaptain.ship.name);
console.log('Amount of Sails:', awesomeCaptain.ship.amountOfSails);

How to define many to many with Feathers and Sequelize with additional fields in join table?

I am struggling to find the solution for that.
I want to have users which can belong to many organizations.
Each user can have a different role (I would prefer even roles but it sounds even more complicated...) at a specific organization.
In the table like User_Organization_Role I need to have fields like role (roleId?), isActive. Maybe some more.
I am using Feathers Plus generator but I do not think it matters in this case, however it may be beneficial to add something to the schema file?
I thought having belongsTo with simple organizationId field will be sufficient but I've realized that changing that to manyToMany, later on, would be painful so I think it is much better to implement that now.
I will appreciate any solutions / suggestions / best practices etc.
n:m relations are by far the most difficult to handle, and there's really no one-size-fits-all solution. The biggest thing is to read and understand this page and its sub-pages, and then read them 2 more times for good measure. Try to focus on doing one thing at a time. I outline how I would approach this with feathersjs in this issue:
https://github.com/feathersjs/feathers/issues/852#issuecomment-406413342
The same technique could be applied in any application... the basic flow goes like this:
Create or update your primary objects first (users, organizations, roles, etc.). There are no relations made at this point. You need to have your objects created before you can make any relations.
Create or update the relations. This involves updating a "join" table (aka: "mapping" or "through" table) with data from step #1. The join table can (and should) have its own model. It should contain a foreign key for each of the objects you are associating (userId, organizationId, roleId etc.). You can put other fields in this table too.
Here is some pseudo code for how I would define my models (only showing relevant code for brevity). There is a little more to it than what I describe below, but this should get you started.
const UserOrganizationRole = sequelize.define('User_Organization_Role', {
// Define any custom fields you want
foo: DataTypes.STRING
})
// Let sequelize add the foreign key fields for you.
// Also, save a reference to the relationship - we will use it later
User.Organization = User.belongsToMany(Organization, { through: UserOrganizationRole });
User.Role = User.belongsToMany(Role, { through: UserOrganizationRole });
Organization.User = Organization.belongsToMany(User, { through: UserOrganizationRole });
Role.User = Role.belongsToMany(User, { through: UserOrganizationRole });
... and here is how I would go about handling inserts
const user = await User.create({ ... });
const org = await Organization.create({ ... });
const role = await Role.create({ ... });
await UserOrganizationRole.create({
userId: user.id,
organizationId: org.id,
roleId: role.id,
foo: 'bar'
});
... and finally, load the data like so:
// Now we can reference those relationships we created earlier:
const user = await User.findById(123, {
include: [User.Organization, User.Role]
});
const org = await Organization.findById(456, {
include: [Organization.User]
});

AdonisJs migrations solution for many to many

I was wondering how I can solve a many to many solutions with migrations like at this diagram:
DB relation diagram
u_id_user is a FK from the User table
u_g_id is unique and auto-increment
g_id_group is a FK from Group
Code example:
class UserGroupsSchema extends Schema {
up () {
this.create('user_groups', (table) => {
table.increments()
table.integer('u_id_user')
table.integer("g_id_group")
table.timestamps()
})
}
An other problem is that if I run my migrations it creates the tables with type MyISAM and I cant build relations with this type.. so how can I change the default type to InnoDB?
From the adonis.js documentation you need to define your models like this:
const Model = use('Model')
class User extends Model {
groups () {
return this.belongsToMany('App/Models/Group')
}
}
module.exports = User
After that you can query the date like this:
const user = await User.find(1)
const groups = await user
.groups()
.wherePivot('user_id', '=', user.id)
.fetch()
I haven't tested the code but this should put you on the right track

Updating a waterline model from an instance method

I am using the Waterline ORM to interface with my database.
I am extend Waterline.Collections into a new objects and assign them to a module:
var Waterline = require('waterline');
var User = Waterline.Collection.extend({
...some fields...
attributes:{
col_a:{
type:'integer'
},
touch_user:function(){
// increment col_a and update in table
}
}
});
export = module.exports = User;
I query for the specific models I need to update, then manipulate them in a promise.
database.models.users.findAll().where({'id':1}).then(...model manipulation...)
There is a task I perform often, almost whenever I access the model. I would like to encapsulate this task in a function associated with the instance so I can simple call "touch_user" and the relevant fields will be updated for the model i call it on.
I am not able to access the Waterline query methods (such as .update()) from the model instance after a model is queried like so:
database.models.users.findAll().where({'id':1).then((user)=>{
user.touch_user();
//user.update // does not work
});
But it is not until I query it that I retrieve the data associated with the record.
I would like user.touch_user() to increment col_a in the users table similar to the following
database.models.users.findAll().where({'id':1).then((user)=>{
user.touch_user();
database.models.users.update(user.id, {'col_a':user.col_a+1});
});
The problem with the above is that the user object does not accurately reflect the update to the table.
What I ended up doing was creating a class method to update the model and the instance. Not exactly what I want, but it does in a pinch to keep the model and table synced
var User = Waterline.Collection.extend({
...some fields...
attributes:{
col_a:{
type:'integer'
},
},
// touch_user is a class method here
touch_user:function(user){
// update model instance
user.col_a += 1;
// update database table
database.models.users.update(user.id, {'col_a':user.col_a).exec(function(){});
}
});
Called like so
database.models.users.findAll().where({'id':1).then((user)=>{
...modify user...
database.models.users.touch_user(user);
...other changes to user that have to occur here...
database.models.users.touch_user(user);
});

Resources