Sails JS - Deep populate of the same model and attribute - javascript

Let me explain it a little bit. The idea is to have categories and each category can have a parent category and a subcategory as well, but we don't know how deep this tree can go. For Example:
->Clothes
-->Men
--->Kids
---->Newborns
----->Etc, etc
-->Women
-->Unisex
So I thought that my Category.js model could have these attributes:
module.exports = {
attributes: {
name: {
type: 'string',
required: true,
unique: true
},
products: {
collection: 'product',
via: 'category'
},
parentCategory: {
model: 'category'
},
subCategories: {
collection: 'category',
via: 'parentCategory'
}
}
};
And when I get all my categories:
Category.find({}).populate('subCategories').exec(........
I get a list of all categories and its subcategories, but I also want to have the subcategories of the subcategories, just like the tree hierarchy I wrote before, but instead I'm getting
Clothes { subCategories: [Men:{}, Women:{}, Unisex:{}] }
Men { .....
But inside of Clothes, I have Men, that's correct. But now inside of this Men object I don't have "Kids".
Am I being clear?

Populate method only works in one level (at the moment), so you can't populate and object and then populate other one inside. I had a similar feature and I found and wrote this code, maybe it can help you:
return Object.findOne({
id: id
}).populateAll().then(function (result) {
var otherObject= otherObject.find(result.id).then(function (otherResult) {
return otherResult;
});
return [result, otherResult];
}).spread(function (house, otherResult) {
result= result.toObject();
result.otherResult= otherResult;
return result;
});

Related

Retrive some columns of relations in typeorm

I need to retrieve just some columns of relations in typeorm query.
I have an entity Environment that has an relation with Document, I want select environment with just url of document, how to do this in typeorm findOne/findAndCount methods?
To do that you have to use a querybuilder, here's an example:
return this.createQueryBuilder('environment') // use this if the query used inside of your entity's repository or getRepository(Environment)...
.select(["environment.id","environment.xx","environment.xx","document.url"])
.leftJoin("environment.document", "document")
.where("environment.id = :id ", { id: id })
.getOne();
Sorry I can't add comment to post above. If you by not parsed data mean something like "environment.id" instead of "id"
try this:
return this.createQueryBuilder("environment")
.getRepository(Environment)
.select([
"environment.id AS id",
"environment.xx AS xx",
"document.url AS url",
])
.leftJoin("environment.document", "document")
.where("environment.id = :id ", { id: id })
.getRawOne();
Here is the code that works for me, and it doesn't require using the QueryBuilder. I'm using the EntityManager approach, so assuming you have one of those from an existing DataSource, try this:
const environment = await this.entityManager.findOne(Environment, {
select: {
document: {
url: true,
}
},
relations: {
document: true
},
where: {
id: environmentId
},
});
Even though the Environment attributes are not specified in the select clause, my experience is that they are all returned in the results, along with document.url.
In one of the applications that I'm working on, I have the need to bring back attributes from doubled-nested relationships, and I've gotten that to work in a similar way, shown below.
Assuming an object model where an Episode has many CareTeamMembers, and each CareTeamMember has a User, something like the code below will fetch all episodes (all attributes) along with the first and last name of the associated Users:
const episodes = await this.entityManager.find(Episode, {
select: {
careTeamMembers: {
id: true, // Required for this to work
user: {
id: true,
firstName: true,
lastName: true,
},
}
},
relations: {
careTeamMembers: {
user: true,
}
},
where: {
deleted: false,
},
});
For some reason, I have to include at least one attribute from the CareTeamMembers entity itself (I'm using the id) for this approach to work.

Sequelize belongsToMany get source model by querying on target model

I have two models Brand and Campaign.
A Brand can have many Campaigns
export default(sequelize, DataTypes)=> {
const Brand = sequelize.define('Brand', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
})
Brand.associate = models=> {
Brand.belongsToMany(models.Campaign, {
through: models.CampaignBrand,
foreignKey: 'brand',
})
}
return Brand
}
A Campaign can also have many Brand
export default(sequelize, DataTypes)=> {
const Campaign = sequelize.define('Campaign', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
})
Campaign.associate = models=> {
Campaign.belongsToMany(models.Brand, {
through: models.CampaignBrand,
foreignKey: 'campaign',
})
}
return Campaign
}
And here is through model:
export default(sequelize, DataTypes)=> {
const CampaignBrand = sequelize.define('CampaignBrand', {
// see enums
status: {
type: DataTypes.INTEGER,
allowNull: false,
},
roleText: {
type: DataTypes.STRING,
},
})
CampaignBrand.associate = models=> {
CampaignBrand.belongsTo(models.Campaign, {
foreignKey: 'campaign',
targetKey: 'id',
onDelete: 'CASCADE',
})
}
return CampaignBrand
}
In case I want to get Campaigns by brand. What should I do?
I have tried query likes document mentioned but it does not work for me
With Belongs-To-Many you can query based on through relation and select specific attributes. For example using findAll with through
User.findAll({
include: [{
model: Project,
through: {
attributes: ['createdAt', 'startedAt', 'finishedAt'],
where: {completed: true}
}
}]
});
I have found some ways to work around, but it is not what I am looking for:
SOLUTION 1:
Update belongsToMany Brand to hasMany CampaignBrand and the query by CampaignBrand.brand
SOLUTION 2:
Get Campaign by querying Brand
Any other advices?
Dialect: postgres
Database version: 9.4
Sequelize version: 4.2.1
I think you don't need this association in the the through model:
CampaignBrand.associate = models=> {
CampaignBrand.belongsTo(models.Campaign, {
foreignKey: 'campaign',
targetKey: 'id',
onDelete: 'CASCADE',
})
}
You already have the belongsToMany association in the definitions of Brand and Campaign, so I think you just need to create the CampaignBrand model with your status and roleText attributes.
As I understand it, then you can query brands through campaigns and it should return each brand element and its associated campaigns,
Brand.findAll({
include: [{
model: Campaign
}]
});
This answer is kinda old, but with belongstomany associations, you can use mixins.
https://sequelize.org/api/v6/class/src/associations/belongs-to-many.js~belongstomany
const campaigns = await CampaignModel.findAll();
const campaignsBrandsModels = await campaigns.getBrandsModels({
limit:1,
through: {where:{pivot_field:'pivot_value'} } // << You want this, i think!
});
Now, remember that, the mixins (get, set, count...) set their name with inflection library, so, a way to set the names without error (or without spending a lot of time searching the name in in the console) is setting an 'as' alias in the Association
CampaignBrand.belongsToMany(models.Campaign, {
through: models.CampaignBrand,
foreignKey: 'brand',
as: 'BrandsModels' //<<<< This is Super important! at least for easy usage
})
You can achieve this with an include in the findAll method too, BUT!
the 'limit: #' part will not work! (Will give you the error of "This only works with hasMany because separate: true")
I really hope this can help anyone, seya!

Model not associated with Model Sequelize

I'm trying to esatblish a One-To-Many relationship between the tables: Exam and Exam_Questions, using Sequelize.
Even though the tables are created properly and I can see them in PhpMyAdmin, I keep getting the following error in console:
Error: exam_question is not associated to exam!
exam.js
...
const ExamQuestion = require('./exam-question');
...
const Exam = sequelizeInstance.define("exam", {
name: { type: Sequelize.STRING },
date: { type: Sequelize.DATE }
});
// Build the model relations
Exam.hasMany(ExamQuestion, { as: "Questions" });
exam-question.js
const ExamQuestion = Sequelize.db.define("exam_question", {
correct_answer: {
type: Sequelize.STRING
},
text: {
type: Sequelize.STRING
}
});
module.exports = ExamQuestion;
To solve the error, I tried:
ExamQuestion.belongsTo(Exam);
But that doesn't change anything.
The query is:
Exam.findAll({
include: [ExamQuestion]
})
How to fix this problem and get the Exam objects including their questions?
TL;DR
For some very non-intuitive reason this seems to be happening because of the as property. To fix the problem, simply remove the as property:
Exam.hasMany(ExamQuestion);
Fixing the methods
By default, after removing the as property, Sequelize will automagically add the following methods: getExam_questions, addExam_question and so on.
They look quite bad: camel and snake cases mixed up together.
To solve that, we can easily define the singular and plural names in the ExamQuestion model options (the third argument):
const ExamQuestion = Sequelize.db.define("exam_question", {
correct_answer: {
type: Sequelize.STRING
},
text: {
type: Sequelize.STRING
}
}, {
name: {
singular: "question",
plural: "questions"
}
});
This will dictate Sequelize to create methods such as getQuestions and addQuestion instead of getExam_questions and addExam_question.

Query and map one-way referenced children and sub-children from a parent with mongoose in Keystone.js

I got three models with one-to-many relationships. Simple tree. What I need is a simple, efficient way to query a structured relationship tree, preferably similar to mongoose's .populate() which I cant't use since I don't have id's on the parent model. I suppose keeping children ids on parent would be efficient, but Keystone doesn't provide this functionality by default and I am unable to write an update callback to control relational changes. I tried and wasted too much time, finding myself astray while maybe what I'm trying to achieve is much easier, but I just can't see it.
Here's the stripped code:
Category model
Category.add({
name: { type: String}
});
Category.relationship({ path: 'sections', ref: 'Section', refPath: 'category' });
Section model, child of a category
Section.add({
name: { type: String, unique: true, required: true}
category: { type: Types.Relationship, ref: 'Category', many: false}
});
Section.relationship({ path: 'articles', ref: 'Article', refPath: 'section'});
Article model, child of the Section
Article.add({
name: { type: String, required: true}
section: { type: Types.Relationship, ref: 'Section', many: false }
});
I want to get a structured view of a category with all children and their respective sub-children like this:
[ { _id: 57483c6bad451a1f293486a0,
name: 'Test Category',
sections: [
{ _id: 57483cbbad451a1f293486a1,
name: 'Test Section',
articles: [
{ _id: 57483c6bad451a1f293486a0,
name: 'Test Category' }
]
]
} ]
So that's how I did it. Not at all efficient but at least it's working. I didn't put anything in first-level parent since I need only one.
// Load current category
view.on('init', function (next) {
var q = keystone.list('Category').model.findOne({
key: locals.filters.category
});
q.exec(function (err, result) {
if (err || !results.length) {
return next(err);
}
locals.data.category = result;
locals.section = locals.data.category.name.toLowerCase();
next(err);
});
});
// Load sections and articles inside of them
view.on('init', function (next) {
var q = keystone.list('Section').model.find().where('category').in([locals.data.category]).sort('sortOrder').exec(function(err, results) {
if (err || !results.length) {
return next(err);
}
async.each(results, function(section, next) {
keystone.list('Article').model.find().where('section').in([section.id]).sort('sortOrder').exec(function(err, articles){
var s = section;
if (articles.length) {
s.articles = articles;
locals.data.sections.push(s);
} else {
locals.data.sections.push(s);
}
});
}, function(err) {
next(err);
});
next(err);
});
});
But now I'm getting another issue. I'm using Jade 1.11.0 for templates and sometimes it doesnt't show the data in the view.
I will post another question for this issue.

NodeJS|SailsJS|Waterline: Automatic Model Generation in One-To-Many--To--Many-To-Many Relationships

What Type of Relationship is needed below, and How Can I Leverage SailsjS/Waterline to Simplify My Find and Update Queries?
In my app, I have Lists (List-Model), Items (Item-Model), and Provisions (Provision-Model). The app's intention is to manage Inventory. As so, Items are discrete -- meaning, there can only be one of the same Item in the Items-collection (primaryKey is set on item.name). A single List shares a One-To-Many relationship with Items -- vicariously through Provisions. A Provision is simply a [discrete] Item's details for a given List. In this case, a Provision is only accessible using both a List-Id and and Item-Id (list.id + item.id === provision.compositePrimaryKey).
My issue is the complexity in dealing with find and update operations. With an alternative approach, I had just a Many-To-Many relationship between Lists and Items (with Items dominant) -- and this generated an Item upon List-Updates and aggregated Items with List.find(...).populate('items'). This was nearly ideal but I needed Provisions in the mix. Now (with the Schema below), the same relationship exists between Lists and Provisions -- List updates and 'populates' generate and aggregate a Provision with the correct list.id, but I was expecting Sails/Waterline to generate an Item-Model-Instance because Provision contains a item: { model: 'item' } attribute. Here is my current approach:
Provisions:
//api/models/Provision.js
module.exports = {
schema: true,
attributes: {
...
list: { model: 'list' },
item: { model: 'item' },
quantity: { type: 'integer' },
...
}
};
Lists:
//api/models/List.js
module.exports = {
schema: true,
attributes: {
...
items: { collection: 'provision', via: 'list' },
...
}
};
Items:
//api/models/Item.js
module.exports = {
schema: true,
attributes: {
...
name: { type: 'string', primaryKey: true }
lists: { collection: 'provision', via: 'item' },
inStock: { type: 'integer' },
...
}
};
I would like to generate a Provision -- if only through updating a list -- and have a discrete Item found or created automatically -- and hydrate an Item in the Provision-instance when performing a find on a List, as each List's Provision will have a model link to Item.
How can I do this, or is there a better design which I should employ?
Currently primaryKey must be id, look after sails hooks blueprint at actionUtil that primary key must be id field yet. So your Item.js should be
module.exports = {
schema: true,
attributes: {
...
// change it's column name if necessary fo your DB scheme
id: { type: 'string', unique: true, primaryKey: true, columnName: 'name' }
lists: { collection: 'provision', via: 'item' },
inStock: { type: 'integer' },
...
}
};
I don't pretty understand what and how do you want to approach. But using Lifescycle callbacks may help you. For example in your statement I would like to generate a Provision -- if only through updating a list -- and have a discrete Item found or created automatically, so use beforeUpdate, beforeCreate, etc. that meets your need.

Categories