I have a sequelize model called Activity, that belongs to another model called User.
I want to be able to generate a dynamic property on the Activity model, so I did the following:
class Activity extends Sequelize.Model {
toJSON() {
return {
...this.dataValues,
my_property: 'its value', // Dynamic property
};
}
}
This works quite fine when I query the Activity itself (or get them all), but the property is not there when I query a User with its activities e.g.
const user = await User.findByPk('some-pk-here', { include: [Activity] });
Can this behavior be changed, and if yes then how?
Related
I have an interface created for my model, where I only want to return specific data from the record
// code.interface.ts
import { Document } from 'mongoose';
export interface CodeI extends Document {
readonly _id: string;
readonly logs: any;
}
But when I get the result from mongo, it completely ignores what is in my interface. (I am using NestJs as framework)
//constructor
constructor(#InjectModel(Coupon.name) private couponModel: Model<CouponDocument>) {}
// function
async findOne(codeId: string): Promise<CodeI> {
const coupon = await this.couponModel.findOne({ _id: codeId }).exec();
if (!coupon) {
throw new NotFoundException([`#${codeId} not found`]);
}
return coupon;
}
TypeScript interfaces don't work this way. They can't limit the fields of an object because they don't exist at runtime, so, we can't use them to guide any runtime behavior. TypeScript interfaces are useful for compile-time type check only.
However, in your case, there are two ways you can achieve the expected behavior.
The first one is to select only the required fields which you need to return (recommended).
In your findOne, you can do something like this
async findOne(codeId: string): Promise<CodeI> {
const coupon = await this.couponModel.findOne({ _id: codeId }, '_id logs').exec();
if (!coupon) {
throw new NotFoundException([`#${codeId} not found`]);
}
return coupon;
}
Here, as you can see, I have passed an additional string type parameter to findOne function which is projection and it will select only the specified fields from the object. This will not only solve your problem but also save query time and have increase query performance. Read more about findOne here.
The other way is to create a DTO where you can define the fields you want to return from the function.
Something like this:
// CouponDto.ts
class CouponDto {
public readonly _id: string;
public readonly logs: any;
constructor(data: CodeI) {
this._id = data._id;
this.logs = data.logs;
}
}
Then, in your service file, you can do something like
return new CouponDto(coupon);
(make sure to change the return type of the function to CouponDto as well)
You can use any of these two ways. While I would recommend going with the first one, it's up to you and how you wanna structure your project.
External Links:
Mongoose FindOne Docs
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"
}
}
*/
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
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);
});
While different from a previous question I asked here, it's related and so wanted to link it.
I have been trying hard to find out how I can get the model name (identity) or model "class" (exposed in sails.models) of a record. So, given a waterline record, how can I find out its model name or class?
Example (of course here I know the model is User but that is an example):
User.findOne(1).exec(function(err, record) {
// at this point think that we don't know it's a `user` record
// we just know it's some record of any kind
// and I want to make some helper so that:
getTheModelSomehow(record);
// which would return either a string 'user' or the `User` pseudo-class object
});
I have tried to access it with record.constructor but that is not User, and I couldn't find any property on record exposing either the model's pseudo-class object or the record's model name.
UPDATE:
To clarify, I want a function to which I'll give ANY record, and which would return the model of that record as either the model name or the model pseudo-class object as in sails.models namespace.
modelForRecord(record) // => 'user' (or whatever string being the name of the record's model)
or
modelForRecord(record) // => User (or whatever record's model)
WOW, ok after hours of research, here is how I am doing it for those who are interested (it's a very tricky hack, but for now unable to find another way of doing):
Let's say record is what you get from a findOne, create, ... in the callback, to find out what instance it is, and so find the name of the model owning the record, you have to loop over all your models (sails.models.*) and make an instanceof call this way:
function modelFor(record) {
var model;
for (var key in sails.models) {
model = sails.models[key];
if ( record instanceof model._model.__bindData__[0] ) {
break;
}
model = undefined;
}
return model;
}
Do not try to simply do instanceof model, that does not work
After if you need the model name simply modelFor(record).globalId to get it.
In your model definition why not just create a model attribute. Then the model will be returned with every record call. This will work even after the record become a JSON object.
module.exports = {
attributes : {
model : {type:'string',default:'User'}
}
}
Use this example for parsing model name
var parseModel = function(request) {
request = request.toLowerCase();
return sails.models[request];
};
and use in controller this code for using this model
parseModel(req.param('modelname')).find().then().catch();
Sails exposes everything in the request object. Try grabbing the name of the model this way:
var model = req.options.model || req.options.controller;
That will give you the raw name. To use it, you'll have to plug the model into the sails models array.
var Model = req._sails.models[model];
Check out the source code to see it in action. (https://github.com/balderdashy/sails/blob/master/lib/hooks/blueprints/actionUtil.js#L259)