Can't exclude association's fields from select statement in sequelize - javascript

I have the following code (simplified):
var group = sequelize.define("group", {
id: {type: DataTypes.INTEGER, autoIncrement: false, primaryKey: true},
name: type: DataTypes.STRING,
parentId: DataTypes.INTEGER
}, { classMethods: {
associate: function (models) {
group.belongsToMany(models.item, { as:'items', foreignKey: 'group_id', through: models.group_item_tie });
}}
});
var group_item_tie = sequelize.define("group_item_tie", {}, {freezeTableName: true});
var item = sequelize.define("item", {
spn: { type: DataTypes.INTEGER, autoIncrement: false, primaryKey: true },
}, { classMethods: {
associate: function (models) {
item.belongsToMany(models.group, { foreignKey: 'spn', through: models.group_item_tie });
}}
});
When I try to return some records with relationships, let's say like this:
dbcontext.group.findAll({
where: { id: 6 },
include: [{
model: dbcontext.item,
as: 'items',
attributes: ['spn']
}]
})
I also get in result the fields from a tie table group_item_tie:
[{
"id": 6,
"name": "abc",
"parentId": 5,
"createdAt": "2015-05-06T15:54:58.000Z",
"updatedAt": "2015-05-06T15:54:58.000Z",
"items": [
{ "spn": 1,
"group_item_tie": {
"createdAt": "2015-05-06 15:54:58.000 +00:00",
"updatedAt": "2015-05-06 15:54:58.000 +00:00",
"group_id": 6,
"spn": 1
}
},
{ "spn": 2,
"group_item_tie": {
"createdAt": "2015-05-06 15:54:58.000 +00:00",
"updatedAt": "2015-05-06 15:54:58.000 +00:00",
"group_id": 6,
"spn": 2
}
},
I see it in generated sql query. How to exclude those from select statement? I've tried a few other things but was not successful.
I hope there is something cleaner then just doing:
delete item.group_item_tie;

I'm going to answer myself as it might be useful to someone in future. So according to #3664, #2974 and #2975 the answer is the following (thanks to mickhansen):
include: [{
model: dbcontext.item,
as: 'items',
attributes: ['spn'],
through: {
attributes: []
}
}]
And soon it will be documented.

I realize this thread is a bit outdated, but since this is high in the Google search results and I struggled to find the answer myself, I thought I'd add this here.
If you're using Model.getAssociatedModel() or Model.$get() (for sequelize-typescript), the current answers listed will not work for this use case. In order to hide the model associations you need to add joinTableAttributes: []
Example:
Model.getAssociatedModel({
joinTableAttributes: []
})
Example:
Model.$get('property', <any>{
joinTableAttributes: []
});
At the time of this post, joinTableAttributes is not included in the sequelize-typescript types hence the <any>

through: {
attributes: []
}
works for me

Related

Sequelize ORM, get count of associated items with additional where conditions

I am new to Sequelize ORM so I am struggling to get through it.
I want to get a list of items and count of associated items by applying filter.
Here are my models
const ActionModel = db.define(
'action',
{
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
...
buttonIndex: { type: Sequelize.INTEGER },
...
},
{
tableName: 'action',
},
)
const PageModel = db.define(
'pages',
{
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
pageTitle: { type: Sequelize.INTEGER },
pageLink: { type: Sequelize.STRING },
},
{
tableName: 'pages',
},
)
PageModel.hasMany(Action, { as: 'actions', onDelete: 'CASCADE'})
So If I get a list of pages, it will look like this
[
{
"id": 1,
"pageTitle": "Python community",
"pageLink": "https://fairy-dev.io/python",
}
]
The actions are associated with pages as they happen on the pages.
I want to add two more fields to the response.
visitCount and buttonClickCount
visitCount is total number of actions on the page where buttonIndex == 0
buttonClickCount is total number of actions on the page where buttonIndex != 0
So the result will look like this
[
{
"id": 1,
"pageTitle": "Python community",
"pageLink": "https://fairy-dev.io/python",
"visitCount": 5,
"buttonClickCount": 24
},
{
"id": 2,
"pageTitle": "Sequelize community",
"pageLink": "https://fairy-dev.io/sequelize",
"visitCount": 7,
"buttonClickCount": 57
}
]
I know I should use attributes and include but not sure what the exact answer is.
Any help would be appreciated.
Thanks in advance.

Virtual populate mongoose

I've tried to use virtual populate between two models I created:
in this case to get all the reviews with the tour id and show them with the tour.
(when using query findById() to show only this tour)
my virtual is set to true in the Schema (I've tried to set them to true after using the virtual populate but it doesn't work - by this soultion)
after checking the mongoose documentation its seems to be right but it doesn't work.
my tourSchema:
const tourSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'A tour must have a name'], //validator
unique: true,
trim: true,
maxlength: [40, 'A tour name must have less or equal then 40 characters'],
minlength: [10, 'A tour name must have at least 10 character']
//validate: [validator.isAlpha, 'A tour name must have only alphabetic characters']
},
etc...
etc...
etc...
//guides: Array --- array of user id's || embedding
guides: [
//Reference to the user data model without saving the guides in the tour data model
//Child referencing
{
type: mongoose.Schema.ObjectId,
ref: 'User'
}
]
},
{
//passing options, getting the virual properties to the document/object
toJSON: { virtuals: true },
toObject: { virtuals: true }
}
);
//Define virtual properties
tourSchema.virtual('durationWeeks').get(function () {
console.log('Virtual 1');
//using function declaration => using this keyword
return this.duration / 7;
});
//Virtual populate
tourSchema.virtual('reviews', {
ref: 'review',
localField: '_id', // Find tour where `localField`
foreignField: 'tour', // is equal to `foreignField`
//look for the _id of the tour in the tour field in review
});
my reviewSchema:
** I used in the review schema in for the tour and user populate for the tour id **
const reviewSchema = new mongoose.Schema({
review: {
type: String,
required: [true, 'Review can not be empty!']
},
rating: {
type: Number,
min: 1,
max: 5
},
createdAt: {
type: Date,
default: Date.now(),
},
tour: [
{
type: mongoose.Schema.ObjectId,
ref: 'Tour',
required: [true, 'Review must be belong to a tour.']
}
],
user: [
{
type: mongoose.Schema.ObjectId,
ref: 'User',
required: [true, 'Review must be belong to a user.']
}
]
},
{
//passing options, getting the virual properties to the document/object
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
//Query middleware
reviewSchema.pre(/^find/, function (next) {
this.populate({
path: 'tour',
select: 'name'
})
.populate({
path: 'user',
select: 'name'
});
next();
});
My output:
get all reviews (review model data):
{
"status": "success",
"data": {
"review": [
{
"_id": "5f6ba5b45624454efca7e0b1",
"review": "What an amzing tour",
"tour": {
"guides": [],
"_id": "5c88fa8cf4afda39709c2955",
"name": "The Sea Explorer",
"durationWeeks": null,
"id": "5c88fa8cf4afda39709c2955"
},
"user": {
"_id": "5f69f736e6eb324decbc3a52",
"name": "Liav"
},
"createdAt": "2020-09-23T19:44:52.519Z",
"id": "5f6ba5b45624454efca7e0b1"
}
]
}
}
and the get tour by id:
{
"status": "success",
"data": {
"tour": {
"startLocation": {
"type": "Point",
"coordinates": [
-80.185942,
25.774772
],
"description": "Miami, USA",
"address": "301 Biscayne Blvd, Miami, FL 33132, USA"
},
"ratingsAverage": 4.8,
"ratingsQuantaity": 0,
"images": [
"tour-2-1.jpg",
"tour-2-2.jpg",
"tour-2-3.jpg"
],
"startDates": [
"2021-06-19T09:00:00.000Z",
"2021-07-20T09:00:00.000Z",
"2021-08-18T09:00:00.000Z"
],
"secretTour": false,
"guides": [],
"_id": "5c88fa8cf4afda39709c2955",
.
.
.
.
"slug": "the-sea-explorer",
"__v": 0,
"durationWeeks": 1,
"id": "5c88fa8cf4afda39709c2955"
}
}
}
as you can see the review has the tour as an arr and the id is inside the arr of the tour is there an option that the populate is not targeting the right field?
You need an option virtuals: true passed into the schema creation:
const tourSchema = new mongoose.Schema({
...
}, {
virtuals: true
}
In addition, we use the mongoose-lean-virtuals module to help with .lean and virtuals. e.g.
const mongooseLeanVirtuals = require('mongoose-lean-virtuals');
...
tourSchema.plugin(mongooseLeanVirtuals);
tourSchema.set('toJSON', { virtuals: true });
tourSchema.set('toObject', { virtuals: true });
though I'm guessing that's not strictly necessary.
So I figure it out.
First i asked in github - mongoose repo and got answerd:
reviewSchema.pre(/^find/, function (next) {
this.populate({
path: 'tour',
options: { select: 'name' } // <-- wrap `select` in `options` here...
}).populate({
path: 'user',
options: { select: 'name photo' } // <-- and here
});
next();
});
We should improve this: nested options are very confusing and it's
hard to remember whether something should be options.select or select
The second issue was to add populate after using the FindById method in the tour controller, using the populate without using the wrap 'select' didn't work for me.
exports.getTour = catchAsync(async (req, res, next) => { //parameter => :id || optinal parameter => :id?
//populate reference to the guides in the user data model
const tour = await Tour.findById(req.params.id).populate('reviews');
if (!tour) {
return next(new AppError('No tour found with that id', 404));
}
res.status(200).json({
status: 'success',
data: {
tour
}
});
})
and in the tour model, I changed the foreign key from "tour_id" (as I saw in other questions to "tour").
//Virtual populate
tourSchema.virtual('reviews', {
ref: 'Review',
localField: '_id', // Find tour where `localField`
foreignField: 'tour' // is equal to `foreignField`
//look for the _id of the tour in the tour field in review
});
Now i do have reviews in my tour data and it does virtual populate to the tour by id

Sequelize adding many-to-many relation with additional data not working

I have the following models defined:
var Order = sequalize.define(
"order",
{
id: {
type: Sequelize.STRING,
primaryKey: true,
},
menuId: {
type: Sequelize.UUID,
field: "menu_id",
},
},
{
timestamps: false,
}
);
Item.belongsToMany(Order, { through: OrderItem });
Order.belongsToMany(Item, { through: OrderItem });
and
var OrderItem = sequalize.define(
"order_item",
{
orderId: {
type: Sequelize.UUID,
field: "order_id",
},
itemId: {
type: Sequelize.UUID,
field: "item_id",
},
count: {
type: Sequelize.INTEGER,
field: "count",
},
},
{
timestamps: false,
freezeTableName: true,
}
);
I am trying to figure out how to add a order with items without creating items but just adding them to the relationship.
I have this initial format for the order:
{
"id": "som-other-id7",
"items": [{"id": "727f9b52-a88b-4ec3-a68c-98d190564497", "count": 2}, {"id": "7dfd30e7-2d4a-4b16-ae3d-20a330d9b438"}],
"menuId": "7715af03-968f-40e5-9eb2-98016f3deeca"
}
and I try to add it to the db in the following way:
Order.create(orderJson)
.then((order) =>
orderJson.items.map((item) => order.addItem(item.id, { count: item.count }))
)
However the count is not populated. I tried:
using setItem instead of addItem
instead of passing item.id passing {itemId, orderId}
You should call addItem like this:
order.addItem(item.id, { through: { count: item.count }})
See an example in BelongsToMany section

Sequelize.js includes returning incorrect data

I'm making a sample donations app and I want to get all of the donations a user has made, as well as the Causes that donation is connected too.
exports.getUserDonations = (req, res) => {
// Get Donations made by the user by userID
Donation.findAll({
where: {
userID: req.params.id,
},
attributes: ['amount', 'updatedAt', 'causeID'],
include: [{
model: Cause,
as: 'Causes',
}]
})
.then(donations => {
if (donations) {
res.status(200).json(donations);
} else {
res.status(404).send({ error: "No Donations found" });
}
})
.catch(err => {
console.log("Error: ", err)
res.status(500).json(err);
});
}
For some reason I get this kind of result. As you can see the "causeID"(12) and the "Cause.id"(17) fields do not match. My thought is that the where statement above might be overriding something.
{
"amount": 82,
"updatedAt": "2018-11-02T17:04:30.847Z",
"causeID": 12,
"Causes": {
"id": 17,
"userID": 1,
"orgID": null,
"name": "Croation Trip",
"type": "Trip",
"amount": 3000,
"description": "Just taking a trip",
"purpose": "Food, sun, and fun",
"createdAt": "2018-09-20T21:56:37.330Z",
"updatedAt": "2018-09-20T21:56:37.330Z"
}
},
// Most fo the time the cause comes back as null
{
"amount": 10,
"updatedAt": "2018-11-27T01:35:06.061Z",
"causeID": 13,
"Causes": null
},
Here are my associations for each of those
// Cause association
Cause.associate = function(models) {
Cause.belongsTo(models.User, {
as: "Users",
foreignKey: "id"
})
Cause.hasMany(models.Donation, {
as: "Donations",
foreignKey: "causeID"
})
};
// Donation associations
Donation.associate = function(models) {
Donation.belongsTo(models.Cause, {
as: "Causes",
foreignKey: "id"
})
Donation.belongsTo(models.User, {
as: "Users",
foreignKey: "id"
})
};
Looks like you are mismatching some keys. I think your example is joining Donation.Id to Cause.Id - you should confirm by looking at the generated SQL. Here's how to associate keys with different names:
Donation.belongsTo(models.Cause, {
as: "Causes",
{foreignKey: "id", sourceKey: "causeId"}
})
Or test with more Donation attributes:
attributes: ['id', 'userId', 'amount', 'updatedAt', 'causeID'],
Does the donation id match the cause id?

How to Avoid Sequelize JS Associations Including both the Foreign Key and the Included Object

How can I avoid showing both the foreignKey that sequelize creates and the eagerly fetched object through includes?
I have the following model structure:
FormEntry:
owner: User
createdBy: User
modifiedBy: User
formEntryData: [FormEntryData]
I modeled it after reading through SequelizeJS docs and came up with the following:
const User = sequelize.define('user', {
id: {
type: Sequelize.BIGINT(20),
field: 'user_id',
primaryKey: true
},
emailAddress: {
type: Sequelize.STRING(256),
field: 'email_address'
}
}, {
tableName: 'users',
timestamps: false
});
const FormEntryData = sequelize.define('formEntryData', {
id: {
type: Sequelize.BIGINT(20),
field: 'id',
primaryKey: true
},
entryId: {
type: Sequelize.BIGINT(20),
field: 'entry_id'
},
...
}, {
tableName: 'formEntryData',
timestamps: false
});
const FormEntry = sequelize.define('formEntry', {
id: {
type: Sequelize.BIGINT(20),
field: 'entry_id',
primaryKey: true
},
...
}, {
tableName: 'formEntries',
timestamps: false
});
I then need to create the associations to tie the models together and after a lot of trial and error I came up with the following:
FormEntry.hasMany(FormEntryData, {foreignKey: 'entry_id', as: 'FormEntryData'});
FormEntry.belongsTo(User, {foreignKey: 'created_by', as: 'CreatedBy'});
FormEntry.belongsTo(User, {foreignKey: 'modified_by', as: 'ModifiedBy'});
FormEntry.belongsTo(User, {foreignKey: 'owner', as: 'Owner'});
I then was able to query the data by doing the following:
FormEntry.findByPrimary(1472280, {
include: [
{
model: FormEntryData,
as: "FormEntryData"
},
{
model: User,
as: "CreatedBy"
},
{
model: User,
as: "Owner"
},
{
model: User,
as: "ModifiedBy"
}
]
})
Unfortunately, my results seem kind of repetitive as it seems to be including both the foreign key and the object that is eagerly fetched.
{
"id": 1472280,
...
"created_by": 26508, <-- repetitive (don't want this)
"modified_by": 26508, <-- repetitive (don't want this)
"owner": null, <-- repetitive (don't want this)
"FormEntryData": [
{
"id": 27164476,
"entryId": 1472280, <-- repetitive (but I want this one)
...
"entry_id": 1472280 <-- repetitive (don't want this)
},
...
],
"CreatedBy": { <-- repetitive (but I want this one)
"id": 26508,
"emailAddress": "swaraj.kler#greywallsoftware.com"
},
"Owner": null, <-- repetitive (but I want this one)
"ModifiedBy": { <-- repetitive (but I want this one)
"id": 26508,
"emailAddress": "swaraj.kler#greywallsoftware.com"
}
}
You need to exclude specified fields from the query
FormEntry.findByPrimary(1472280, {
include: [
{
model: FormEntryData,
as: "FormEntryData",
attributes: { exclude: ['entry_id'] }
},
{
model: User,
as: "CreatedBy"
},
{
model: User,
as: "Owner"
},
{
model: User,
as: "ModifiedBy"
}
],
attributes: { exclude: ['owner', 'created_by', 'modified_by'] }
})

Categories