Ignore default model attributes in SailsJS for 1 specific model - javascript

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.

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.

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){
....
});

Ember Data: Allow Embedded ID for Record

While using the RESTAdapter, I have an Organization model which is to be embedded in the response. It appears that the default implementation of the Ember.RESTAddapter sends the id, using the same model name, but not as an object (this currently 'works'):
POST/PUT api/v1/item/{id}
{
"item" {
id: "1029383829"
...
organization: "26044097612186763401268824297"
}
}
I have consulted the documentation, and found that the mixin DS.EmbeddedRecordsMixin should do this, coupled with declaring embedded: "always" on the attrs or the Serializer:
models/item.js
var Item = DS.Model.extend({
...,
organization: DS.belongsTo("organization", {embedded: "always"})
});
serializers/item.js:
var ItemSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
organisation: {serialize: "id", embedded: "always"}
}
}
);
However, when deserializing records, Ember Data complains, saying that it expected an object, but just got a string:
Assertion Failed: Expected an object as data in a call to push for
app#model:organization: , but was 26044097612186763401268824297
Ultimately, I would prefer a system, likened to sideloading, wherein an additional attribute, post-fixed "_id", describes the corresponding id of an embedded record:
{
"item": {
id: 1,
name: "name",
organization_id: "26044097612186763401268824297"
...
}
}
How can I allow serializing and deserializing embedded id sideloading for my Organization model?
You aren't actually embedding the record, you're just specifying the id, in that case you should mark it as async.
var Item = DS.Model.extend({
...,
organization: DS.belongsTo("organization", {async: true})
});
And remove your embedded records implementation.

EXTJS + Updating a store with the database ID after saving a grid

I'm trying to learn how to use the EXTJS grids for some simple CRUD operations over a table in a admin app.
I have a simple grid that allows someone to edit users, the store is defined as:
var userDataStore = new Ext.data.Store({
id: 'userDataStore',
autoSave: false,
batch: true,
proxy: new Ext.data.HttpProxy({
api: {
read: '/Admin/Users/All',
create: '/Admin/Users/Save',
update: '/Admin/Users/Save'
}
}),
reader: new Ext.data.JsonReader(
{
root: 'Data',
idProperty: 'ID',
totalProperty: 'total',
successProperty: 'success',
messageProperty: 'message'
}, [
{ name: 'ID', type: 'string', allowBlanks: false },
{ name: 'NT_ID', type: 'string', allowBlank: false },
{ name: 'EMail', type: 'string', allowBlank: false },
{ name: 'Name', type: 'string', allowBlank: false },
{ name: 'Enabled', type: 'bool', allowBlank: false },
{ name: 'CurrentRoleCode', type: 'string', allowBlank: false}]
),
writer: new Ext.data.JsonWriter(
{
encode: false,
writeAllFields: true,
listful: true
})
});
This is bound to a grid, and I am able to load and save users without issue. The save button looks like this:
var saveButton = new Ext.Button({
text: 'Save',
disabled: true,
handler: function() {
userDataStore.save();
pageState.ClearDirty();
saveButton.disable();
}
});
However, when creating a new user, the JSON POST for the user is posted to the same REST service end point as "Update", with the only difference being that no ID value is posted (as one is only set in the store when loading from the server).
This works, and I am able to create users.
The save REST service emits back the created row with the new database ID, and I was under the assumption that EXTJS would automatically bind the new generated database ID to the row. This allows the user to further edit that row, and cause an update instead of a insert.
Instead, the row continues to have a blank user ID, so an additional save creates another new user.
So either:
EXTJS is supposed to resolve generated row ID's automatically and I am just doing something wrong.
I am supposed to manually reload the grid after each save with an additional REST call.
I've been looking at EXTJS documentation and forums, but I am unclear on the proper approach.
Can someone clarify?
EDIT: I tried returning Success = True in JSON to match the SuccessProperty, however this still didn't seem to work.
EDIT #2: So far the only thing I've found that works is doing "userDataStore.reload()" after saving, however because I was returning the contents of the store back after saving, I was hoping that EXTJS would understand that and update the row values.
I've got an idea that may help you. Let't suppose that user added a new
record in grid, in that moment add a new property newRecOrderNo to the record to
identify the record after response. When user will post data to server after
inserting you must get a new ID and associate it to newRecOrderNo
(like Map<Integer,Integer>). Then return json object like that :
{
success : true,
newIdes : {
1 : 23,
2 : 34
}
}
Then when you get response do set proper IDs to records:
userDataStore.each(function(rec){
if(rec.data.newRecOrderNo){
rec.data.ID = response.newIdes[rec.data.newRecOrderNo];
delete rec.data.newRedOrderNo;
}
})
})
Yes, it sets id (and also other fields, if server returns modified values of them), if create ajax backend returns record with set id, at least in extjs 4.1. You should return inserted record, with id set, under 'root' key as json dictionary, in this example root is 'Data', i.e.:
{
"Data": {
"ID": 8932,
"NT_ID": 28738273,
...
"CurrentRoleCode": "aaa",
},
"success": true
}
You need reload store with new params in savebtn handler
like
store.reload();
of course you can add more params to load action

Categories

Resources