i am new in Loopback 4
I followed this tutorial to get started and everything worked fine.
I tried to create my own models Category and SubCategory using a MySQL database, with one to many relation (one category has many sub categories), i have noticed that it did create a field in subcategory table (categoryId) but the foreign key index is missing.
can someone help?
LoopBack 4 does not implicitly add foreign key constraints. This is to allow weak cross-datasource relations (e.g. a relation between PostgreSQL & Oracle).
Hence, the responsibility falls on the connectors to provide an interface to define these constraints. This however, means that there isn't a consistent interface across different connectors. There is an open issue to track this.
In the case of MySQL:
#model({
settings: {
foreignKeys: {
categorySubCategoryFK: {
name: 'categorySubCategoryFK',
entity: 'Category',
entityKey: 'id',
foreignKey: 'categoryId',
},
},
},
})
In case auto-migration is used (which it should not be used in production!), migrate.ts would need to be updated to define explicit ordering of the schemas:
await app.migrateSchema({
existingSchema,
models: ['Category', 'SubCategory'],
});
Further reading
https://loopback.io/doc/en/lb4/todo-list-tutorial-sqldb.html#specify-the-foreign-key-constraints-in-todo-model
https://loopback.io/doc/en/lb4/MySQL-connector.html
found the answer here, besides adding the settings to #model annotation, like so
#model({
settings: {
foreignKeys: {
categorySubCategoryFK: {
name: 'categorySubCategoryFK',
entity: 'Category',
entityKey: 'id',
foreignKey: 'categoryId',
},
},
},
})
you have to change to specify in which order tables should be created in migrate.ts and change this
await app.migrateSchema({existingSchema});
to this
await app.migrateSchema({
existingSchema,
models: ['Category', 'SubCategory'],
});
more details here
Loopback 4 creates relation on models and Api's not on database. Thus it won't get reflected on your mysql database
Related
I have a following table. I don't want user to follow himself so I want to add a CHECK constraint.
For example, if this is inserted, I want database to throw an error.
{
userID: 1,
followingID: 1,
}
I can check with Javascript if userID and followingID are equal but I want database to check it.
My MYSQL version is 8.0.17 so I think it is possible to create this constraint with SQL. How can I add this constraint with Sequelize?
There is two solution
1. Using Model wise validation and adding check constraint in database manually:
Model wise validation:
const FollowingModel = sequelize.define("following", {
userId: {
type: Sequelize.INTEGER,
// .. other configuration like `allowNull`
},
followingId: {
type: Sequelize.INTEGER,
// .. other configuration like `allowNull`
}
}, {
validate: {
userShouldNotFollowSelf : function() {
if(this.userId === this.followingId) {
throw Error("User should not follow self") // Use any custom error class if your application has such class.
}
}
}
}
Beware this will allow you create entry in database which does not maintain this constraint.
It is just ORM's application layer check that, this application won't allow any entry where userId and followingId is not same.
Mysql database layer check constraint.
CREATE TABLE `following`
(
`userId` INT NOT NULL,
`followingId` INT NOT NULL,
CONSTRAINT `no_self_following` CHECK (`userId` <> `followingId`)
-- other properties and foreign key constraints.
);
It will ensure that, no such entry inserted where userId and followingId is same.
2. Declaring constraint in sequelize query interface.
This require to declare your model using query interfaces addConstraint as follows
sequelize.getQueryInterface().addConstraint("following", ['userId'], {
type: 'check',
name: "no_self_following"
where: {
userId: {
[Sequelize.Op.ne]: Sequelize.col("followingId")
}
}
});
Run this while all database model is been synced correctly. It will add database level constraint.
Which one to use ?
Approach #1 is more efficient. It is checking within the application without going into the database call, Makes your database less busy.
I am using Sequelize, a nodejs ORM for mysql. Using mysql workbench I made an EEM diagram and pushed that design into the db, so far so good.
Now in Sequelize I have to tell it what the design of the DB looks like, part of that is telling it what foreign keys are called.
In Workbench there is a foreign key tab in the tablethere are variables formatted likefd_positions_tradingPLan1` but I never name that, in fact in my EEM diagram I have
Then if I go to that foreign keys tab at the bottom I get this. I am confused as to exactly what I should tell the ORM the foreign key is...
Let's take your positions Table as reference. To build your model on sequelize you have to do the following:
module.exports = (sequelize, DataTypes) => {
const Position = sequelize.define('Position', { // this is the name that you'll use on sequelize methods, not what you have on your db
// define your columns like this:
tradeName: { //the name of the variable that you'll use on sequelize and js
field: 'trade_name', //the actual name of your column on the table
type: DataTypes.STRING(128) // the data type
},
// .......
// for your foreignKeys you have to define the column like your other attributes.
userId: {
field: 'user_id',
type: DataTypes.INTEGER
},
}, {
tableName: 'positions', //this is the name of your table on the database
underscored: true, // to recognize the underscore names
createdAt: 'created_at', //
updatedAt: 'updated_at',
});
//now for your association let's say that you defined your USER table like this example.
Position.associate = (models) => {
// on the foreignKey value, youhave to put the same that you define above, and on the db.
Position.belongsTo(models.User, { as: 'User', foreignKey: 'user_id' });
//depending on your other relations, you are gonna use hasMany, hasOne, belongsToMany
};
return Position;
};
Sequelize does the association only one way, that means that on this example, you can't query with sequelize from User to Position, to be able to
have two way association you have to defined on both models.
User.associate = (models) => {
// on this case we use hasMany cause user can have many positions I suppose, if not, use hasOne
User.hasMany(models.Poisition, { as: 'positions', foreignKey: 'user_id' }); //remeber to use the same foreignKey name
};
UPDATE:
as is an identfier for Sequelize. Let's say you make two associations for the same model, later when you try to query one of this associations, you can specify the association that you want
User.associate = (models) => {
User.hasMany(models.Poisition, { as: 'positions', foreignKey: 'user_id' });
User.hasMany(models.Poisition, { as: 'customerPositions', foreignKey: 'customer_id' });
};
//the actual association call
User.findAll({
include:[{
model: db.user,
as: 'positions'
}, {
model: db.user,
as: 'customerPositions'
}]
})
Now for fk_positions_users1, this is an identifier for MySQL itself. Sequelize only check for the foreignKey and the models involve. Obviously when Sequelize create the reference, it gives a template name using the table and column name. I tried myself creating a new foreignKey on my table and then updating the model and everything goes fine. You should'nt have problems with that.
I think I've done enough research on this subject and I've only got a headache.
Here is what I have done and understood: I have restructured my MySQL database so that I will keep my user's data in different tables, I am using foreign keys. Until now I only concluded that foreign keys are only used for consistency and control and they do not automatize or do anything else (for example, to insert data about the same user in two tables I need to use two separate insert statements and the foreign key will not help to make this different or automatic in some way).
Fine. Here is what I want to do: I want to use Sequelize to insert, update and retrieve data altogether from all the related tables at once and I have absolutely no idea on how to do that. For example, if a user registers, I want to be able to insert the data in the table "A" containing some user information and in the same task insert in the table B some other data (like the user's settings in the dedicated table or whatever). Same with retrievals, I want to be able to get an object (or array) with all the related data from different tables fitting in the criteria I want to find by.
Sequelize documentation covers the things in a way that every thing depends on the previous one, and Sequelize is pretty bloated with a lot of stuff I do not need. I do not want to use .sync(). I do not want to use migrations. I have the structure of my database created already and I want Sequelize to attach to it.
Is it possible insert and retrieve several rows related at the same time and getting / using a single Sequelize command / object? How?
Again, by "related data" I mean data "linked" by sharing the same foreign key.
Is it possible insert and retrieve several rows related at the same
time and getting / using a single Sequelize command / object? How?
Yes. What you need is eager loading.
Look at the following example
const User = sequelize.define('user', {
username: Sequelize.STRING,
});
const Address = sequelize.define('add', {
address: Sequelize.STRING,
});
const Designation = sequelize.define('designation', {
designation: Sequelize.STRING,
});
User.hasOne(Address);
User.hasMany(Designation);
sequelize.sync({ force: true })
.then(() => User.create({
username: 'test123',
add: {
address: 'this is dummy address'
},
designations: [
{ designation: 'designation1' },
{ designation: 'designation2' },
],
}, { include: [Address, Designation] }))
.then(user => {
User.findAll({
include: [Address, Designation],
}).then((result) => {
console.log(result);
});
});
In console.log, you will get all the data with all its associated models that you want to include in the query
I have the following scenario, my application has two entities: box and items with N to N relationship. I am using sequelize with MySQL.
I am using pseudocode to represent the tables:
Box {
id: Integer primary key
name: String
}
Item {
id: Integer primary key
name: String
}
I have set up the schemas with relations hasMany in both directions using the following through relation:
Box.hasMany(Item, { through: Box_Item });
Item.hasMany(Box, { through: Box_Item });
Box_Item {
id_box: Integer,
id_item: Integer,
item_order: Integer
}
With primary_key(id_box, id_item).
I tested it and I can call myBox.getItems() on my instance object myBox and easily get all the items it has.
I can make calls as
BoxModel.findOne({
where: { id: 1 },
include: [{ model: ItemModel }]
});
And it automatically understands there is a relation between the models through Box_Item and get everything correctly, except that I'm not getting the results sorted by item_order field. This field is a number from 1 to N that represents the item order inside that box.
I tried
BoxModel.findOne({
where: { id: 1 },
include: [
{
model: ItemModel,
order: 'item_order'
}
]
});
But it seems sequelizejs does not support order inside include yet (checked on their github repo).
I tried to force
BoxModel.findOne({
where: { id: 1 },
order: '`box_model`.`item_order`'
include: [ { model: ItemModel } ]
})
looking through the query sequelize creates but it just put the ORDER BY in two different places (inside INNER JOIN and at the end of the query, don't know why...) and I got an error.
So I searched for this on stackoverflow (1), found a few questions but I don't get a good way for doing that using the ORM.
How could I get the items sorted by item_order field when asking for specific box items?
After a few days trying to get it done I found an answer on stackoverflow that helped me.
After creating the relationships between Box and Item I can easily call on an instance:
myBox.getItems({
order: '`box_model`.`item_order`'
});
And then I get the result I'm expecting. But I had to look through the query sequelize is creating based on the models and get the correct field based on their renaming rules.
If you want you can pass the as parameter and rename your tables.
Using sequelize, I expect this line:
m.User.belongsToMany(m.Company, {through: 'UserCompany'});
to generate a new table in my database called 'user_company' that would link the 'user' table and the 'company' table together. However, it isn't doing that. Am I misunderstanding the documentation when it says
This will create a new model called UserProject with with the
equivalent foreign keys ProjectId and UserId. Whether the attributes
are camelcase or not depends on the two models joined by the table (in
this case User and Project).
or am I doing something wrong?
Here are the relations I am setting up
m.Company.hasMany(m.User);
m.User.belongsToMany(m.Company, {
through: m.UserCompany
});
m.User.sync({force: true, match: /_test$/});
m.Company.sync({force: true, match: /_test$/});
m.UserCompany.sync({force: true, match: /_test$/});
Looks like I needed to create the UserCompany model manually. So, UserCompany.js looks like:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('UserCompany', {
}, {
freezeTableName: true,
paranoid: true
});
}
Then the belongsToMany automatically adds the correct columns to the table.