TypeORM why is my relationship column undefined? foreign-key is undefined - foreign-keys

I just use TypeORM and find the relationship column is undefined
#Entity({name: 'person'})
export class Person {
#PrimaryGeneratedColumn('uuid')
id!: string;
#OneToOne( () => User)
#JoinColumn()
user!: User;
#Column({
type: "enum",
enum: PersonTitle,
default: PersonTitle.Blank
})
title?: string;
#Column({type: 'varchar', default: ''})
first_name!: string;
#Column('varchar')
last_name!: string;
#ManyToOne(() => Organization, org => org.people, { nullable: true})
belong_organization!: Organization;
and I also have Organization entity:
export class Organization {
#PrimaryGeneratedColumn('uuid')
id!: string;
...
}
when I use Repository like:
const db = await getDatabaseConnection()
const prep = db.getRepository<Person>('person')
presult = await prep.findOne({where: {id}})
console.log(result)
my result is:
Person {
id: '75c37eb9-1d88-4d0c-a927-1f9e3d909aef',
user: undefined,
title: 'Mr.',
first_name: 'ss',
last_name: 'ls',
belong_organization: undefined, // I just want to know why is undefined? even I can find in database the column
expertise: [],
introduction: 'input introduction',
COVID_19: false,
contact: undefined
}
the database table like:
"id" "title" "first_name" "last_name" "expertise" "COVID_19" "userId" "belongOrganizationId" "introduction"
"75c37eb9-1d88-4d0c-a927-1f9e3d909aef" "Mr." "test" "tester" "nothing" "0" "be426167-f471-4092-80dc-7aef67f13bac" "8fc50c9e-b598-483e-a00b-1d401c1b3d61" "input introduction"
I want to show organization id, how typeORM do it? Foreign-Key is present undefined?

You need to either lazy load the relation or you need to specify the relation in the find
Lazy:
#Entity({name: 'person'})
class Person {
...
#ManyToOne(() => Organization, org => org.people, { nullable: true})
belong_organization!: Organization;
...
}
...
async logOrganization() {
const db = await getDatabaseConnection()
const prep = db.getRepository<Person>('person')
presult = await prep.findOne({where: {id}})
console.log(await result.belong_organization)
}
Find
const prep = db.getRepository<Person>('person')
presult = await prep.findOne({
where: { id },
relations: ["belong_organization"]
})
You could also always do an eager load, but i'd advise against this since then it would always do the join when it fetches a person.
If you want to query the belong_organizationId you need to add its field to the person entity. This field is usual something like belongOrganizationId
That would make
#Entity({name: 'person'})
class Person {
...
#Column()
belongOrganizationId:number
#ManyToOne(() => Organization, org => org.people, { nullable: true})
belong_organization!: Organization;
...
}
This would make it possible to query for its id too.
You could also query it more directly but this leaves you with some pretty ugly and unmaintainable code:
const findOptions: {
where :{
id,
'belong_organization.id': belong_organizationId
}
}

Related

Stubbing other repository calls in current repository test

I am trying to write test cases for an Node.js backend project
The database is using pg-promise. I run into issue when trying to stub the repository and it tries to call other repositories.
Here is the repository file user.js
async findByRefToken(refToken) {
return await this.db.oneOrNone(`SELECT id FROM useraccount WHERE username = $1`, refToken);
}
async createNormal(data) {
// generate a verify email token
const verifyToken = await this.generateToken();
// create unique ref code
const tokenid = nanoid(Number(env.REF_TOKEN_LENGTH));
const refToken = await this.generateUniqueRefToken(tokenid);
// find the referral's id from the ref token
let referredBy = null;
if(data.referred) {
referredBy = await this.findByRefToken(data.referred);
}
const meta = {
dob: data.dob,
gender: data.gender,
country: data.country,
phone: data.phone,
email_verified: false,
verify_token: verifyToken,
ref_token: refToken,
referred: referredBy ? referredBy.id : null
};
const verificationMeta = {
kyc_verified: false
};
const normalUser = {
id: data.userId,
first_name: data.firstName.trim(),
last_name: data.lastName.trim(),
active: true,
email: data.email.trim().toLowerCase(),
username: data.username.trim(),
role: 'Normal',
password: bcrypt.hashSync(data.password, Number(env.SALT_ROUNDS)),
meta: meta,
verification_meta: verificationMeta
};
const newUser = await this.db.one(CREATE_NORMAL_USER, normalUser);
return {
id: newUser.id,
firstName: data.firstName,
email: data.email,
verifyToken: verifyToken,
refToken: refToken,
referredBy: referredBy
};
}
I am trying to test the createNormal function, but it would also call other repo function like findByRefToken and await this.db.one(CREATE_NORMAL_USER, normalUser); Is there anyway to stub them away?
And here is the test written user.test.js
const chai = require("chai");
const sinon = require("sinon");
const expect = chai.expect;
const {faker} = require("#faker-js/faker");
const UserRepository = require("../../repos/user");
describe("UserRepository", function() {
const stubValue = {
dob: faker.date.birthdate(),
gender: faker.name.gender(),
country: faker.address.country(),
phone: faker.phone.number(),
email_verified: false,
id: faker.datatype.uuid(),
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
username: faker.name.fullName(),
password: faker.random.alphaNumeric(5),
email: faker.internet.email(),
verifyToken: faker.random.alphaNumeric(12),
refToken: faker.random.alphaNumeric(12),
referredBy: faker.random.alphaNumeric(12)
};
describe("create", function () {
it("should add a new user to the db", async function () {
// const stub = sinon.stub(UserRepository, "createNormal").resolves(stubValue.refToken);
const userRepository = new UserRepository();
const user = await userRepository.createNormal(stubValue);
expect(user.id).to.equal(stubValue.id);
expect(user.name).to.equal(stubValue.name);
expect(user.phone).to.equal(stubValue.phone);
expect(user.id).to.equal(stubValue.id);
expect(user.verifyToken).to.equal(stubValue.verifyToken);
});
});
});
Thank you for responding
The method creates and saves a new user in the database. What is left to test if you stub the database calls?
My recommendation would be to not mock; set up the appropriate structures in the database and use them. You can use factory-bot to make this simpler.
Note: it's strange that it doesn't return a user.
I'd extract making the user from saving the user. I'd also put all the token and referral stuff into their own methods.
async newNormal(data) {
const verifyToken = await this.generateToken();
// create unique ref code
const tokenid = this.generateTokenId();
const refToken = await this.generateUniqueRefToken(tokenid);
const referredBy = await this.findByRefToken(data.referred);
const meta = {
dob: data.dob,
gender: data.gender,
country: data.country,
phone: data.phone,
email_verified: false,
verify_token: verifyToken,
ref_token: refToken,
referred: referredBy ? referredBy.id : null
};
return {
id: data.userId,
first_name: data.firstName.trim(),
last_name: data.lastName.trim(),
active: true,
email: data.email.trim().toLowerCase(),
username: data.username.trim(),
role: 'Normal',
password: bcrypt.hashSync(data.password, Number(env.SALT_ROUNDS)),
meta: meta,
verification_meta: {
kyc_verified: false
};
};
}
async createNormal(data) {
const userData = await this.newNormal(data);
return await this.db.one(CREATE_NORMAL_USER, userData);
}
Now createNormal is an integration method. All it does is call newNormal and pass the result through to a SQL query. You can test it by mocking newNormal and db.one.
The important work is happening in newNormal, focus testing on that. You can mock generateToken, generateTokenId, generateUniqueRefToken and findByRefToken. But, again, this test would be easier and more realistic by just setting up the necessary data.

convert mongoose schema to django model

I have below javascript schema
const mongoose = require('mongoose');
const pointSchema = new mongoose.Schema({
timestamp: Number,
coords: {
latitude: Number,
longitude: Number,
altitude: Number,
accuracy: Number,
heading: Number,
speed: Number
}
});
const trackSchema = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
name: {
type: String,
default: ''
},
locations: [pointSchema]
});
mongoose.model('Track', trackSchema);
I'm trying to convert above file to Django models. wondering how to write that locations: [pointSchema] and const pointSchema in my models.py.
Is it possible to convert that ?
this is how express server saves the data. I want to achieve same
router.post('/tracks', async (req, res) => {
const { name, locations } = req.body;
if (!name || !locations) {
return res
.status(422)
.send({ error: 'You must provide a name and locations' });
}
try {
const track = new Track({ name, locations, userId: req.user._id });
await track.save();
res.send(track);
} catch (err) {
res.status(422).send({ error: err.message });
}
});
You will be using a Many-to-one relationship depicted as a ForeignKey for the first part. Taking the docs as a basis here, it would look like this:
from django.db import models
class Coords(models.Model):
latitude = models.FloatField(...)
longitude = models.FloatField(...)
...
class PointSchema(models.Model):
timestamp = models.DateTimeField(...)
coords = models.ForeignKey(Coords, on_delete=models.CASCADE)
And for trackSchema, we use a ManyToManyField. Short excerpt:
class TrackSchema(models.Model):
...
locations = models.ManyToManyField(PointSchema)
...

Set Up Many to Many relations in Loopback 4

I'm setting up many to many relations in my loopback 4 application. Currently I am using this answer as a guide but after the repository and controller creation I don't know how to continue.
Currently I have three tables for the relation: Course, Language, and LanguageCourse. This means a Course can have many languages and a Language can belong to many courses.
My language-course.model.ts looks like this:
import {Course} from './course.model';
import {Language} from './language.model';
#model({settings: {}})
export class LanguageCourse extends Entity {
#property({
type: 'number',
id: true,
})
id?: number;
#belongsTo(() => Course)
courseId?: number;
#belongsTo(() => Language)
languageId?: number;
constructor(data?: Partial<LanguageCourse>) {
super(data);
}
}
export interface LanguageCourseRelations {
// describe navigational properties here
}
export type LanguageCourseWithRelations = LanguageCourse &
LanguageCourseRelations;
My course.model.ts looks like this (I have already set up a one to many relation in this model):
import {User, UserWithRelations} from './user.model';
#model({settings: {}})
export class Course extends Entity {
#property({
type: 'number',
id: true,
})
id?: number;
#property({
type: 'string',
})
name?: string;
#property({
type: 'string',
})
description?: string;
#belongsTo(() => User)
userId?: number;
#property({
type: 'string',
default: 'active',
})
state?: string;
#property({
type: 'string',
default: 'rookie',
})
level?: string;
#property({
type: 'string',
})
course_photo?: string;
constructor(data?: Partial<Course>) {
super(data);
}
}
export interface CourseRelations {
user?: UserWithRelations;
}
export type CourseWithRelations = Course & CourseRelations;
And my language.model.ts looks like this:
#model({settings: {}})
export class Language extends Entity {
#property({
type: 'number',
id: true,
})
id?: number;
#property({
type: 'string',
})
name?: string;
constructor(data?: Partial<Language>) {
super(data);
}
}
export interface LanguageRelations {
// describe navigational properties here
}
export type LanguageWithRelations = Language & LanguageRelations;
I would like to do a GET request to, for example /courses/{id} endpoint (and /courses as well) and have in the response all the languages that course has but I don't know how to make it work. Also I would like this to work in /languages endpoint.
Thanks for your time!
Unfortunately, there is no proper default many to many relations defined in loopback 4 documentation.
But it seems like you have already tried implementing to create a bridging connecting model language-course.model.ts which is good. Now you have to create the repository and controller for this model. Once that done you can write your own custom logic which will handle storing and retrieving of the data.
For example,
As you said you wish to query and fetch all list of all the languages based on a courses ID, your CourseLanguage model should look something like
import {Course} from './course.model';
import {Language} from './language.model';
#model({settings: {}})
export class LanguageCourse extends Entity {
#property({
type: 'number',
id: true,
})
id?: number;
#property({
type: 'string',
})
courses: string;
#property({
type: 'array',
itemType: 'string',
required:true
})
languages: string[];
constructor(data?: Partial<LanguageCourse>) {
super(data);
}
}
export interface LanguageCourseRelations {
// describe navigational properties here
}
export type LanguageCourseWithRelations = LanguageCourse &
LanguageCourseRelations;
What we are doing above is we are creating two new properties courses which will be the ID of the course and languages which will be an array holding all the languages for that course. Once this is done.
Next, you will have to work on the controller for this model
Follow these following steps for GET all languages based on course ID API
Create a custom controller with route /course/{id} as mentioned by you.
Inside that controller, write your custom code as follows:
#get('/course/{id}', {
responses: {
'204': {
description: 'Fetching the list of all the languages as per course ID',
},
},
})
async fetchListOfLanguagesAsPerCourseId(#param.path.string('id') id: string) {
/**
* Search for the course in `CourseLanguage` collection or table by using the course id
**/
let searchedCourseLanguageObject = await this.courseLanguageRepository.findOne({where: {courses: id}});
/**
* If the course does not exists, throw an error
**/
if(searchedCourseLanguageObject === null || undefined) {
throw new HttpErrors.NotFound("Course does not have any languages")
}
/**
* If the course exists, it will have array languages with all the IDs of the languages from LAnguages collection or table in the database related to the course
**/
let arrayOfLanguagesId = searchedCourseLanguageObject.languages;
/**
* Now go to the language table and fetch all the languages object whose IDs are present in the above array
**/
let listOfLanguages = await this.languagesRepository.find({where: {id: { inq: arrayOfLanguagesId }}});
/**
* Above code will look for all the languages ID in the languages table and return back objects of the language whose ID exist in the array arrayOfLanguagesId
**/
return listOfLanguages
}
Hopefully, this is not too intimidating, when you will implement it, it is easy. Write to me if you will still face any problem.
Thanks
Hopefully this helps.

Problem with setting a value in foreign key column

I'm using Sequelize.js with SQLite-database and faced a question with setting a value for foreign key. I have the following code:
const MessageModel = sequelize.define('MessageModel ', {
uuid: DataTypes.STRING,
authorId: DataTypes.STRING,
// ... other props
}, {});
const TodoModel = sequelize.define('TodoModel', {
ownerId: DataTypes.STRING,
status: {
type: DataTypes.STRING,
defaultValue: 'pending'
}
}, {});
TodoModel.belongsTo(MessageModel , {
foreignKey: {
name: 'messageId',
field: 'messageId',
allowNull: false
},
targetKey: 'uuid'
});
MessageModel.create({
uuid: 'testUUIDForExample'
// other props
}).then(message => {
console.log(`Message's created successful`);
TodoModel.create({
ownerId: 'id-string',
status: 'test-status',
messageId: 'testUUIDForExample'
})
})
Sequelize creates MessageModel-row in DB, but it falls when it's trying to generate TodoModel with this err:
DatabaseError: SQLITE_ERROR: foreign key mismatch - "TodoModel" referencing "MessageModel "
at Query.formatError (C:\Users\lrsvo\web-development\projects\platoon-web-electron\node_modules\sequelize\lib\dialects\sqlite\query.js:432:16)
at Query._handleQueryResponse (C:\Users\lrsvo\web-development\projects\platoon-web-electron\node_modules\sequelize\lib\dialects\sqlite\query.js:77:18)
at afterExecute (C:\Users\lrsvo\web-development\projects\platoon-web-electron\node_modules\sequelize\lib\dialects\sqlite\query.js:260:31)
at Statement.errBack (C:\Users\lrsvo\web-development\projects\platoon-web-electron\node_modules\sqlite3\lib\sqlite3.js:16:21)
Err.original.message: "SQLITE_ERROR: foreign key mismatch - "TodoModel" referencing "MessageModel"
Generated SQL:
"INSERT INTO `TodoModel` (`id`,`ownerId`,`status`,`createdAt`,`updatedAt`,`messageId`) VALUES (NULL,$1,$2,$3,$4,$5);"
My TodoModel table looks like:
CREATE TABLE "TodoModel" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"ownerId" VARCHAR(255),
"status" TEXT DEFAULT 'pending',
"createdAt" DATETIME NOT NULL,
"updatedAt" DATETIME NOT NULL,
"messageId" VARCHAR(255) NOT NULL,
FOREIGN KEY("messageId") REFERENCES "MessageModel"("uuid") ON DELETE NO ACTION ON UPDATE CASCADE
);
I can't get why is the err occurs and need help, cause I'm dummy in this ORM.
I'm using "sequelize": "^5.1.0" with SQLite.
MyConfig file:
const Sequelize = require("sequelize");
const electron = require('electron');
const storagePath = electron.app.getPath('userData') + '/plt.db';
module.exports = {
development: {
dialect: "sqlite",
storage: storagePath,
username: null,
password: null,
operatorsAliases: Sequelize.Op,
define: { freezeTableName: true },
query: { raw: true }, // Always get raw result
logging: true,
},
};
There are a copuple of things here. First If you are going to use uuid on MessageModel as primary key, you have to define it, otherwise you'll have a default id field.
const MessageModel = sequelize.define('MessageModel ', {
uuid:{ // if this is your primary key you have to define it
type: DataTypes.STRING, //there is also DataTypes.UUID
allowNull: false,
primaryKey: true,
unique: true
},
authorId: DataTypes.STRING,
// ... other props
}, {});
Then on your TodoModel, you are setting the messageId association as integer. To change it to string, you have to define the field on the model, and on the association use it as a foreign key.
const TodoModel = sequelize.define('TodoModel', {
ownerId: DataTypes.STRING,
status: {
type: DataTypes.STRING,
defaultValue: 'pending'
},
messageId: { //you also have to add the field on your model and set it as STRING, because on the association Sequelize by default is going to use INTEGER
type: DataTypes.STRING,
allowNull: false
}
}, {});
TodoModel.belongsTo(MessageModel , {
as: 'Message',
foreignKey: 'messageId', // and you only set the foreignKey - Same name as your field above
});

Foreign Key with Sequelize not working as expected

I was trying to create an association between two tables and I wanted to add a foreign key.
The two models are User and Companies
User.associate = (models) => {
User.belongsTo(models.Companies, { foreignKey: 'Company' });
};
My expectation of the code above was that a Company ID field gets added in the user table which references the Company ID of the Companies table.
On running the code above, I don't see any additional columns getting created. I tried checking if a foreign key association is created in the DB and that also is missing.
However, if I try to add a column with the same name while keeping the association code, I get a name conflict. This seems to suggest that the association is getting created but I am unable to see it.
Could someone help me understand what I am doing wrong? Thanks for the help!
models/company.js
module.exports = (sequelize, DataTypes) => {
var Company = sequelize.define('company', {
company: { type: DataTypes.STRING, primaryKey: true },
});
Company.associate = (models) => {
Company.hasMany(models.user, { as: 'users' });
};
Company.sync();
return Company;
};
models/user.js
const uuid = require('uuid/v4');
'use strict';
module.exports = (sequelize, DataTypes) => {
var User = sequelize.define('user', {
id: { type: DataTypes.UUID, primaryKey: true },
name: { type: DataTypes.STRING, allowNull: false }
});
User.associate = (models) => {
User.belongsTo(models.company);
};
User.beforeCreate((user, _ ) => {
user.id = uuid();
return user;
});
User.sync();
return User;
};
models/index.js
'use strict';
var fs = require('fs');
var path = require('path');
var Sequelize = require('sequelize');
var basename = path.basename(__filename);
var env = process.env.NODE_ENV || 'development';
// var config = require(__dirname + '/../config/config.js')[env];
var db = {};
// if (config.use_env_variable) {
// var sequelize = new Sequelize(process.env[config.use_env_variable], config);
// } else {
// var sequelize = new Sequelize(config.database, config.username, config.password, config);
// }
const sequelize = new Sequelize('postgres://postgres:user#localhost:5432/mydb');
fs
.readdirSync(__dirname)
.filter(file => {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
})
.forEach(file => {
var model = sequelize['import'](path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
I was able to get this resolved.
The issue was with regard to the sequence in which the sync was called. In my original code, I was calling sync inside each model. Even though I added the options force and alter, I think the foreign keys were not getting added. So, I removed the sync code from inside the models, and added it in a separate loop inside index.js.
This gave me a new issue. Tables were getting created in an order that is not consistent with the order in which tables should be created for foreign keys to work since tables should pre-exist. I resolved it by manually providing the sequence of sync and now I see the columns getting created.
To summarise: model defn -> model association -> model sync in sequence
Thank you for your suggestions, members of SO.
Your model is fine! you must remove sync from models file , then check migration file for models with foreign key that foregin key is there,
for Migration User :
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.UUID
},
name: {
type: Sequelize.STRING
},
companyId: {
type: Sequelize.UUID,
references: {
model: 'Company',// company migration define
key: 'id'
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Users');
}
};
for create automate table from index.js and models you must install sequelize-cli
by type npm install --save sequelize-cli
then you must run this command for create models table in db
sequelize db:migrate
By using foreignKey: 'Company' you are telling it to associate with a column named Company. You typically also want to use singular table names, so company with an association of companies. By default Sequelize will use the primary key for the association, so you only need to specify foreignKey if you want to change it or set other parameters.
const User = sequelize.define(
'user',
{ /* columns */ },
{ /* options */ }
);
User.associate = (models) => {
User.belongsTo(models.Company);
};
const Company = sequelize.define(
'company',
{ /* columns */ },
{ /* options */ }
);
Company.associate = (models) => {
Company.hasMany(models.User, { as: 'users' });
};
This will create the following tables Company (id) and User (id, company_id).
Query all User records associated to a single Company:
const user = await User.findAll({ include: { model: Company } });
/*
user = {
id: 1,
company_id: 1,
company: {
id: 1,
},
};
*/
Query all Company records with multiple associated User records via users:
const company = await User.findAll({ include: { model: User, as: 'users' } });
/*
company = {
id: 1,
users: [{
id: 1
company_id: 1,
}],
};
*/
My guess is that the associate method is not getting called, and therefore, your association does not get created. Keep in mind that associate is not a built-in Sequelize method, but it is just a pattern used by the community. (More info on this thread)
There are various approaches to handle calling associate, here is one example. You have a models.js file that handles your association and you initialize that inside your main app.js file.
// app.js (aka your main application)
const models = require('./models')(sequelize, DataTypes);
// models.js
module.exports = (sequelize, DataTypes) => {
const models = {
user: require('./userModel')(sequelize, DataTypes),
company: require('./companyModel')(sequelize, DataTypes)
};
Object.keys(models).forEach(key => {
if (models[key] && models[key].associate) {
models[key].associate(models);
}
});
};
// companyModel.js
module.exports = (sequelize, DataTypes) => {
var Company = sequelize.define('company', {...});
Company.associate = (models) => {
Company.hasMany(models.user, { as: 'users' });
};
Company.sync();
return Company;
};
// userModel.js
module.exports = (sequelize, DataTypes) => {
var User = sequelize.define('user', {...});
User.sync();
return User;
};
Also, FYI, You probably know this but sync should only be used for experimenting or testing, not for a production app.