How to create many to many relationship between models in loopback? - node.js

I might be missing something obvious here but have spent hours on this and unable to find a solution.
Suppose I have an employee model
"properties": {
"name": {
"type": "string",
"required": true
},
"age": {
"type": "number",
"required": true
}
};
and its child model
"properties": {
"Code": {
"type": "string",
"required": true
},
"Desc": {
"type": "string",
"required": true
}
}
How do I create a many to many relationship between them?

You can add accepted properties passing accepts fields to remoteMethod options.
Read this page in the documentation.
module.exports = function(Task) {
fs.readdir(PATH_PROCESS+PATH_API, (err, o) => {
for(var c in o){
var i = o[c];
Task[i] = require(PATH_PROCESS+i+".js").lpb;
Task.remoteMethod(
    i, {
http: {path: ('/'+i), verb: 'post'},
returns: {arg: 'result', type: 'object'},
accepts: [
{arg: 'params', type: 'object', required: true, http: {source: 'body'},
description: (i+' params.')}
]
    }
    );
}
});
};

From the docs:
A hasManyThrough relation sets up a many-to-many connection with another model. This relation indicates that the declaring model can be matched with zero or more instances of another model by proceeding through a third model.
Use slc loopback:relation command to create relations between models. Don't forget to add through model when prompted (also explained in docs).
After you create relation between your models you will have to synchronize your changes with database using automigrate() or autoupdate().
Be careful when using automigrate because it will create or re-create your database which means that you can potentially loose your data.

Related

Not able to query for nested relations using dgraph-orm

I am using dgraph-orm for fetching nested relational values but it works for single level but not multiple level.
I am getting the page details but unable to fetch the avatar of the page.
Here is my snippet:
let posts = await PagePost.has('page_id', {
filter: {
page_id: {
uid_in: [page_id]
}
},
include: {
page_id: {
as: 'page',
include: {
avatar: {
as: 'avatar'
}
}
},
owner_id: {
as: 'postedBy'
}
},
order: [], // accepts order like the above example
first: perPage, // accepts first
offset: offset, // accepts offset
});
I am not getting avatar for the attribute page_id:
{
"uid": "0x75b4",
"title": "",
"content": "haha",
"created_at": "2019-09-23T08:50:52.957Z",
"status": true,
"page": [
{
"uid": "0x75ac",
"name": "Saregamaapaaaa...",
"description": "This a is place where you can listen ti thrilling music.",
"created_at": "2019-09-23T06:46:50.756Z",
"status": true
}
],
"postedBy": [
{
"uid": "0x3",
"first_name": "Mohit",
"last_name": "Talwar",
"created_at": "2019-07-11T11:37:33.853Z",
"status": true
}
]
}
Is there a support for multilevel field querying in the orm??
There was some issue with ORM itself it was not able to recognize the correct model name for multilevel includes and generating the wrong queries.
Fixed the same in version 1.2.4, please run npm update dgraph-orm --save to update your DgraphORM.
Thanks for the issue.

Sequelize - get proper path while validating associations of a model

I'm using Sequelize as an ORM for my project. I have this structure:
const Event = sequelize.define('event', {
// fields defined
});
const Question = sequelize.define('question', {
description: {
type: Sequelize.STRING,
allowNull: false,
defaultValue: '',
validate: {
notEmpty: { msg: 'Description should be set.' }
},
},
// other fields defined
});
Event.hasMany(Question);
Question.belongsTo(Event);
Then I create an instance of the Event model, with associate, like that:
const body = {
questions: [
{ description: '' } // is obviously invalid
],
// some other fields
}
const newEvent = await Event.create(body, {
include: [ Question ]
});
If I have validation errors for the Event instance itself, it returns SequelizeValidationError where I can see the path attribute for each ValidationErrorItem. However, when I have the validation error on a child model, the path attribute for this validation error is unclear:
{
"message": "Description should be set.",
"type": "Validation error",
"path": "description",
"value": "",
"origin": "FUNCTION",
"instance": {
"required": true,
"id": null,
"description": "",
"event_id": 60,
"updated_at": "2018-06-11T12:25:04.666Z",
"created_at": "2018-06-11T12:25:04.666Z"
},
"validatorKey": "notEmpty",
"validatorName": "notEmpty",
"validatorArgs": [
{
"msg": "Description should be set."
}
],
"__raw": {
"validatorName": "notEmpty",
"validatorArgs": [
{
"msg": "Description should be set."
}
]
}
The problem is, it's unclear what caused this error and which child is invalid. When I've used Mongoose as an ORM, if I'd do the same, the path attribute would be equal to something like questions.0.description, and that is way more clear, that way you can see which child is invalid.
So, my question is: is there a way to set up the path attribute while validating the child models?
Apparently it's not presented yet, I've filed an issue on Sequelize repo, here it is: https://github.com/sequelize/sequelize/issues/9524

Level 2 (related model) scope over REST - strongloop api

I found in the documentation that scopes enable you to specify commonly-used queries that you can reference as method calls on a model. Below i have a categories model. I am trying to create scopes that applies to the relation with model games. Unfortunately the below does nothing. How can I get scopes to apply to relation as shown below?
GET /Categories/{id}/games - This gets all games
common/models/category.json
"relations": {
"categories": {
"type": "hasMany",
"model": "game",
"foreignKey": ""
}
},
/common/models/game.json
"scopes": {
"mature": {"where": {"mature": true}}
},
"validations": [],
"relations": {
"category": {
"type": "belongsTo",
"model": "category",
"foreignKey": ""
}
}
I want to be able to get the data through endpoing: /Categories/{id}/games/mature
Table schema:
catgories
category_name category_id
------------- -----------
fighting 1001
racing 1002
sports 1003
games
game_id game_name category_id mature
----------- ------------ ----------- --------------
13KXZ74XL8M Tekken 10001 true
138XZ5LPJgM Forza 10002 false
Loopback api is based on swagger and scopes is a new concept in loopback.
Currently it doesn't have support for related methods i.e. you cannot access it from a related model i.e category (in your case) but only from model where the scope is defined i.e. game.
Thus you can achieve what you want right now using remote methods.
By Using Remote Methods. Loopback Remote Methods
common/models/category.js add the following lines.
module.exports = function(Category) {
Category.mature = function(id, filter, callback) {
var app = this.app;
var Game = app.models.Game;
if(filter === undefined){
filter = {};
}
filter.where = filter.where || {};
filter.where.categoryId = id;
filter.where.mature = true;
Game.find(filter, function(err, gameArr) {
if (err) return callback(err);
console.log(gameArr);
callback(null, gameArr);
});
}
Category.remoteMethod(
'mature', {
accepts: [{
arg: 'id',
type: 'number',
required: true
},
{
arg: 'filter',
type: 'object',
required: false
}
],
// 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'
}
}
);
};
Now in your common/models/category.json
Add this to your ACL property.
.....
.....
"acls": [
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "mature"
}
]
Now you can get all your game of mature type by get method
http://0.0.0.0:3000/api/categories/:id/games/mature
Or you can also try the API from loopback-explorer now.

nodejs/loopback: defining relationships doesn't seem to be reflected on database?

I am creating an API with strongloop loopback.
I have defined my models, and basically all is good there.
But I have a problem understanding how loopback deals with relationships.
Not all of my relationships I defined seem to really be reflected in the database and the interface.
For example, I have a model song, it
hasAndBelongsToMany albums
hasAndBelongsToMany playlists
hasAndBelongsToMany userplaylists
belongsTo artist
Here is /common/models/song.json
{
"name": "song",
"plural": "song",
"base": "PersistedModel",
"idInjection": true,
"properties": {
//some more properties of song
},
"validations": [],
"relations": {
"albums": {
"type": "hasAndBelongsToMany",
"model": "album",
"foreignKey": ""
},
"artist": {
"type": "belongsTo",
"model": "artist",
"foreignKey": ""
},
"playlists": {
"type": "hasAndBelongsToMany",
"model": "playlist",
"foreignKey": ""
},
"userplaylists": {
"type": "hasAndBelongsToMany",
"model": "userplaylist",
"foreignKey": ""
}
},
"acls": [],
"methods": []
}
But when I look at the postgresql table generated, I see:
title | character varying(1024) | not null
id | integer | not null default nextval('song_id_seq'::regclass)
#some other properties of song
artistid | integer |
Accordingly, the interface in loopbacks explorer at localhost:3000/explorer says:
post /song
Response Class
Model
Model Schema
{
"title": "",
//some other properties of song
"id": 0,
"artistId": 0
}
The question: Shouldn't there also be a songs, a playlists and a userplaylists variable??? Or have I been working too much in the NoSql world and now I forgot how to handle relationships?
BTW. I have a migrate script which I executed when adding the relationships to the models:
var path = require('path');
var app = require(path.resolve(__dirname, '../server'));
var dataSource = app.dataSources.cantoalegre_ps_DS;
dataSource.automigrate(function(err) {
if (err) {
console.log("Error migrating models: " + err);
}
else {
console.log("Successfully migrated models");
}
process.exit();
});
usually related data has the foreign key:
customer,
order belongs to customer so order has a column that contains the customer id.
Take care that at every change models have to be synchronized to the db by an autoupdate script.
module.exports = function(app) {
var ds = app.dataSources.pg;
ds.isActual('mymodel', function(err, actual) {
if (!actual) {
ds.autoupdate('mymodel', function(err, result) {
console.log("AUTOUPDATE mymodel",err,result);
});
}
});
};

Loopback REST findById doesn't work well

I'd like to use findById function through REST API.
I defined "ID" as string all constructed by number.
I try to find by ID, the system seems to recognize it number.
I can't use it when the ID is a big number over "9007199254740992" - max number of integer.
I'd like to use ID just as string.
Please tell me how to solve this problem.
Thank you,
--follow up--
My program is as follow.
Model - sample-model.json
{
"name": "SampleModel",
"base": "PersistedModel",
"idInjection": true,
"properties": {
"id": {
"type": "string",
"id": "true",
"required": true,
"doc": "MODEL ID"
},
"prop1": {
"type": "string",
"required": true
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": []
}
When I access to findById function through REST API, I always get following debug message.
strong-remoting:shared-method - findById - invoke with +11ms [ 9007199254740992, undefined, [Function: callback] ]
strong-remoting:shared-method - findById - result null +25ms
strong-remoting:rest-adapter Invoking rest.after for SampleModel.findById +6ms
express:router restRemoteMethodNotFound : /api/SampleModels/9007199254740993 +143ms
express:router restUrlNotFound : /api/SampleModels/9007199254740993 +8ms
express:router restErrorHandler : /api/SampleModels/9007199254740993 +2ms
strong-remoting:rest-adapter Error in GET /SampleModels/9007199254740993: Error: Unknown "SampleModel" id "9007199254740993".
I resolved my question by myself.
We can define arguments that the remote method accepts using "accepts" option.
Built-in findById function defines as follow at PersistedModel:
accepts: [
{ arg: 'id', type: 'any', description: 'Model id', required: true,
http: {source: 'path'}},
{ arg: 'filter', type: 'object',
description: 'Filter defining fields and include'}
],
When the type is defined any, the id changes to number by HttpContext.coerce function - if the id consists only number chars.
To solve this problem, I defines SampleModel.findByIdCustom and create another remote method as follow:
SampleModel.js
SampleModel.findByIdCustom = function(id, filter, cb) {
SampleModel.findById(id, filter, cb);
}
//Define remote method
SampleModel.remoteMethod(
'findByIdCustom',
{
description: 'Find a model instance by id from the data source.',
accessType: 'READ',
accepts: [
{ arg: 'id', type: 'string', description: 'Model id', required: true,
http: {source: 'path'}},
{ arg: 'filter', type: 'object',
description: 'Filter defining fields and include'}
],
returns: {arg: 'data', type: 'user', root: true},
http: {verb: 'get', path: '/:id'},
rest: {after: SampleModel.convertNullToNotFoundError},
isStatic: true
}
);
//disable built-in remote method
SampleMethod.disableRemoteMethod('findById', true);
Thank you,
Just set idInjection to false (so that loopback doesnt automatically add an id property to your model), then define a property with the following parameters:
{
"idInjection": false,
"properties": {
"id": {
"type": "string",
"id": true,
"generated": true
}
}
}

Resources