allow null value for sequelize foreignkey? - javascript

How do I allow null for a foreignkey? I have a through and belongsToMany association:
Shelf
belongsToMany(Book, { through: Library, as: "Books"});
Book
belongsToMany(Shelf, { through: Library, as: "Shelves"});
Library
section: DataTypes.STRING,
// ... other fields
I would like to record a null on the bookId in Library:
Library.create({
section: "blah",
bookId: null,
shelfId: 3
});
Since Sequelize automatically creates the bookId and shelfId in the join table Library, how would I specify that I want to allow a null on the bookId foreignkey in Library?

I am no expert on node.js nor sequelize.js, but with one search in google I got the official documentation. You might need to pay some attention to this part of the code in the documentation:
Library.hasMany(Picture, {
foreignKey: {
name: 'uid',
allowNull: false
}
});
It seems you need to specify the {foreignKey: {name: 'bookId', allowNull: true} } in Library.

By default, the Sequelize association is considered optional. If you would like to make the foreign key required, you can set allowNull to be false.
Foo.hasOne(Bar, {
foreignKey: {
allowNull: false
}
});
See the docs

Related

Sequelize: how to do a WHERE condition on joined table with left outer join

My database model is as follows:
An employee drives one or zero vehicles
A vehicle can be driven by one or more employees
A vehicle has a model type that tells us it's fuel type amongst other things.
I'd like sequelize to fetch me all employees where they don't drive a vehicle, or if they do then the vehicle is not diesel.
So where VehicleID is null OR Vehicle.VehicleModel.IsDiesel = false
My current code is as follows:
var employee = sequelize.define('employee', {
ID: Sequelize.INTEGER,
VehicleID: Sequelize.INTEGER
});
var vehicle = sequelize.define('vehicle', {
ID: Sequelize.INTEGER,
ModelID: Sequelize.INTEGER
});
var vehicleModel = sequelize.define('vehicleModel', {
ID: Sequelize.INTEGER,
IsDiesel: Sequelize.BOOLEAN
});
employee.belongsTo(vehicle);
vehicle.belongsTo(vehicleModel);
If I run the following:
options.include = [{
model: model.Vehicle,
attributes: ['ID', 'ModelID'],
include: [
{
model: model.VehicleModel,
attributes: ['ID', 'IsDiesel']
}]
}];
employee
.findAll(options)
.success(function(results) {
// do stuff
});
Sequelize does a left outer join to get me the included tables. So I get employees who drive vehicles and who don't.
As soon as I add a where to my options:
options.include = [{
model: model.Vehicle,
attributes: ['ID', 'ModelID'],
include: [
{
model: model.VehicleModel,
attributes: ['ID', 'IsDiesel']
where: {
IsDiesel: false
}
}]
}];
Sequelize now does an inner join to get the included tables.
This means that I only get employees who drive a vehicle and the vehicle is not diesel. The employees who don't drive a vehicle are excluded.
Fundamentally, I need a way of telling Sequelize to do a left outer join and at the same time have a where condition that states the column from the joined table is false or null.
EDIT:
It turns out that the solution was to use required: false, as below:
options.include = [{
model: model.Vehicle,
attributes: ['ID', 'ModelID'],
include: [
{
model: model.VehicleModel,
attributes: ['ID', 'IsDiesel']
where: {
IsDiesel: false
},
required: false
}],
required: false
}];
I had already tried putting the first 'required:false' but I missed out on putting the inner one. I thought it wasn't working so I gave up on that approach. Dajalmar Gutierrez's answer made me realise I needed both for it to work.
When you add a where clause, sequelize automatically adds a required: true clause to your code.
Adding required: false to your include segment should solve the problem
Note: you should check this issue iss4019
Eager loading
When you are retrieving data from the database there is a fair chance that you also want to get associations with the same query - this is called eager loading. The basic idea behind that, is the use of the attribute include when you are calling find or findAll.
when you set
required: false
will do
LEFT OUTER JOIN
when
required: true
will do
INNER JOIN
for more detail docs.sequelizejs eager-loading

Sequelize Migration: update model after updating column attributes

I will get through to the point already. I'm having a problem of updating the rows after I have changed the status column attribute.
up: function(queryInterface, Sequelize) {
return queryInterface.changeColumn('projects', 'status', {
type: Sequelize.ENUM('processing', 'unassigned', 'ongoing', 'completed'),
allowNull: false,
defaultValue: 'unassigned'
}).then(function() {
return Project.update({
status: 'unassigned'
}, {
where: {
status: 'processing'
}
});
});
}
The Project.update() seems not working in any case but changing the attributes of the column works.
Any idea guys? I'm somehow a newbie in sequelize and any idea would be a great help. Thanks.
Depending on how you execute the migration ( via sequelize-cli or programmatically via umzug ). There is a different way to expose the table via the ORM.
In your case you have queryInterface passed as an argument to your function. So you can do a "raw query" via the attached sequelize property.
up: function(queryInterface, Sequelize) {
return queryInterface.changeColumn('projects', 'status', {
type: Sequelize.ENUM('processing', 'unassigned', 'ongoing', 'completed'),
allowNull: false,
defaultValue: 'unassigned'
}).then(function() {
return queryInterface.sequelize
.query("UPDATE projects SET status='unassigned' WHERE status='processing'");
});
}
By doing this you will make a raw Query to your database.
You can check out this gist for more details on an advanced way of using the ORM inside the migration.
I'm a fan of using umzug programmatically, which executes the migrations and also provides the initialized models of your database. If you configure it properly, you will benefit the exposed models ( e.g. sequelize.model('project').update() ) and have a better looking code.

Ignore default model attributes in SailsJS for 1 specific model

Hi everyone im stuck at using a model of a specific table of a mysql database. I am using 2 different databases in my SailsJS application. One of the two databases has been created before the SailsJS application, so the tables in this database doesn't have the default attributes configured in config/models.js.
This causes an error when I try to call for example the find() function on the model that uses the older database because it misses a column. See following error:
: ER_BAD_FIELD_ERROR: Unknown column 'tbl_user.deleted' in 'field list'
I don't want to add the default attributes to the older database columns, so is it possible to ignore the default attributes configured in config/models.js for specific models?
After trying a few things i came up with the following solution.
Just add the default attributes to your model but add it as an function.
module.exports = {
connection: connection,
migrate: 'safe',
tableName: 'tbl_name',
autoCreatedAt: false,
autoUpdatedAt: false,
autoPK: false,
schema: true,
attributes: {
id: {
columnName: 'id',
type: 'string',
primaryKey: true
},
name: {
columnName: 'name',
type: 'string'
},
email: {
columnName: 'email',
type: 'string'
},
deleted: function(){
var obj = this.toObject();
delete obj.deleted;
return obj;
},
createdBy: function(){
var obj = this.toObject();
delete obj.createdBy;
return obj;
}
}
};
In this example the attributes deleted and createdBy are default attributes in config/models.js. I made a function of these attributes in the specific model. In this function i delete this attribute and return the object without the deleted attribute.

Querying association tables in Sequelize

I have two tables (users and games) joined by an association table (game_players), creating a many-to-many relationship:
models.Game.belongsToMany(models.User, { through: models.GamePlayer, as: 'players' });
models.User.belongsToMany(models.Game, { through: models.GamePlayer, foreignKey: 'user_id' });
In addition to the foreign keys user_id and game_id, game_players has a few extra columns for link-specific data:
sequelize.define('game_player', {
isReady: {
defaultValue: false,
type: Sequelize.BOOLEAN,
field: 'is_ready'
},
isDisabled: {
defaultValue: false,
type: Sequelize.BOOLEAN,
field: 'is_disabled'
},
powerPreferences: {
type: Sequelize.TEXT,
field: 'power_preferences'
},
power: {
type: Sequelize.STRING(2),
defaultValue: '?'
}
}, {
underscored: true
});
Suppose I want to fetch a game and eagerly load active players. This was my first effort:
db.models.Game.findAll({
include: [{
model: db.models.User,
as: 'players',
where: { 'game_player.isDisabled': false }
}]
}).nodeify(cb);
This generates the following SQL, which throws the error Column players.game_player.isDisabled does not exist:
SELECT "game"."id",
"game"."name",
"game"."description",
"game"."variant",
"game"."status",
"game"."move_clock" AS "moveClock",
"game"."retreat_clock" AS "retreatClock",
"game"."adjust_clock" AS "adjustClock",
"game"."max_players" AS "maxPlayers",
"game"."created_at",
"game"."updated_at",
"game"."gm_id",
"game"."current_phase_id",
"players"."id" AS "players.id",
"players"."email" AS "players.email",
"players"."temp_email" AS "players.tempEmail",
"players"."password" AS "players.password",
"players"."password_salt" AS "players.passwordSalt",
"players"."action_count" AS "players.actionCount",
"players"."failed_action_count" AS "players.failedActionCount",
"players"."created_at" AS "players.created_at",
"players"."updated_at" AS "players.updated_at",
"players.game_player"."is_ready" AS
"players.game_player.isReady",
"players.game_player"."is_disabled" AS
"players.game_player.isDisabled",
"players.game_player"."power_preferences" AS
"players.game_player.powerPreferences",
"players.game_player"."power" AS "players.game_player.power",
"players.game_player"."created_at" AS
"players.game_player.created_at",
"players.game_player"."updated_at" AS
"players.game_player.updated_at",
"players.game_player"."game_id" AS
"players.game_player.game_id",
"players.game_player"."user_id" AS
"players.game_player.user_id"
FROM "games" AS "game"
INNER JOIN ("game_players" AS "players.game_player"
INNER JOIN "users" AS "players"
ON "players"."id" = "players.game_player"."user_id")
ON "game"."id" = "players.game_player"."game_id"
AND "players"."game_player.isdisabled" = false;
Clearly Sequelize is wrapping my constraint alias with incorrect quotes: 'players'.'game_player.isdisabled' should be 'players.game_player'.isdisabled. How can I revise my Sequelize code above to correctly query this column?
I got it, but only through manually browsing the repository's closed tickets and coming upon #4880.
Clauses using joined table columns that don't work out of the box can be wrapped in $. I honestly don't understand its magic, because I swear I don't see any documentation for it. Modifying my query above achieved what I wanted:
db.models.Game.findAll({
include: [{
model: db.models.User,
as: 'players',
where: { '$players.game_player.is_disabled$': false }
}]
}).nodeify(cb);
After searching around, I found that through.where can also be used:
db.models.Game.findAll({
include: [{
model: db.models.User,
as: 'players',
through: { where: { isDisabled: false } }
}]
})
Reference:
Is it possible to filter a query by the attributes in the association table with sequelize?
Eager loading with Many-to-Many relationships
Your query should be on the join table with the 'where' condition, and then you should use the 'include' clause to include the two other models, like this:
db.models.GamePlayer.findAll({
where: {isDisabled: false},
attributes: [],
include: [models.User, models.Game]
}).then(function(result){
....
});

sequelize saving multiple objects that assosiates

I wasnt quite sure what to call this question but here is my setup:
var AcademyModule = sequelize.define('academy_module', {
academy_id: {
type: DataTypes.INTEGER,
primaryKey: true
},
module_id: {
type: DataTypes.INTEGER,
primaryKey: true
},
module_module_type_id: DataTypes.INTEGER,
sort_number: DataTypes.INTEGER,
requirements_id: DataTypes.INTEGER
}, {freezeTableName: true,}
With the following assosiation:
Requirements = sequelize.define('requirements', {
id: DataTypes.INTEGER,
value: DataTypes.STRING,
requirement_type_id: DataTypes.INTEGER
}, {freezeTableName: true});
AcademyModule.belongsTo(Requirements, {foreignKey: 'requirements_id'});
Now as you can see from my table setup i would have to save a row in requirements table and then use the inserted id to insert into the academy_module table.
for this i created the following:
add: function (requirements,academyModule,onSuccess, onError) {
var academyModule = academyModule;
if(requirements == null) {
AcademyModule.build(this.dataValues)
.save().ok(onSuccess).error(onError);
} else {
if(requirements.requirement_type_id == '1') {
requirements.value = requirements.module.id;
}
Requirements.create(requirements).then(function(createdReq) {
var associationPromises = [];
associationPromises.push(AcademyModule.create(this.dataValues));
return sequelize.Promise.all(associationPromises);
}).success(onSuccess).error(onError);
}
}
However in the then function i am unable to reach the academyModule object that contains the values that needs to be inserted.
This is a repeating problem for me and i really want to know how it is possible to connect so they do it automatically without doing small hacks
i have scouted the documentations but i havnt been able to find a single example of the above (which i find rather odd seeing as this is a fairly normal situation)
Jan's method
I tried to solve it using Jan's elegant method
However i am getting an error saying:
academy_module is not associated to requirements!
My code looks like this as for now:
var academyModule = academyModule;
if(requirements == null)
{
AcademyModule.build(this.dataValues)
.save().ok(onSuccess).error(onError);
}
else
{
requirements.academy_module = academyModule;
Requirements.create(requirements, {
include: [AcademyModule]
});
}
Funny thing here is t hat i have the assosiation:
AcademyModule.belongsTo(Requirements, {foreignKey: 'requirements_id'});
Requirements.hasOne(AcademyModule, {foreignKey: 'requirements_id'});
First of all, I cannot answer why you are having problems accessing academyModule - it should be available via simple javascript scoping.
There are some unexplained bits of your code... You are pushing a single promise to associationPromises - Why not just return that promise?
You are accessing this.dataValues to create an AcademyModule instance - this will normally be undefined inside a promise handler.
Your issue could be solved more elegantly with nested creation, which is in 2.0.5: https://github.com/sequelize/sequelize/pull/3386
First of all you need to create the reverse association from Requirements --> AcademyModule
Requirements.hasOne(AcademyModule, {foreignKey: 'requirements_id'});
This is needed because the Requirement is the 'main model', which is used to create the AcademyModule
requirements.academy_module = academy_module;
Requirements.create(requirements, {
include: [AcademyModule]
});
By setting academy_module on the requirements object, and adding include as the 2nd parameter (similar to how you use includes for find) both are created and associated in one go.
You'll need to modify your models slightly. With your current model definition the table will contain an id column, but sequelzie does not know that it is the primary key. You either need to remove id completely from the Requirements model (in which case sequelize will add it automatically and mark it as primary key), or add primaryKey to it:
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true // optional - only if you actually want AI :)
}

Categories

Resources