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

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

Related

Find a document by an object id in that doc in mongoDB node js express js

Hey this i want to fetch the products of a certain user so i added the user in the model by Object id , but when i was setting the router callBack function i didn't know how to do it !
{
"_id": {
"$oid": "62f250d3e2533cf4ca2ad7f3"
},
"image": "https://eshop-server.herokuapp.com/public/uploads/Surface-Pro-7-128GB-1604665289664.png",
"brand": "ASUS",
"price": {
"$numberInt": "250"
},
"rating": null,
"numReviews": null,
"isFeatured": false,
"name": "Surface Pro 7 128GB",
"description": "Tablet PC - Intel Core i3 1005G1 Ice Lake, touchscreen 12.3\" IPS 2736 × 1824, RAM 4GB LPDDR4X, Intel UHD Graphics",
"category": {
"$oid": "5f15d54cf3a046427a1c26e3"
},
"user": {
"$oid": "62f0e99be78e33cc5662d1f4"
},
"reviews": [{
"avatar": "https://pbs.twimg.com/profile_images/1760228143/Vetor-Johnny-Bravo-Vetorizado-Corel_400x400.jpg",
"name": "Johnny Bravo",
"review": "Amazing experience all around"
}, {
"avatar": "https://vignette.wikia.nocookie.net/asterix/images/3/3c/Asterix.png",
"name": "Asterix",
"review": "It's very hard. Only managed with resource to magic potion"
}, {
"avatar": "https://vignette.wikia.nocookie.net/looneytunes/images/7/7f/Coyote.gif",
"name": "Wild Coyote",
"review": "All my trickery and tools paid of. Thank ACME for suggesting this product to me"
}],
"countInStock": {
"$numberInt": "8"
},
"__v": {
"$numberInt": "0"
},
"richDescription": "eatures a detachable keyboard and offers the comfort of a classic laptop as well as the convenience of a tablet.&nbsp",
"images": ["http://localhost:3000/public/uploads/5f15d8852a025143f9593a7c-1604665316399.png", "http://localhost:3000/public/uploads/5f15d8852a025143f9593a7c-1604665316400.jpeg"]
}
as You can see the user object id is included so how can i find it ! this i what i tried but it didn't work
router.get(`/:id/products`, async (req, res) => {
const productList= await Product.find({user.$oid:req.params.id}).populate("user");
if (!productList) {
res.status(500).json({ success: false });
}
res.send(productList);
});
Solution
Replace Product.find({user.$oid:req.params.id})
with Product.find({user._id: mongo.ObjectId(req.params.id)})
Explanation
$oid is a way to represent ObjectId in JSON. $oid is not a property that exists on the document. We need to convert the id received in request to an ObjectId and match it with the _id field of the document.
Further
Wrap the conversion into a try/catch or something similar. Converting a string to an ObjectId can fail if the string is not a proper representation of an ObjectId.
const mongo = require('mongodb');
let idAsObjectId;
try {
idAsObjectId = mongo.ObjectId(req.params.id)
} catch (err) {
// handle error
}

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.

MongoDB: Query model and check if document contains object or not, then mark / group result

I have a Model called Post, witch contains an property array with user-ids for users that have liked this post.
Now, i need to query the post model, and mark the returned results with likedBySelf true/false for use in by client - is this possible?
I dont have to store the likedBySelf property in the database, just modify the results to have that property.
A temporary solution i found was to do 2 queries, one that finds the posts that is liked by user x, and the ones that have not been liked by user x, and en map (setting likedBySelf true/false) and combine the 2 arrays and return the combined array. But this gives some limitations to other query functions such as limit and skip.
So now my queries looks like this:
var notLikedByQuery = Post.find({likedBy: {$ne: req.body.user._id}})
var likedByQuery = Post.find({likedBy: req.body.user._id})
(I'm using the Mongoose lib)
PS. A typical post can look like this (JSON):
{
"_id": {
"$oid": "55fc463c83b2d2501f563544"
},
"__t": "Post",
"groupId": {
"$oid": "55fc463c83b2d2501f563545"
},
"inactiveAfter": {
"$date": "2015-09-25T17:13:32.426Z"
},
"imageUrl": "https://hootappprodstorage.blob.core.windows.net/devphotos/55fc463b83b2d2501f563543.jpeg",
"createdBy": {
"$oid": "55c49e2d40b3b5b80cbe9a03"
},
"inactive": false,
"recentComments": [],
"likes": 8,
"likedBy": [
{
"$oid": "558b2ce70553f7e807f636c7"
},
{
"$oid": "559e8573ed7c830c0a677c36"
},
{
"$oid": "559e85bced7c830c0a677c43"
},
{
"$oid": "559e854bed7c830c0a677c32"
},
{
"$oid": "559e85abed7c830c0a677c40"
},
{
"$oid": "55911104be2f86e81d0fb573"
},
{
"$oid": "559e858fed7c830c0a677c3b"
},
{
"$oid": "559e8586ed7c830c0a677c3a"
}
],
"location": {
"type": "Point",
"coordinates": [
10.01941398718396,
60.96738099591897
]
},
"updatedAt": {
"$date": "2015-09-22T08:45:41.480Z"
},
"createdAt": {
"$date": "2015-09-18T17:13:32.426Z"
},
"__v": 8
}
#tskippe you can use a method like following to process whether the post is liked by the user himself and call the function anywhere you want.
var processIsLiked = function(postId, userId, doc, next){
var q = Post.find({post_id: postId});
q.lean().exec(function(err,res){
if(err) return utils.handleErr(err, res);
else {
if(_.find(doc.post.likedBy,userId)){ //if LikedBy array contains the user
doc.post.isLiked = true;
} else {
doc.post.isLiked = false;
}
});
next(doc);
}
});
}
Because you are using q.lean() you dont need to actually persist the data. You need to just process it , add isLiked field in the post and send back the response. **note that we are manuplating doc directly. Also you chan tweek it to accept doc containing array of posts and iterating it and attach an isLiked field to each post.
I found that MongoDB's aggregation with $project tequnique was my best bet. So i wrote up an aggregation like this.
Explanation:
Since i want to keep the entire document, but $project purpose is to modify the docs, thus you have to specify the properties you want to keep. A simple way of keeping all the properties is to use "$$ROOT".
So i define a $project, set all my original properties to doc: "$$ROOT", then create a new property "likedBySelf", which is marked true / false if a specified USERID is in the $likedBy set.
I think that this is more clean and simple, than querying every single model after a query to set a likedBySelf flag. It may not be faster, but its cleaner.
Model.aggregate([
{ $project: {
doc: "$$ROOT",
likedBySelf: {
$cond: {
"if": { "$setIsSubset": [
[USERID],
"$likedBy"
]},
"then": true,
"else": false
}
}
}}
]);

Strongloop: how to fetch a related model using code (not REST API)

Having trouble getting a related model on a User object. Users have a to-many relation with Customers.
Can I not just say User.customers to grab the customers associated with a User?
I have tried
User.find({include:'customers'}, function(err, user) {
//now what?
//user.customers does not work; there is no Customer array returned.
});
Happy to look in the docs for this but I can't find where this is written.
Thank you
In the loopback examples they often create a "user" model as an extension of loopbacks "User" model.
Note the lower case u.
I had trouble accessing the model when using "User" not "user"
user.json
{
"name": "user",
"base": "User",
"idInjection": true,
"emailVerificationRequired": false,
"properties": {
"createdAt": {
"type": "date"
},
"updatedAt": {
"type": "date"
},
.......
user.js
module.exports = function(user) {
user.observe('before save', function(ctx, next){
if (ctx.instance) {
//If created at is defined
if(ctx.instance.createdAt){
ctx.instance.updatedAt = new Date();
}
else{
ctx.instance.createdAt = ctx.instance.updatedAt = new Date();
}
} else {
ctx.data.updatedAt = new Date();
}
next();
})`

CouchDB - Basic design for joins data

Just playing around with CouchDb and CouchApp, what amazing tech ! Very surprised, seams to be very powerful. After playing and reading a lot, as I'm a old relational database user, I still questioning myself about how to design some basic things.
Here is my question :
1/ I have a document of type 'user' and document of type 'item'
2/ My Couchdb contains the following documents :
{ "_id": "...", "type": "user", "email":"u1#gmail.com" ... }
{ "_id": "...", "type": "user", "email":"u2#gmail.com" ... }
{ "_id": "...", "type": "user", "email":"u3#gmail.com" ... }
{ "_id": "...", "type": "user", "email":"u4#gmail.com" ... }
{ "_id": "...", "type": "item", "title":"My title",
created_by:"u1#gmail.com", modified_by:"u3#gmail.com" }
3/ Now I want a view or something to fetch document by type=item and _id with informations for each users (creator & modifier)
I have seen a way to emulate a simple join here : http://www.cmlenz.net/archives/2007/10/couchdb-joins
But I can't adapt it for two joins, I'am playing around with key format since few hours, testing lots things, but nothing works.
I think, I'm missing something important with CouchDb map/reduce, if someone has some help I will appreciate it.
PS : Don't answer me to insert 'user' document inside 'item' document. This not my question.
Relax, relax ... :-)
I think you should put _ids in created_by and modified_by:
{ "_id": "u1", "type": "user", "email":"u1#gmail.com" ... }
{ "_id": "u2", "type": "user", "email":"u2#gmail.com" ... }
{ "_id": "u3", "type": "user", "email":"u3#gmail.com" ... }
{ "_id": "u4", "type": "user", "email":"u4#gmail.com" ... }
{ "_id": "anitem", "type": "item", "title":"My title",
created_by:"u1", modified_by:"u3" }
so you can use the following map function and query it with ?key="anitem"&include_docs=true:
function(doc) {
if (doc.type === "item") {
emit(doc._id, 1);
emit(doc._id, { _id: doc.created_by });
emit(doc._id, { _id: doc.modified_by });
}
}
You can read Jan Lehnardt's post about it for more details.
As a side note, I generally put the type in the _id so it is easier to get unique keys, and you do not need a view if you only want to filter by type:
{ "_id": "user/username1", "email":"u1#gmail.com" ... }
{ "_id": "user/username2", "email":"u2#gmail.com" ... }
{ "_id": "user/username3", "email":"u3#gmail.com" ... }
{ "_id": "user/username4", "email":"u4#gmail.com" ... }
{ "_id": "item/itemid1", "title":"My title",
created_by:"user/username1", modified_by:"user/username3" }
and the map function is
function(doc) {
if (doc._id.slice(0, 4) === "item/") {
emit(doc._id, 1);
emit(doc._id, { _id: doc.created_by });
emit(doc._id, { _id: doc.modified_by });
}
}
UPDATE: Due to bug COUCHDB-1229, use of / in doc._id may cause problems. Depending on your use-case, it may be better to use another separator, e.g. : or _.
What do you mean here "two joins"? Is this "tableA join tableB ON ... AND ..."?
Now I want a view or something to fetch document by type=item and _id with informations for each users
This can be done without two/more joins.
Anyway, my advise is to divide your data in 2 databases: items & users. The example above suits only few simple tasks. But when your data grows big (say 10K users & 100K items) it becomes very hard to process your data and it's kinda sucks that all your documents differ from each other with only one field.

Resources