Mixins in loopback4 - loopbackjs

I want to add createdAt and updatedAt to each model on loopback 4
can not find name 'MixinTarget'.
Type parameter 'T' of exported function has or is using private name 'MixinTarget'.
If I try from documentation above error occurs.

MixinTaget must be imported from #loopback/core:
import {MixinTarget} from '#loopback/core';
import {Class} from '#loopback/repository';
export function TimeStampMixin<T extends MixinTarget<object>>(baseClass: T) {
return class extends baseClass {
// add a new property `createdAt`
// eslint-disable-next-line #typescript-eslint/ban-ts-comment
// #ts-ignore
public createdAt: Date;
constructor(...args: any[]) {
super(args);
this.createdAt = new Date();
}
printTimeStamp() {
console.log('Instance created at: ' + this.createdAt);
}
};
}
Further reading
As of the writing of this answer, the docs hasn't been updated to reflect the latest clarifications.
https://github.com/strongloop/loopback-next/blob/4932c6c60c25df885b4166b7c4c425eba457a73b/docs/site/Mixin.md

To resolve this issue, I didn't use the mixin approach. I added the following fields to my model.
#property({
type: 'date',
default: () => new Date(),
postgresql: {
columnName: 'updated_at',
},
})
updatedAt?: Date;
It should work as expected

Related

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

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
}
}

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.

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.

Serialize id in _id format with serializeId in Ember

In my response from server i have id attribute like 'id'. But to update record i need to send in request id like '_id'. I try to use serializeId serializeId method. My adapter looks like
import DS from 'ember-data'
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-
mixin'
import Ember from 'ember'
import Inflector from 'ember-inflector'
export default DS.JSONAPIAdapter.extend(DataAdapterMixin, {
authorizer: 'authorizer:token',
pathForType (modelName) {
let underscored = Ember.String.underscore(modelName)
return Inflector.inflector.pluralize(underscored)
}
})
and voicemail serializer
import ApplicationSerializer from './application'
import DS from 'ember-data'
import Ember from 'ember'
export default ApplicationSerializer.extend(DS.EmbeddedRecordsMixin, {
primaryKey: 'id',
attrs: {
history: {embedded: 'always'}
},
serializeIntoHash (data, type, snapshot, options) {
let root = Ember.String.decamelize(type.modelName)
data[root] = this.serialize(snapshot, options)
},
serializeId (snapshot, json, primaryKey) {
let id = snapshot.id
json['_id'] = id
}
})
But serializeId method did not called during serializing. And in my payload i still get id like 'id' instead '_id'. How to solve my issue?
UPDATED
I find in ember code that serializeId runs only if ds-serialize-id feature is enabled.
serialize(snapshot, options) {
let json = {};
if (options && options.includeId) {
if (isEnabled('ds-serialize-id')) {
this.serializeId(snapshot, json, get(this, 'primaryKey'));
} else {
const id = snapshot.id;
if (id) {
json[get(this, 'primaryKey')] = id;
}
}
}
To run serializeId method i turn on ds-serialize-id property in enviroment.js
EmberENV: {
FEATURES: {
// Here you can enable experimental features on an ember canary build
// e.g. 'with-controller': true
'ds-serialize-id': true
}
}
I think that isues solved.

Ember Data model.save misses fields in request body when mapping changed in keyForAttribute

I run into a problem when use Ember-data to save a model. The JSON structure for my model looks like:
{ post: {
id: 1,
name: 'post-1',
trigger: ['trigger-1', 'trigger-2'],
data: ['data-1', 'data-2']
}
}
Because 'data' and 'trigger' are reserved keywords for DS.Model, I created a mapping and renamed them to sc_data and sc_trigger as suggestion by Jurre using
Application.SERIALIZATION_KEY_MAPPINGS = {
'sc_data': 'data',
'sc_trigger': 'trigger'
};
Application.ApplicationSerializer = DS.ActiveModelSerializer.extend({
keyForAttribute: function (attr) {
if (Application.SERIALIZATION_KEY_MAPPINGS.hasOwnProperty(attr)) {
return Application.SERIALIZATION_KEY_MAPPINGS[attr];
} else {
return this._super(attr);
}
}
});
So my model for post looks like:
Application.Post = DS.Model.extend({
name: DS.attr('string'),
sc_trigger: DS.attr(),
sc_data: DS.attr()
});
the sc_trigger and sc_data are renmaed mapping for data and trigger.
It all worked fine when use this.store.find('post') and this.store.find('post', 1), i.e. GET calls. When I try to create a record using this.store.createRecord('post'), it creates a record with the correct attribute name sc_data and sc_trigger.
var newPost = this.store.create('post', {
name: 'test post',
sc_data: [],
sc_trigger: []
})
And the serialize function interprets the mapping correctly as well. newPost.serialize() returns
{
name: 'test post',
data: [],
trigger: []
}
But when I call newPost.save(), in the HTTP request body of the POST call, data and trigger field is missing. It only has
{
name: 'test post'
}
I have no idea why newPost.save() doesn't generate the correct request body when serialize() is working just fine.
Update
I managed to get around this by removing the keyForAttribute mapping and using
Application.ApplicationSerializer = DS.ActiveModelSerializer.extend({
attrs: {
sc_data: {key: 'data'},
sc_trigger: {key: 'trigger'}
}
});
This seems to be the suggested way to handle data with reserved keywords.
Which ember data version and emberjs version are you using?
Try saving with id like-
var newPost = this.store.create('post', {
id:1
name: 'test post',
sc_data: [],
sc_trigger: []
});
Save and create always expects id. So it's better to save/create record with id.