I am using featherjs v2.0.3 with sequelize v3.29.0 and I just created three models, the third having a relationship to the other two.
I used the feathers-cli to generate services for each and then edit the model file of each.
So far, so good, the tables are created (using PostgreSQL), indexes are created, feathersjs takes care of the CRUD nicely, but not foreign keys yet.
So, when I try to tell feathersjs the relationship between the models, I get in trouble.
When I add role_permission.belongsTo(permissions) to the role_permission model, I get this error:
ReferenceError: permissions is not defined
As I've seen in the Sequelize documentation, the models are defined in the same "document", hence I suspect the problem is somewhere there, but I don't understand what need to be done.
Finally, here's the relevant parts of the model definitions of permission and role_permission:
// permission-model.js - A sequelize model
module.exports = function(sequelize) {
const permission = sequelize.define('permissions', {
permission_id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV1,
primaryKey: true,
allowNull: false
}...
}, ...);
permission.sync();
return permission;
};
// role_permission-model.js - A sequelize model
module.exports = function(sequelize) {
const role_permission = sequelize.define('role_permissions', {
permission_id: {
type: Sequelize.UUID,
allowNull: false
}...
}, ...);
role_permission.belongsTo(permissions) //<-- undefined?
role_permission.sync();
return role_permission;
};
Do you have any pointers to help me solve this?
Thanks!
You need to import the permissions model. But it may or may not be defined yet.
Here's a method I discovered from #mrpatiwi on github to ensure every model is loaded before the associations are set up.
First, when you need to define a relationship add a classMethod called associate that accepts all the models and sets up the relationships.
module.exports = function(sequelize) {
const role_permission = sequelize.define('role_permisson', {
...
}, {
classMethods: {
associate(models) {
role_permission.belongsTo(models.permission);
},
},
});
// Don't add role_premission.sync() here
return role_permission;
};
Then, in src/services/index.js at the end of the module.exports function, add:
// Setup relationships
const models = sequelize.models;
Object.keys(models)
.map(name => models[name])
.filter(model => model.associate)
.forEach(model => model.associate(models));
sequalize.sync();
I had the same issue and resolved it simply by changing this:
role_permission.belongsTo(permissions)
to this:
role_permission.belongsTo(models.permissions)
(I used the Feathers CLI to generate the models today, 9 Aug 2018)
Related
I wrote a service that analyses videos with Google Cloud Video Intelligence
And I save the analysis results to the MongoDB with mongoose
This is the model I use (I've simplified everything to avoid confusion):
// Video.js
const mongoose = require('mongoose');
const videoSchema = new mongoose.Schema({
analysis_progress: {
percent: { type: Number, required: true },
details: {}
},
status: {
type: String,
enum: ['idle', 'processing', 'done', 'failed'],
default: 'idle'
}
});
module.exports = mongoose.model('Video', videoSchema);
When analyse operation ends, I call the function below and run update like this:
function detectFaces(video, results) {
//Build query
let update = {
$set: {
'analysis_results.face_annotations': results.faceDetectionAnnotations // results is the the test result
}
};
Video.findOneAndUpdate({ _id: video._id }, update, { new: true }, (err, result) => {
if (!err)
return console.log("Succesfully saved faces annotiations:", video._id);
throw err // This is the line error thrown
});
}
And this is the error I get:
Error: cyclic dependency detected
at serializeObject (C:\Users\murat\OneDrive\Masaüstü\bycape\media-analysis-api\node_modules\bson\lib\bson\parser\serializer.js:333:34)
at serializeInto (C:\Users\murat\OneDrive\Masaüstü\bycape\media-analysis-api\node_modules\bson\lib\bson\parser\serializer.js:947:17)
...
Solutions I tried:
Added {autoIndex: false} inside db config.
mongoose.connect(process.env.DB_CONNECTION, {useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false, autoIndex: false });
Removing retryWrites=true from Mongo URI structure. (I didn't have that parameter in my connection URI already)
So, I think the source of the problem is that I am saving the whole test result but I don't have any other option to do that. I need to save as it is.
I am open to all kinds of suggestions.
Just as I guessed, the problem was that there was a cyclic dependency in the object that came to me from google.
With help of my colleague:
Then since JSON.stringify() changes an object into simple types:
string, number, array, object, boolean it is not capable of storing
references to objects therefor by using stringify and then parse you
destroy the information that stringify cannot convert.
Another way would be knowing which field held the cyclic reference and
then unsetting, or deleting that field.
I couldn't find which field has cycylic dependency so I used I JSON.stringfy() and JSON.parse() to remove it.
let videoAnnotiations = JSON.stringify(operationResult.annotationResults[0]);
videoAnnotiations = JSON.parse(videoAnnotiations);
I write an application in SailsJS and i have a problem with relations,
some code at the beginning:
User.js
module.exports = {
tableName: 'user',
attributes: {
schedules: { collection: 'userworkstationschedule', via: 'user' },
}
UserWorkstationSchedule.js
module.exports = {
tableName: 'user_workstation_schedule',
attributes: {
user: { model: 'user', required: true },
}
}
After run my code in Postaman, in JSON response i get:
{
...
"user": 2,
...
}
I get only ID of my user, but i want to get a whole object of User model with his firstname, lastname, etc.
Ccould anyone help me?
I'm more accustomed to a sails 0.12, but I know there you can configure your app so that population happens automatically, but it is not the default - to do this, go into config/blueprints.js and set the populate option to true.
However, I recommend against this - population may not be something you need on every page / every api call. You can make your api calls with population requests built in, like so:
/api/userworkstationschedule/[workstationid]?populate=user
That should make the populate happen just for that api call.
I've been using sequelize a lot in recent projects and I'm curious about what happens under the hood for associations vs migrations. For example, when I generate 2 models:
user = {
id,
name,
}
and
post = {
id,
name,
}
and then I generate a migration to add the associated columns:
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn(
'posts',
'userId', // name of the key we're adding
{
type: Sequelize.UUID,
references: {
model: 'users', // name of Target model
key: 'id', // key in Target model that we're referencing
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
}
);
},
down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn(
'posts', // name of Source model
'userId' // key we want to remove
);
}
};
what does the associate method in the model do if the migration above adds the actual userId column to the posts table?
example of an associate method in a model:
module.exports = (sequelize, DataTypes) => {
const post = sequelize.define('post', {
name: DataTypes.TEXT
}, {});
post.associate = function(models) {
post.belongsTo(models.user);
};
return post;
};
which raises a bigger question, if the associate method ends up creating the actual foreign key column in the db, is an intermediate migration (like the one shown above, which creates the foreign key columns) necessary to create the foreign key column?
TL;DR: Sequelize Associations do not do anything on the DB side, meaning they can't (create tables, add columns, add constraints, ..etc)
Disclaimer: I might've not covered all the benefits/differences of
both in this answer, this just an abstract.
1) here is how i differentiate the Model from the Migration
(based on functionality):
The Migration (creates tables, add constraints, ..etc) on the DB
The Model makes it easier for you as a developer to interact with the table that corresponds with the Model (which is the model is defined for) on the DB, for example: A User model helps you to interact with the Users table without the need to write SQL queries.
2) The Associate methods give you two special powers which are lazyLoading and eagerLoading who both spare you the headache of doing Joins manually through raw SQL queries.
so yeah again: "The Model spare you the headache of writing raw SQL queries yourself."
Although this does not fully answer the question in detail, there's a decent description about associations in the sequelize github repo under the associations folder
The comment states:
Creating an association will add a foreign key constraint to the attributes
also, the following hints at the fact that a column is actually generated from the association:
* To get full control over the foreign key column added by sequelize,
* you can use the `foreignKey` option. It can either be a string,
* that specifies the name, or and object type definition,
* equivalent to those passed to `sequelize.define`.
*
* ```js
* User.hasMany(Picture, { foreignKey: 'uid' })
* ```
*
* The foreign key column in Picture will now be called `uid`
* instead of the default `userId`.
*
* ```js
* User.hasMany(Picture, {
* foreignKey: {
* name: 'uid',
* allowNull: false
* }
* })
* ```
Suppose we have a such structure in NodeJs Sequelize.
var User = sequelize.define('user', {/* ... */})
var Project = sequelize.define('project', {/* ... */})
Project.hasMany(User)
In this part of video presenter offers to save embedded objects with two steps using promises. In our case it would be something like:
Project.create({
...
}).then(function(project){
User.create({
...
projectId:project.id
})
})
But this approach will result two db calls.
So, is it possible to save embedded objects (Project which contains User e.g. User must have Project's id as a foreign key) into the db with one db call or within a transaction using Sequelize?
You should be able to insert parent-children by passing an array of objects into a key with the same name as the "as" value used on the "include". Although the documentation is light on the usage, you can see it handled in the source code here.
No promises (pun semi-intended) that this is actually run in single SQL query, not sure of the exact implementation in Sequelize. You should be able to enable logging (logging: console.log in the Sequelize(options)) to see what it's running.
// specify an "as" value and require a User.project_id value
Project.hasMany(User, { as: 'Users', foreignKey: { allowNull: false } });
// define your Project -> Users as json with the "as" value as the key
const project = {
name: 'project name',
Users: [
{
name: 'user 1',
},
{
name: 'user 2',
},
],
};
// create a transaction and "include" the Model in the create, txn falls back in .catch()
sequelize.transaction(t =>
Project.create(project, {
include: [{
model: User,
as: 'Users',
}],
transaction: t,
})
)
.catch(e => console.log('the txn failed because', e));
I am very new to Sails.js and I am trying to understand its magic.
I have three nested models using associations : Series.js -> Season.js -> and Episode.js.
Series.js
module.exports = {
attributes: {
/* other attributes */
seasons: {
collection: 'season',
via: 'series'
}
}
};
Season.js
module.exports = {
attributes: {
/* other attributes */
episodes: {
collection: 'episode',
via: 'season'
},
series: {
model: 'series'
}
}
};
Episode.js
module.exports = {
attributes: {
/* other attributes */
season: {
model: 'season'
}
}
};
Out of the box, I can get the seasons of a series using the route:
/series/:id/seasons
And also the episodes of a season using the route:
/seasons/:id/episodes
What I don't understand is why I can't get the episodes of a season of a series using this route:
series/:id/seasons/:id/episodes
Any idea why is this happening? I know I could use custom routes to get the behaviour I want, but it'd be nice to be able to do it without any further configuration.
I know Sails.js only allows one level population, but this doesn't seem the issue since I have set populate: false.
Sails doesn't provide this route automatically:
series/:id/seasons/:id/episodes
because it doesn't really make sense if you think about it. If you already know the ID of the season, then the series it belongs to is irrelevant. Just use:
seasons/:id/episodes
to get the episodes you want.