SailsJS relations not working as expected - mongodb

SailsJS with MongoDB adapter not working as expected. I have following relations defined:
Post.js:
module.exports = {
connection: 'mongodb',
attributes: {
title: 'string',
categories: {
collection: 'postCategory',
via: 'posts'
}
}
};
PostCategory.js
module.exports = {
connection: 'mongodb',
attributes: {
name: 'string',
posts: {
collection: 'post',
via: 'categories'
}
}
};
When I query without criteria and populate categories like:
Post.find()
.populate('categories')
.then(...)
it gives me correct result, the document has categories nested.
But when I try to pass criteria it returns no result. e.g.
Post.find({categories: 'food'})
.populate('categories')
.then(...)
Note: I inserted category _id as string (food, travel, etc) in database
Please help

You will not get any results because in categories it will store the _id.
You can get the results by doing the following query.
PostCategory.find({name: 'food'})
.populate('posts')
.then(...)

Related

Does migrating from MongoDB to SQL breaks basic Strapi queries?

const PostPopulateObj = () => ({
path: 'posts',
model: 'Post',
select: 'id order title alias',
...({ match: { published_at: { $ne: null } } })
})
const GroupPopulateObj = {
path: 'groups',
model: 'Group',
select: 'id order label posts groups'
}
module.exports = {
async getNavigationByAlias(ctx) {
const { alias } = ctx.params
const nav = await strapi.query('navigation').find({ alias }, [
{
...GroupPopulateObj,
populate: [PostPopulateObj, {
...GroupPopulateObj,
populate: PostPopulateObj
}]
},
PostPopulateObj
])
return nav.length > 0 ? nav : null
}
};
I have this and using PostgresSQL instead of MongoDB breaks the above query. But my understanding is that it shouldn't break basic queries and only custom queries as shown in the documentations.
https://docs-v3.strapi.io/developer-docs/latest/development/backend-customization.html#queries
https://github.com/strapi/migration-scripts/tree/main/v3-mongodb-v3-sql
I used the scripts and was able to repopulate the db, but like I said I am getting different results, where I am getting some generic default post (converted null post?) instead of 2 specific posts. The post now returned by Postgres seems to not be inside the db, not sure what's going on, but for some reason it's not returning an error.
A little below the section, they mention custom queries and how to use Bookshelf and Mongoose. I used the Mongoose library for custom queries in my understanding, but the above doesn't use Bookshelf or Mongoose at all, so it should work.

Sails.js populate with where

I need to select users with dogs (pets with type equal 'dog')
var User = Waterline.Collection.extend({
identity: 'user',
attributes: {
firstName: 'string',
lastName: 'string',
pets: {
collection: 'pet',
via: 'owner'
}
}
});
var Pet = Waterline.Collection.extend({
identity: 'pet',
attributes: {
type: 'string',
name: 'string',
owner: {
model: 'user'
}
}
});
I didn't find any examples, I tried like this
User.find().populate('pets', {type: 'dog'}).exec(err, users) ...
and this
User.find().where({'pets.type': 'dog'}).populate('pets').exec(err, users) ...
but that does not work
Would be greate if result users array will has no pets records
Did you try this?
User.find().populate('pets', {
where: {
type: 'dog'
}
}).exec(err, users)...
If you don't need to query users and just need the query for dogs. You could just as easily reverse the query.
Pet.find({type: 'dog'}).populate('users').exec(err, petsWithUsers)
What you are looking for hasn't been implemented in waterline (Sails ORM) yet, check issue #266 for more details.
User.find().populate('pets', {type: 'dog'}).exec(err, users) ...
This will return all users (User.find()) and only populate pets of type dog (populate('pets', {type: 'dog'})). So you'll have users without dogs in your results.
User.find().where({'pets.type': 'dog'}).populate('pets').exec(err, users) ...
Waterline does not support dot (.) notation. Sails-mongo does have some support for it due to MongoDB support.
Finally, if you are using one of the SQL adapters you may work around this by doing a raw sql query using .query() (docs).

Sails.js - Many-to-Many fails to save

I have a simple need to add tags to patients. I followed the Sails and Waterline documentation concerning many-to-many associations, but it's failing at some point (no errors). I'm using MongoDB for data storage. Code below:
Tag Model
module.exports = {
attributes: {
name: 'STRING',
color: {
type: 'STRING',
defaultsTo: '#777777'
},
tagged: {
collection: 'patient',
via: 'tags',
dominant: true
}
}
};
Patient Model
module.exports = {
attributes: {
name: 'STRING',
tags: {
collection: 'tag',
via: 'tagged'
}
}
};
And this is the controller method that tries to associate data:
module.exports = {
addToPatient: function(req, res) {
Patient.findOne({id: req.param('patientId')}).exec(function(err, patient) {
// Queue up a record to be inserted into the join table
patient.tags.add(req.param('tagId'));
// Save the user, creating the new associations in the join table
patient.save(function(err) {});
});
res.send("tag assigned");
}
};
I've inspected the responses at various breaks and everything seems to be passing just fine. The patient is found. The save function shows a tag association in the patient object, but nothing is added in the database. I assume I will see either a join table being created or something in the patient/tag collections to signify an association, but I see nothing. I'm so very confused. If I do an HTTP get, I'm presented with a "tag assigned" response. What am I missing?
Works fine for me, but you're right in the tag and patient collections, you won't see a populated field with the associations. You'll see new join collections that are created that contains the relationships like #sgress454 pointed out.

Sails.js Associations many-to-many relation

I have a problem with many to many relationship in sails and if somebody can help me it would be great :D
I have two models User and Message and the associations is defined as follows
api/models/User.js
module.exports = {
attributes: {
messages: {
collection: 'message',
via: 'owner',
dominant: true
}
}
};
api/models/Message.js
module.exports = {
attributes: {
owner: {
model: 'user'.
via: 'messages'
}
}
};
I checked in the DB (MySQL) the middle table is created and i successfully inserted the data but i cant retrive the data.
i start sails console and type
User
.find()
.populate('messages')
.exec(function(err,r){
while(r.length){
var thisUser=r.pop();
console.log(thisUser.toJSON())
}
});
But i always recive no data for messages, the messages field is always empty messages: []
my Current version of sails is 0.10.0-rc4

Mongoose: Populate a populated field

I'm using MongoDB as a log keeper for my app to then sync mobile clients. I have this models set up in NodeJS:
var UserArticle = new Schema({
date: { type: Number, default: Math.round((new Date()).getTime() / 1000) }, //Timestamp!
user: [{type: Schema.ObjectId, ref: "User"}],
article: [{type: Schema.ObjectId, ref: "Article"}],
place: Number,
read: Number,
starred: Number,
source: String
});
mongoose.model("UserArticle",UserArticle);
var Log = new Schema({
user: [{type: Schema.ObjectId, ref: "User"}],
action: Number, // O => Insert, 1 => Update, 2 => Delete
uarticle: [{type: Schema.ObjectId, ref: "UserArticle"}],
timestamp: { type: Number, default: Math.round((new Date()).getTime() / 1000) }
});
mongoose.model("Log",Log);
When I want to retrive the log I use the follwing code:
var log = mongoose.model('Log');
log
.where("user", req.session.user)
.desc("timestamp")
.populate("uarticle")
.populate("uarticle.article")
.run(function (err, articles) {
if (err) {
console.log(err);
res.send(500);
return;
}
res.json(articles);
As you can see, I want mongoose to populate the "uarticle" field from the Log collection and, then, I want to populate the "article" field of the UserArticle ("uarticle").
But, using this code, Mongoose only populates "uarticle" using the UserArticle Model, but not the article field inside of uarticle.
Is it possible to accomplish it using Mongoose and populate() or I should do something else?
Thank you,
From what I've checked in the documentation and from what I hear from you, this cannot be achieved, but you can populate the "uarticle.article" documents yourself in the callback function.
However I want to point out another aspect which I consider more important. You have documents in collection A which reference collection B, and in collection B's documents you have another reference to documents in collection C.
You are either doing this wrong (I'm referring to the database structure), or you should be using a relational database such as MySQL here. MongoDB's power relies in the fact you can embed more information in documents, thus having to make lesser queries (having your data in a single collection). While referencing something is ok, having a reference and then another reference doesn't seem like you're taking the full advantage of MongoDB here.
Perhaps you would like to share your situation and the database structure so we could help you out more.
You can use the mongoose-deep-populate plugin to do this. Usage:
User.find({}, function (err, users) {
User.deepPopulate(users, 'uarticle.article', function (err, users) {
// now each user document includes uarticle and each uarticle includes article
})
})
Disclaimer: I'm the author of the plugin.
I faced the same problem,but after hours of efforts i find the solution.It can be without using any external plugin:)
applicantListToExport: function (query, callback) {
this
.find(query).select({'advtId': 0})
.populate({
path: 'influId',
model: 'influencer',
select: { '_id': 1,'user':1},
populate: {
path: 'userid',
model: 'User'
}
})
.populate('campaignId',{'campaignTitle':1})
.exec(callback);
}
Mongoose v5.5.5 seems to allow populate on a populated document.
You can even provide an array of multiple fields to populate on the populated document
var batch = await mstsBatchModel.findOne({_id: req.body.batchId})
.populate({path: 'loggedInUser', select: 'fname lname', model: 'userModel'})
.populate({path: 'invoiceIdArray', model: 'invoiceModel',
populate: [
{path: 'updatedBy', select: 'fname lname', model: 'userModel'},
{path: 'createdBy', select: 'fname lname', model: 'userModel'},
{path: 'aircraftId', select: 'tailNum', model: 'aircraftModel'}
]});
how about something like:
populate_deep = function(type, instance, complete, seen)
{
if (!seen)
seen = {};
if (seen[instance._id])
{
complete();
return;
}
seen[instance._id] = true;
// use meta util to get all "references" from the schema
var refs = meta.get_references(meta.schema(type));
if (!refs)
{
complete();
return;
}
var opts = [];
for (var i=0; i<refs.length; i++)
opts.push({path: refs[i].name, model: refs[i].ref});
mongoose.model(type).populate(instance, opts, function(err,o){
utils.forEach(refs, function (ref, next) {
if (ref.is_array)
utils.forEach(o[ref.name], function (v, lnext) {
populate_deep(ref.ref_type, v, lnext, seen);
}, next);
else
populate_deep(ref.ref_type, o[ref.name], next, seen);
}, complete);
});
}
meta utils is rough... want the src?
or you can simply pass an obj to the populate as:
const myFilterObj = {};
const populateObj = {
path: "parentFileds",
populate: {
path: "childFileds",
select: "childFiledsToSelect"
},
select: "parentFiledsToSelect"
};
Model.find(myFilterObj)
.populate(populateObj).exec((err, data) => console.log(data) );