I am upgrading an Express application to Feathersjs application. I have the following model definition for belongs_to_many associations:
Clase A (usergroup):
userGroup.associate = function (models) {
models.user_group.belongsToMany(models.permission, {through: 'user_group_permission'});
};
Class B (permission):
permission.associate = function (models) {
models.permission.belongsToMany(models.user_group, {through: 'user_group_permission'});
};
With that definition, we sould be able to create a group and use the addPermission/addPermissions method to associate permissions to a group.
The following code intends to associate permissions with a group
newGroup = await Model.create({
groupName: data.groupName
});
newGroup.addPermissions(plist);
where Model is the variable for the UserGroup model class, and plist is a list of Permisssions.
This code is written in a service class generated by the feathers generate service command. Following this suggest, I overwrote the create method. The goal is to work with the permission list provided in create group endpoint.
The result is this one:
In the following image we can see the cause of the exception
What is wrong with models definition if with Express application the same code works fine?
Related
I'm working on an express sequelize project.
I've got an issue with sequelize.
I've got a simply schema like the documentation says:
class Task extends Model {}
Task.init({ title: Sequelize.STRING }, { sequelize, modelName: 'task' });
class User extends Model {}
User.init({ username: Sequelize.STRING }, { sequelize, modelName: 'user' });
User.hasMany(Task); // Will add userId to Task model
Task.belongsTo(User); // Will also add userId to Task model
But when I'm querying with include
sequelize.models['task'].findAll({include:[{all:true}]});
I'm facing to some issues:
The first :
this._getIncludedAssociation is not a function
Here is the github source origin code : https://github.com/sequelize/sequelize/blob/master/lib/model.js#L615
I just change this to Model to check.
This error is gone away, but a second (linked with the first, because I changed the behaviour), appears
User is not associated to Model
But It should say User is not associated to Task
(github of error: https://github.com/sequelize/sequelize/blob/master/lib/model.js#L711)
It's like the class Model wasn't instanciate...
What could be bad in my code ? Maybe the fact to call the model by sequelize.models[modelName] ?? I'm struggling with this for a long time...
Thanks.
More Infos :
Sequelize Version : ^5.7.0
Architecture :
At the application launch, all my models are store in a custom Database class stored in my process.
Inside any controller, I call a repository class with for exemple a method like the following where the wanted model is accessible via this.model:
findAll(options, force = false) {
let data = this.performExtraOptions(options);
data.paranoid = force;
if (this.isDebug) {
this.req.debug.addDatabaseCall('Table ' + this.model.getTableName() + ' findAll', data);
}
return this.model.findAll(data);
}
When I inspect with Chrome Debugger, the model is show like this :
const model = class extends Model {};
Ok.
In reading the sequelize code, I found that the options.model is required inside the algo.
The problem was that I sent the model too in the query options, but as the model name (string).
The algo, transform the option.model as the instance object of Model to make the process.
So I just have to rename my variable passed as query options.
This line of code was the problem :
https://github.com/sequelize/sequelize/blob/master/lib/model.js#L493
if (!options.model) options.model = this;
I am struggling to find the solution for that.
I want to have users which can belong to many organizations.
Each user can have a different role (I would prefer even roles but it sounds even more complicated...) at a specific organization.
In the table like User_Organization_Role I need to have fields like role (roleId?), isActive. Maybe some more.
I am using Feathers Plus generator but I do not think it matters in this case, however it may be beneficial to add something to the schema file?
I thought having belongsTo with simple organizationId field will be sufficient but I've realized that changing that to manyToMany, later on, would be painful so I think it is much better to implement that now.
I will appreciate any solutions / suggestions / best practices etc.
n:m relations are by far the most difficult to handle, and there's really no one-size-fits-all solution. The biggest thing is to read and understand this page and its sub-pages, and then read them 2 more times for good measure. Try to focus on doing one thing at a time. I outline how I would approach this with feathersjs in this issue:
https://github.com/feathersjs/feathers/issues/852#issuecomment-406413342
The same technique could be applied in any application... the basic flow goes like this:
Create or update your primary objects first (users, organizations, roles, etc.). There are no relations made at this point. You need to have your objects created before you can make any relations.
Create or update the relations. This involves updating a "join" table (aka: "mapping" or "through" table) with data from step #1. The join table can (and should) have its own model. It should contain a foreign key for each of the objects you are associating (userId, organizationId, roleId etc.). You can put other fields in this table too.
Here is some pseudo code for how I would define my models (only showing relevant code for brevity). There is a little more to it than what I describe below, but this should get you started.
const UserOrganizationRole = sequelize.define('User_Organization_Role', {
// Define any custom fields you want
foo: DataTypes.STRING
})
// Let sequelize add the foreign key fields for you.
// Also, save a reference to the relationship - we will use it later
User.Organization = User.belongsToMany(Organization, { through: UserOrganizationRole });
User.Role = User.belongsToMany(Role, { through: UserOrganizationRole });
Organization.User = Organization.belongsToMany(User, { through: UserOrganizationRole });
Role.User = Role.belongsToMany(User, { through: UserOrganizationRole });
... and here is how I would go about handling inserts
const user = await User.create({ ... });
const org = await Organization.create({ ... });
const role = await Role.create({ ... });
await UserOrganizationRole.create({
userId: user.id,
organizationId: org.id,
roleId: role.id,
foo: 'bar'
});
... and finally, load the data like so:
// Now we can reference those relationships we created earlier:
const user = await User.findById(123, {
include: [User.Organization, User.Role]
});
const org = await Organization.findById(456, {
include: [Organization.User]
});
I have two models that are related, Customers and Addresses. I first discovered this issue when I was trying to create a customer with a related address. For our purposes, a single customer can have multiple addresses, and when creating a new customer, we want to create an address at the same time as we create the customer.
I did some digging through the documentation and set up the relationship as best as I could, and this seemed to work well enough, but then I noticed that when I included both the models in modules together, (i.e. my routes/controllers), I was getting circular references.
Long story short, my research lead me to add the registry plugin to my bookshelf.js file. This worked at the time, but now it looks like my Address model isn't properly exported when being referenced in Customers.
Here's a snippet of my current configuration
// bookshelf.js
const bookshelf = require('bookshelf')(knex);
bookshelf.plugin([
'registry',
]);
module.exports = bookshelf;
// customers.js
const bookshelf = require('../bookshelf');
const Address = require('./address');
const Customer = bookshelf.Model.extend({
tableName: 'customers',
addresses: function () {
return this.hasMany('Address');
},
}, {
customCreate: function (attributes) {
return this.forge(attributes)
.save()
.tap(c => {
return Address.forge(attributes)
.save({
customer_id: c.get('id'),
});
})
}
});
module.exports = bookshelf.model('Customer', Customer);
// address.js
const bookshelf = require('../bookshelf');
const Customer = require('./customer');
const Address = bookshelf.Model.extend({
tableName: 'addresses',
customer: function () {
return this.belongsTo('Customer');
}
});
module.exports = bookshelf.model('Address', Address);
I started to notice that when I would run Customer.customCreate(), I got an error saying Address.forge is not a function. I threw some console logs into my customer.js file and saw that Address is an empty object ({}) when being referenced within customer.js. However, in other places, it's returning the proper Bookshelf model.
Looks to me like I'm trying to use my Address model in customers before it's properly required, which made me wonder if I'm structuring my project and models properly, or if there's any changes I need to make.
There's a circular reference problem alright. The best way to structure your models so that there are no such problems is to load them all during your app's initialization in a single file, e.g. index.js on your models' directory, attach each one to an object and export that object. Then you just require() that file and get access to all the models in a single place.
However, to solve your problem in a much easier way you just need to make a single change to your customCreate() method:
customCreate: function (attributes) {
return this.forge(attributes)
.save()
.tap(c => this.related('addresses').create(attributes))
}
}
This makes use of the Collection.create method to easily create a new model inside a collection, and since it's used on a relation it will also set the correct foreign key.
Note that the Registry plugin will not save you from circular dependency problems, but it will allow you to write your models in a way that avoids them.
When I define a relation, I specify what model object it is related to:
models.Guest.belongsTo(models.Member, {foreignKey: 'guestId', as: 'guest'});
Now to use this relationship in a query, I need to specify the model object again:
Guest.findAll({
include: models.Member,
as: 'guest'
});
In my setup, all the relationships are defined in a single file, while individual models are in files by themselves. The setup defines the models object, which has all of them - and the thing is available to application logic.
What I would like is define some of the more complex queries directly in the Guest model object, to abstract them away from the application. To do this, I'd like to find the models.Member object - retrieving it somewhere out of the relationship:
Guest.findMembers = function(){
return this.findAll({
include: <that model that guest is related to>,
as: 'guest'
});
}
Please imagine that the example is complex enough that it warrants solving. Lots more other joins and where statements, for instance. Or data processing after the fact.
I can pass the models object into the setup of each individual object, so its siblings can be accessed. Is that my only option?
You can just also add an include in your include mode also
Guest.findMembers = function(){
return this.findAll({
include: [{
model: <that model that guest is related to>,
as: 'guest',
include: [{
model: <that model that guest is related this included model>,
as: 'setTheAlias',
}]
}]
});
}
The only reference in Sequelize's documentation that I could find about this is at Relations / Associations.
Apparently, if you have something like:
Person.hasOne(Person, {as: 'Father', foreignKey: 'DadId'})
then you can do something like:
Person.setFather();
Person.getFather();
As far as I understand, these set and get methods are created dynamically for associations.
However, I have tried something similar and it does not work:
Person.hasOne(Father, { foreignKey: 'father_id' });
var aPerson = Person.build({
name: 'Mike'
});
aPerson.setFather({ id: 1 });
I realized that the { id: 1 } bit may not be correct, but the problem is that I get a message saying that the setFather function is undefined.
Basically, my question is: how can I attach associations to an already-created instance of some model in Sequelize?
Do you have a specific model for fathers? The associations uses the model name, which means to just importing the user model as Father is not going to work:
var Person = sequelize.define('person')
var Father = person;
Person.hasOne(Father) // adds setPerson
Person.hasOne(Person, { as: 'father' }) // adds setFather
I recently updated the API docs for associations - hopefully it should be a bit clearer which functions are added and what parameters they take http://docs.sequelizejs.com/en/latest/api/associations/has-one/