When sails default model attributes get their values? - node.js

When sails fill default global attributes which we added on config/models.js ,
default settings looks like :
attributes: {
id: { type: 'number', autoIncrement: true },
createdAt: { type: 'number', autoCreatedAt: true },
updatedAt: { type: 'number', autoUpdatedAt: true },
}
Now if we add sth like creatorId to this default attributes , how we should fill it once for all our models ?
attributes: {
id: { type: 'number', autoIncrement: true },
createdAt: { type: 'number', autoCreatedAt: true },
updatedAt: { type: 'number', autoUpdatedAt: true },
creatorId: { type: 'number'}
}
After this change , all models have creatorId with 0 value , how I can set userId to all of my models creatorId before save without repeating my self?

In the controller you are creating the entry in the database this should be quite straight forward. Let's assume that you have two models, User, which comes with Sails built-in authentication, and a Thing, something that someone can own.
In the Thing model, I'd change the ownerId to be owner and associate it with the User model like so:
attributes: {
id: { ... },
createdAt: { ... },
updatedAt: { ... },
owner: {
model: 'User',
required: yes // Enable this when all the stuff in the db has this set
},
}
This creates an association or one-to-many relationship if you know database terminology.
Now in the controller where you create your object to be inserted:
Thing.create({
someAttribute: inputs.someValue,
someOtherAttribute: inputs.someOtherValue,
owner: this.req.me.id
});
If you want to use the created object right away, append .fetch() to the chain after .create({...}) like so:
var thing = await Thing.create({ ... }).fetch();
Let me know if something is unclear.
I'd actually recommend you invest the $9 in buying the SailsJS course. It's an official course, taught by the creator of SailsJS, Mike McNeil. It takes you from npm i sails -g to pushing to production on the Heroku cloud platform. It teaches basic Vue (parasails flavour), using MailGun, Stripe payments, and more. They link to the course on the site here
Update
Did some further digging, and was inspired by a couple of similar cases.
What you can do is expand your model with a custom method that wraps the .create() method. This method can receive the request object from your controllers, but doing this, rather than the previous suggestion, will probably be more work than just adding ownerId: this.req.me.id, to existing calls. I1ll demonstrate anyway.
// Your model
module.exports = {
attributes: { ... },
proxyCreate(req, callback) {
if(!req.body.ownerId){
req.body.ownerId = req.me.id // or req.user.id, cant remember
// which works here
}
Thing.create(request.body, callback);
}
}
And in your controller:
...
// Change from:
Thing.create(req.body);
// To:
Thing.proxyCreate(req);
...
Update #2
Another idea I had was adding the middleware on a per-route basis. I don't know the complexity of your routes, but you can create a custom middleware for only those routes.
In router.js you edit your routes (I'll show one for brevity):
....
'POST /api/v1/things/upload-thing': [
{ action: 'helpers/add-userid-to-ownerid' },
{ action: 'new-thing' }
],
....
In helpers/add-userid-to-ownerid:
module.exports: {
fn: function(req, res) {
if(!req.body.ownerId){
req.body.ownerId = req.me.id;
}
}
}

Related

Create one to many relationship object in database using Sequelize

I've searched a lot and didn't find any answer.
My english isn't that good so i might not used the best keywords...
Here is my problem, is there a way to insert in one query an object like this ?
I have a one to many relationship between two objects define like this :
foo can have many bar
bar can have one foo
export const foo = sequelize.define('foos', {
idfoo: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
value: Sequelize.STRING,
valueInt: Sequelize.INTEGER
})
export const bar = sequelize.define('bars', {
idbar: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
value: Sequelize.STRING,
valueInt: Sequelize.INTEGER
})
foo.hasMany(bar, {
foreignKey: 'idfoo'
});
bar.belongsTo(foo);
Imagine you got this object you want to insert in your db. How would you do it ?
{
"value": "test",
"valueInt": 5,
"bars": [
{
"value": "test",
"valueInt": 6
},
{
"value": "test",
"valueInt": 7
}
]
}
I have read Sequelize documentation i didn't find any clue of this issue (or maybe i've missed it ).
I wasn't be able to make this solution works Sequelize : One-to-Many relationship not working when inserting values
Actually here is my Sequelize create function, i thought Sequelize handled all the imports alone (pretty naive i guess)...
static async post(body) {
await foo.create(body);
}
First, you need to indicate the same foreignKey option for both paired associations:
foo.hasMany(bar, {
foreignKey: 'idfoo'
});
bar.belongsTo(foo, {
foreignKey: 'idfoo'
});
That way Sequelize can make sure these two models linked by the same fields from both ends.
Second, in order to insert both main and child records together you need to indicate the include option in the create call with bar model:
static async post(body) {
await foo.create(body, {
include: [bar]
});
}

Sequelize: Includes not working on defaultScope when querying an object through belongsToMany association

I am trying to move an include object to the default scope of my model CommodityContract. However, when I do this, sequelize throws me an error upon querying results:
'Include unexpected. Element has to be either a Model, an Association or an object.'
Here is my block of code, which works perfectly if I put it inside a custom scope.
defaultScope: {
include: [
{
model: sequelize.models.CommodityContractPayments,
required: false
},
{
model: sequelize.models.CommodityContractTreatmentCharges,
required: false
},
{
model: sequelize.models.CommodityContractRefiningCharges,
required: false
},
{
model: sequelize.models.CommodityContractPriceParticipation,
required: false
},
]
}
The problem is that I am trying to query this specific model as a through object in a belongs-to-many association, and therefore I'm not sure how to apply a scope when querying, unless I do another separate query, which I was trying to avoid.
The main query looks like this:
const contract = await Contract.findOne(
{
where: { id },
include: [
sequelize.models.Commodity,
sequelize.models.ContractSubstance,
sequelize.models.Client,
sequelize.models.Invoice.scope('default_includes')
]
})
Where Commodity is linked to Contract through CommodityContract:
Commodity.belongsToMany(models.Contract, {
through: models.CommodityContract,
foreignKey: 'commodity_id',
otherKey: 'contract_id'
})
And the scope 'default_includes' is defined as such, within the model Invoice:
scopes: {
default_includes: () => {
return {
include: [
{
model: sequelize.models.InvoiceVersion,
required: false
},
{
model: sequelize.models.Contract,
required: false,
},
{
model: sequelize.models.User,
required: false,
},
]
}
}
}
I have had this problem quite a few times before and it annoys me greatly. Sometimes the includes work in a defaultScope, but sometimes it doesn't. Something tells me this has to do with the many-to-many association, but I am not sure.
Any help would be greatly appreciated!

Sequelize how to make a join request?

I'm trying to make joined queries with Sequelize.
That's my db :
What I need is to select all of my relations and get this kind of result:
[
{
id: 1,
State: true,
FK_User: {
id: 2,
Name: "my name"
},
FK_Team: {
id: 3,
Name: "team name"
}
},
...
]
But today I've got this result:
[
{
id: 1,
State: true,
FK_User: 2,
FK_Team: 3
},
...
]
For each of my relations, I've go to do another request to get datas ...
So I putted a look in this Stack and in the doc.
Then I made this code :
let User = this.instance.define("User", {
Name: {
type: this.libraries.orm.STRING,
allowNull: false
}
});
let Team = this.instance.define("Team", {
Name: {
type: this.libraries.orm.STRING,
allowNull: false
}
});
let Relation = this.instance.define("Relation", {
State: {
type: this.libraries.orm.BOOLEAN,
allowNull: false,
defaultValue: 0
}
});
Relation.hasOne(User, {as: "FK_User", foreignKey: "id"});
Relation.hasOne(Team, {as: "FK_Team", foreignKey: "id"});
With this code, I haven't got any relation between tables... So I added theses two lines. I don't understand why I need to make a two direction relation, because I don't need to access Relation From User and Team ...
User.belongsTo(Relation, {foreignKey: 'FK_User_id'});
Team.belongsTo(Relation, {foreignKey: 'FK_Team_id'});
When I do that, I've a FK_User_id in the User table and a FK_Team_id in the Team table ... I don't know how to make this simple relation and get all I need with my futur request and the include: [User, Team]} line.
User.hasOne(Relation);
Team.hasOne(Relation);
Relation.belongsTo(Team);
Relation.belongsTo(User);
This code seems to work.
I don't know why ...
Here your associations are setup correctly you can join it with include :
Relation.findAll({
where : {
state : true
}
include:[
{
model : User
},
{
model : Team
}
]
})

Paginate Result of related models

This is another follow up to a previous question. There are only two models involved: category and game which share a hasMany relation. Example: The endpoint /Categories/1001/games/mature list all games of fighting category that have mature flag set to true. However, I am unable to paginate the response. What is the proper way to paginate based on the code shown below? I would like to only display 10 results at a time.
common/models/category.js
Category.mature = function(id, callback) {
var app = this.app;
var Game = app.models.game;
Category.findById(id, {}, function(err, category) {
if (err) return callback(err);
//Now call the Game find method
Game.find({
"where": {
categoryId: id,
mature: true
}
}, function(err, gameArr) {
if (err) return callback(err);
callback(null, gameArr);
});
});
}
Category.remoteMethod(
'mature', {
accepts: [{
arg: 'id',
type: 'number',
required: true
}],
// mixing ':id' into the rest url allows $owner to be determined and used for access control
http: {
path: '/:id/games/mature',
verb: 'get'
},
returns: {
arg: 'games',
type: 'array'
}
}
);
Tables/Models
catgories
category_name category_id
------------- -----------
fighting 1001
racing 1002
sports 1003
games
game_id game_name category_id mature description published_date
----------- ------------ ----------- ------- ----------- --------------
13KXZ74XL8M Tekken 10001 true Published by Namco. 1994
138XZ5LPJgM Forza 10002 false Published by Microsoft 2005
API Result:
games [
{
gameName: 'Tekken',
gameInfo :
[
{
description : 'Published by Namco.',
published_date : '1994'
}
],
categorName: 'fighting',
categoryId: 1001,
mature: true
}
.....
]
If you are stuck with the code above, you will need to download the full set of Games and then paginate on the frontend. Without being able to send in limit and skip values to your queries, there is no other way.
If you can change this code and add arguments to the remote method, the underlying mysql connector format with the Node API would look like this:
Game.find({
"where": {
categoryId: id,
mature: true
},
"limit": 10, // 10 per page
"skip": 10 // hard coded for page 2, this needs to be passed in
}, function(err, gameArr) {
if (err) return callback(err);
callback(null, gameArr);
});
The values for limit and skip should be added to your remote method definition and registration, and then the UI can send in dynamic values based on the page displayed.
The page on the skip filter has an example for pagination as well:
https://docs.strongloop.com/display/public/LB/Skip+filter
If this will be using some sort of UI library like the Angular SDK, you can make the same query at the Controller level using the lb-ng generator script and the models created there. You could also add the pagination values there, no need for a custom remote method.
Update:
To add the skip and limit numbers to your remote method, you need to update your remote method signature, the accepts array would change to
accepts: [
{
arg: 'id',
type: 'number',
required: true
},
{
arg: 'skip',
type: 'number',
required: true
},
{
arg: 'limit',
type: 'number',
required: true
}
]
And then add the same 2 new arguments to the method itself:
Category.mature = function(id, skip, limit, callback) {
// ...your code...
});
You can call it using query parameters at this point, like ?skip=10&limit=10 just appended to the existing path. Or, you can change the http.path to something like
path: '/:id/games/mature/:skip/:limit'
and then call the resource with /1/games/mature/10/10 to achieve the same result with a parameterized URL.

Build query to get chat conversation messages in sails js

Lets say i have the following model schema described as below to add some chat functionality between 2 users in my sails app. For instance if i have user A sending a message to user B, then user B will reply back to user A which will always create 2 conversations between both users. So my question is how can i query both conversations to get messages from A and B. I tried something like this but maybe theres a simple logic.
// User Model
attributes: {
username: {
type: 'string',
required: true,
unique: true,
},
conversations_sender: {
collection: conversation,
via: 'sender'
},
conversations_recipient: {
collection: conversation,
via: 'recipient'
}
}
// Conversation model
attributes: {
sender: {
model: user
},
recipient: {
model: user
},
messages: {
collection: 'message',
via: 'conversation'
}
}
// Message model
attributes: {
text: {
type: 'string'
},
conversation: {
model: 'conversation'
},
}
// Conversation Controller
get: function(req, res) {
var params = {
or : [
{
sender: req.param('sender'),
recipient: req.param('recipient')
},
{
sender: req.param('recipient'),
recipient: req.param('sender')
}
]
}
Conversation.find(params) ...
}
You should rethink your schema a bit, see this link that has a good database design for your needs:
http://www.9lessons.info/2013/05/message-conversation-database-design.html
You should be able then to fetch all the messages with the 2 user ids like this:
Conversation.findAll({sender: ..., receiver: ...})
Also you will need a timestamp for the messages, in the future you'll want to sort them somehow and also make the nice 'Read yesterday' feature

Resources