Is there a way to extend (maybe inherit) model to add hooks and fields after model was defined?
So something like this:
User = sequelize.define("user", {
name: sequelize.String
});
makeStateful(User); // adds state,updated,added fields and some hooks
this is not possible at the moment. But you could easily make it work the other way around: Define your mixin before and use that when you define the model:
var Sequelize = require('sequelize')
, sequelize = new Sequelize('sequelize_test', 'root')
var mixin = {
attributes: {
state: Sequelize.STRING,
added_at: Sequelize.DATE
},
options: {
hooks: {
beforeValidate: function(instance, cb) {
console.log('Validating!!!')
cb()
}
}
}
}
var User = sequelize.define(
'Model'
, Sequelize.Utils._.extend({
username: Sequelize.STRING
}, mixin.attributes)
, Sequelize.Utils._.extend({
instanceMethods: {
foo: function() {
return this.username
}
}
}, mixin.options)
)
User.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(u) {
console.log(u.foo()) // 'foo'
})
})
Related
This is my mongoose Setup. This happens only when i use class syntax. When i do the same thing with the use of functional programming it works fine. This is the first time i am using class syntax to do this. I think that's where the problem lies. I am doing something wrong with my class definition.
This is my mongoose Setup. This happens only when i use class syntax. When i do the same thing with the use of functional programming it works fine. This is the first time i am using class syntax to do this. I think that's where the problem lies. I am doing something wrong with my class definition.
const mongooseService = require('./services/mongoose.service')
const slugify = require('slugify')
const { marked } = require('marked')
const createDomPurifier = require('dompurify')
const { JSDOM } = require('jsdom')
const dompurify = createDomPurifier(new JSDOM().window)
class ArticleDao {
Schema = mongooseService.getMongoose().Schema
articleSchema = new this.Schema({
title: {
type: String,
required: true,
},
description: {
type: String,
},
markdown: {
type: String,
required: true,
},
createdAt: {
type: Date,
default: new Date(),
},
slug: {
type: String,
required: true,
unique: String,
},
sanitizedHtml: {
type: String,
required: true,
},
})
Article = mongooseService.getMongoose().model('Article', this.articleSchema)
constructor() {
console.log(`created new instance of DAO`)
this.setPreValidation()
}
setPreValidation() {
console.log('h')
this.articleSchema.pre('save', (next) => {
if (this.title) {
this.slug = slugify(this.title, { lower: true, strict: true })
}
if (this.markdown) {
this.sanitizedHtml = dompurify.sanitize(marked(this.markdown))
}
next()
})
}
async addArticle(articleFields) {
const article = new this.Article(articleFields)
await article.save()
return article
}
async getArticleById(articleId) {
return this.Article.findOne({ _id: articleId }).exec()
}
async getArticleBySlug(articleSlug) {
return this.Article.findOne({ slug: articleSlug })
}
async getArticles() {
return this.Article.find().exec
}
async updateArticleById(articleId, articleFields) {
const existingArticle = await this.Article.findOneAndUpdate({
_id: articleId,
$set: articleFields,
new: true,
}).exec()
return existingArticle
}
async removeArticleById(articleId) {
await this.Article.findOneAndDelete({ _id: articleId }).exec()
}
}
module.exports = new ArticleDao()
This is the error i get:
Article validation failed: sanitizedHtml: Path `sanitizedHtml` is required., slug: Path `slug` is required.
I am trying to retrieve all transactions and include all related stripePayments but get the error include.model.getTableName is not a function.
I have a transactions table that has many stripePayments. This can seen with the following sequelize models:
src/models/transaction.js
module.exports = (sequelize, DataTypes) => {
class Transaction extends Model {
static associate(models) {
this.hasOne(models.StripePayment, {
as: 'StripePayment',
foreignKey: 'stripePaymentId'
});
}
}
Transaction.init(
{
id:{
allowNull: false,
primaryKey: true,
type: DataTypes.UUID
},
stripePaymentId: {
type: DataTypes.UUID,
allowNull: false,
foreignKey: true,
references: {
model: stripePayment,
key: 'id',
},
}
},
{
sequelize,
modelName: 'Transaction',
}
);
return Transaction;
};
src/models/stripePayment.js
module.exports = (sequelize, DataTypes) => {
class StripePayment extends Model {
static associate(models) {
this.belongsTo(models.Transaction, {
as: 'Transaction',
foreignKey: 'stripePaymentId'
});
}
}
StripePayment.init(
{
id:{
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4
}
},
{
sequelize,
modelName: 'StripePayment',
}
);
return StripePayment;
};
I then have an index file that imports all the models and calls the association method like so:
src/models/index.js
const db = fs
.readdirSync(__dirname)
.filter(isModelFile)
.reduce((acc, file) => {
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
acc[model.name] = model;
return acc;
}, {});
Object.keys(db).forEach((modelName) => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
console.log(db[modelName], db[modelName].association())
});
The console log outputs the following:
StripePayment, { Transaction: Transaction }
Transaction, { StripePayment: StripePayment}
which I assume means the association has worked?
However, when running a query like:
await models.Transaction.findAll({
where: { id: "my_id" },
include: [
{
model: 'StripePayment',
as: 'stripePayments'
}
]
});
is when I get the error include.model.getTableName is not a function.
What is causing this to fail?
Have I defined the relationships correctly in the models?
Turns out the issue was with my query. I needed to specify the model instance, not the name of the model as a string:
I.e. I needed to do this:
await models.Transaction.findAll({
where: { id: "my_id" },
include: [
{
model: models.StripePayment,
as: "StripePayment", // match the alias specified in the model
attributes: ['id', 'status'], // need to select attributes
}
]
});
I am trying to fetch data from Table_A and Table_B using node and sequelize
Table Structure
Table_A:
id PK
name Text
Table_B:
id PK
a_id FK_tableA_id
name Text
Model
TableA.js
'use strict';
const DataTypes = require('sequelize').DataTypes;
module.exports = (sequelize) => {
const Table_A = sequelize.define('Table_A', {
id: {
type: DataTypes.UUID,
allowNull: false,
primaryKey: true
},
name: {
type: DataTypes.TEXT,
allowNull: true
}
});
Table_A.associate = models => {
Table_A.belongsTo(models.Table_B, { as: 'tb' });
}
return Table_A;
};
TableB.js
'use strict';
const DataTypes = require('sequelize').DataTypes;
module.exports = (sequelize) => {
const Table_B = sequelize.define('Table_B', {
id: {
type: DataTypes.UUID,
allowNull: false,
primaryKey: true
},
a_id: {
type: DataTypes.UUID,
allowNull: false,
defaultValue: null
},
name: {
type: DataTypes.TEXT,
allowNull: true
}
});
return Table_B;
};
I am getting below error while I am trying to run the query using sequelize, Can you please guide me where I am making the mistake?
Error
EagerLoadingError [SequelizeEagerLoadingError]: Table_B is not associated to Table_A!
at Function._getIncludedAssociation (C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:545:13)
at Function._validateIncludedElement (C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:482:53)
at C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:401:37
at Array.map (<anonymous>)
at Function._validateIncludedElements (C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:397:39)
at Function.aggregate (C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:1204:12)
at Function.count (C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:1252:31)
at async Promise.all (index 0)
at async Function.findAndCountAll (C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:1268:27)
index.js
'use strict';
const { Op } = require('sequelize');
const {sequelize, connect } = require('./db');
const uninitModels = require('./models');
let initModels = uninitModels(sequelize);
initModels = { connection: sequelize, ...initModels }
const {
Table_A, Table_B
} = initModels;
function dbCall(final) {
Table_A.findAndCountAll(final).then((result)=>{
console.log(result)
}).catch((err)=>{
console.log(err)
})
}
function data() {
let final = {
include: [
{
model: Table_B,
attributes: ['id', 'name', 'a_id'],
as: 'tb'
}
]
}
dbCall(final);
}
data();
I suppose you didn't register associations that should be registered by calling associate methods of models.
Also you confused how models are linked. If a model1 has a foreign key field pointing to a model2 then an association should be model1.belongsTo(model2).
In your case it should be:
Table_A.associate = models => {
Table_A.hasMany(models.Table_B, { as: 'tb', foreginKey: 'a_id' });
}
and in the model Table_B:
Table_B.associate = models => {
Table_B.belongsTo(models.Table_A, { as: 'ta', foreginKey: 'a_id' });
}
Pay attention to foreignKey option, you need to indicate it explicitly because your foreign key field is named other than Table_A+id.
I got stuck on this same error (for what seemed like an eternity) and finally realized that there was a big flaw in the way I was declaring my associations.
Incorrect:
Account.associate = function (models) {
Account.hasMany(models.History, {
onDelete: "cascade"
});
};
Account.associate = function (models) {
Account.hasMany(models.User, {
onDelete: "cascade"
});
};
In hindsight, this was a really silly oversight doing two declarations here. tl;dr the 2nd declaration was canceling out the 1st one.
Correct:
Account.associate = function (models) {
Account.hasMany(models.History, {
onDelete: "cascade"
});
Account.hasMany(models.User, {
onDelete: "cascade"
});
};
One declaration with multiple function calls for the win.
This is very basic, and should work.. but doesn't. So first my models:
const Conversation = sequelize.define('Conversation', {
name: {
type: DataTypes.STRING,
allowNull: false
},
...
})
Conversation.associate = (models, options) => {
Conversation.hasOne(models.Audio, options)
}
and:
module.exports = (sequelize /*: sequelize */ , DataTypes /*: DataTypes */ ) => {
const Audio = sequelize.define("Audio", {
name: {
type: DataTypes.STRING,
unique: true,
allowNull: true
},
})
Audio.associate = (models, options) => {
Audio.belongsTo(models.Conversation, options)
}
I have a model loader that does:
fs.readdirSync(`${__dirname}`)
.filter((modelFile) => {
return path.extname(modelFile) === '.js' && modelFile !== 'index.js'
})
.map((modelFile) => {
let model = sequelize.import(`./${modelFile}`)
models[model.name] = model
return model
})
.filter((model) => models[model.name].associate)
.forEach((model) => {
models[model.name].associate(models, {
hooks: true,
onDelete: 'CASCADE'
});
})
So it calls the associate method for all models that have it defined. This works, in that, when I .sync it, it creates a ConversationId field in my Conversations table.
When I try to execute:
let updAudio = {
ConversationId,
name: 'myname'
}
await global.db.models.Audio.create(updAudio, { logging: console.log })
ConversationId is not null, but when it saves in the DB, it's null. I've inspected it a hundred times. The raw query looks like:
INSERT INTO "Audios" ("id","name","createdAt","updatedAt") VALUES (DEFAULT,'myname','2019-10-20 19:59:18.139 +00:00','2019-10-20 19:59:18.139 +00:00') RETURNING *;
So what happened to ConversationId?
You might need to add the foreign key in the model definition:
(One of the two may work)
const Audio = sequelize.define("Audio", {
name: {
type: DataTypes.STRING,
unique: true,
allowNull: true
},
conversationId : {
type: Sequelize.INTEGER,
references: 'Conversations' // or "conversations"? This is a table name
referencesKey: 'id' // the PK column name
}
})
or
const Audio = sequelize.define("Audio", {
name: {
type: DataTypes.STRING,
unique: true,
allowNull: true
},
conversationId : {
type: Sequelize.INTEGER,
references: {
model: Conversation
key: 'id'
}
}
})
You might also need to define your belongsTo association this way:
Audio.belongsTo(Conversation, foreignKey: 'conversationId');
Try to look for the generated raw query on creation of the tables and start from there.
I have two associated models (abbreviated for clarity)
const model = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
fullName: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notEmpty: true,
len: [1,255]
}
}
},
{
classMethods: {
associate: function(models) {
User.hasMany(models.Meeting, {as: 'chairedMeetings', foreignKey: 'chairmanId'})
}
}
}
)
return User
}
and
const model = (sequelize, DataTypes) => {
const Meeting = sequelize.define('Meeting', {
title: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notEmpty: true,
len: [1,255]
}
}
}, {
classMethods: {
associate: function(models) {
Meeting.belongsTo(models.User, {as: 'chairman'})
}
}
}
)
return Meeting
}
module.exports = model
I create a new meeting, given an previously created user with id = 1, as follows:
const data = {
title: 'Some amazing meeting title',
chairmanId: 1
}
Meeting.create(data, {include: [{all: true, nested: true}]}).then(meeting => {
const chairmanId = meeting.get('chairmanId') // ===> this is 1 as expected
const chairman = meeting.get('chairman') // ===> but this is undefined
}
How do I get sequelize to return the associated record without manually having to go through the associations and findOne them by id?
Okay figured it out:
The solution is to do a reload after creation.
Meeting.create(data, {include: [{all: true, nested: true}]}).then(meeting => {
const chairmanId = meeting.get('chairmanId') // ===> this is 1 as expected
meeting.reload({include: [{all: true, nested: true}]}).then(reloadedMeeting => {
const chairman = reloadedMeeting.get('chairman') // ===> is a user!
})
}
I guess sequelize doesn't automatically reload after creation in case you never actually care about the model you just created, and you just want to know it got saved.