Meteor. Sorting my collection by a deeply nested value - mongodb

In my application I have a list of tiles representing each project in a portfolio. This is the main list view for the app and all projects are fetched from the collection without any sorting or ordering.
When I have an optional slug parameter specified in my route (for the category assigned to the project) I want to be able to display the projects within the UI that match that category first, and then display the other ones that don't match the category.
For reference, I have included the code for the route below:
/**
* Project list view (all projects) with optional
* filter parameter for showing projects only by
* their category name.
*/
this.route('list', {
path: '/:_category_slug?',
template: 'template_main',
action: function() {
if(this.ready()) {
this.render();
}
},
waitOn: function() {
return [
Meteor.subscribe('projects'),
Meteor.subscribe('formations'),
Meteor.subscribe('categories')
];
},
data: function() {
if(this.params._category_slug) {
/**
* Building up the query given the category slug and the language
*/
var query = {};
query['slug.' + App.language] = this.params._category_slug;
/**
* Grab the category given the query, so we can get its 'id'
*/
var category = App.models.categories.findOne(query);
/**
* This is the query I need to work on so that I can achieve what I want
*/
return App.models.projects.find({}).fetch();
}
else {
return App.models.projects.find({}).fetch();
}
},
yieldTemplates: {
'components_header': {to: 'header'},
'views_list': {to: 'content'},
'components_footer': {to: 'footer'}
}
});
For reference, I have also included a sample of the data for three projects that is relevant to this question.
{
"id": 10,
"slug": {
"en": "sample-english-slug",
},
"title": {
"en": "Sample English Title",
},
"description": {
"en": "A good description.",
},
"category_ids": [
{
"id": 5
},
{
"id": 6
}
],
},
{
"id": 12,
"slug": {
"en": "another-sample-slug",
},
"title": {
"en": "Another sample title",
},
"description": {
"en": "Sample description three",
},
"category_ids": [
{
"id": 1
},
{
"id": 4
}
],
},
{
"id": 11,
"slug": {
"en": "another-sample-slug",
},
"title": {
"en": "A sample title",
},
"description": {
"en": "Sample description",
},
"category_ids": [
{
"id": 2
},
{
"id": 5
}
],
}
So what I would want to do is make sure that given a category with an ID of 5, I want those first two projects to be the first two that appear.
Can this be done in meteor, without having to resort to writing extra logic in JS? One approach I did have once was to update each project from within the Client side collection (something I no longer do) and set a few extra attributes, then sort after that.
When dealing with syncing client and server collections, this is not really feasible.

From the mongodb docs:
Use the dot notation to match by specific fields in an embedded document. Equality matches for specific fields in an embedded document will select documents in the collection where the embedded document contains the specified fields with the specified values. The embedded document can contain additional fields.
I don't know if you can do it with a single query, but you can concat two complementary queries that use dot notation.
var selected = App.models.projects.find({'category_ids.id': category._id}).fetch();
var other = App.models.projects.find({'category_ids.id': {$ne: category._id}}).fetch();
return selected.concat(other);

Related

Search and update in array of objects MongoDB

I have a collection in MongoDB containing search history of a user where each document is stored like:
"_id": "user1"
searchHistory: {
"product1": [
{
"timestamp": 1623482432,
"query": {
"query": "chocolate",
"qty": 2
}
},
{
"timestamp": 1623481234,
"query": {
"query": "lindor",
"qty": 4
}
},
],
"product2": [
{
"timestamp": 1623473622,
"query": {
"query": "table",
"qty": 1
}
},
{
"timestamp": 1623438232,
"query": {
"query": "ike",
"qty": 1
}
},
]
}
Here _id of document acts like a foreign key to the user document in another collection.
I have backend running on nodejs and this function is used to store a new search history in the record.
exports.updateUserSearchCount = function (userId, productId, searchDetails) {
let addToSetData = {}
let key = `searchHistory.${productId}`
addToSetData[key] = { "timestamp": new Date().getTime(), "query": searchDetails }
return client.db("mydb").collection("userSearchHistory").updateOne({ "_id": userId }, { "$addToSet": addToSetData }, { upsert: true }, async (err, res) => {
})
}
Now, I want to get search history of a user based on query only using the db.find().
I want something like this:
db.find({"_id": "user1", "searchHistory.somewildcard.query": "some query"})
I need a wildcard which will replace ".somewildcard." to search in all products searched.
I saw a suggestion that we should store document like:
"_id": "user1"
searchHistory: [
{
"key": "product1",
"value": [
{
"timestamp": 1623482432,
"query": {
"query": "chocolate",
"qty": 2
}
}
]
}
]
However if I store document like this, then adding search history to existing document becomes a tideous and confusing task.
What should I do?
It's always a bad idea to save values are keys, for this exact reason you're facing. It heavily limits querying that field, obviously the trade off is that it makes updates much easier.
I personally recommend you do not save these searches in nested form at all, this will cause you scaling issues quite quickly, assuming these fields are indexed you will start seeing performance issues when the arrays get's too large ( few hundred searches ).
So my personal recommendation is for you to save it in a new collection like so:
{
"user_id": "1",
"key": "product1",
"timestamp": 1623482432,
"query": {
"query": "chocolate",
"qty": 2
}
}
Now querying a specific user or a specific product or even a query substring is all very easily supported by creating some basic indexes. an "update" in this case would just be to insert a new document which is also much faster.
If you still prefer to keep the nested structure, then I recommend you do switch to the recommended structure you posted, as you mentioned updates will become slightly more tedious, but you can still do it quite easily using arrayFilters for updating a specific element or just using $push for adding a new search

mongo db how to store multi relation like graph

I have to store some users and their group relations like below
So I am planning to create a collection like below
UserGroupRelation Collections
{
"user":String,
"Group":String"
}
example of collections for Super admin users
{
"user":"Adminuser-1",
"Group":"Group1"
}
{
"user":"Adminuser-1",
"Group":"Group2"
}
{
"user":"Adminuser-1",
"Group":"Group3"
}
where user & Group column is indexed and I will run below kind of query
1.Whenever I want to check whether given user has access to the given group
db.UserGroupRelation.find( { user: "Adminuser-1", Group: "Group2" })
2.Also I want to delete all the association whenever we delete group
db.UserGroupRelation.deleteMany({ Group: "Group2" })
3.Also find all the users of a group
db.UserGroupRelation.find( { Group: "Group2" })
4.Find Hierarchy?, with my Approach I am not able to find
But with this approach I am duplicating lot of data also in real time I may have 10000 groups and 1 million user so there would be performance issue. And with this I am not able to maintain a hierarchy like SuperAdmin->SubAdmin->user of same group
I checked with mongo tree but it is not fitting to this requirement. is there a better way to handle this requirement in mongodb .?
This is the structure your graphic requirements show. It does still lead to repetition though so you will need to change it. Read up on one-many relationships.
{
"superAdmin_ID": "001",
"groups": [
{
"_id": "0",
"groupNumber": "1",
"users": [
{
"_userKey": "1023"
"userName": "Fred"
},
{
"_userKey": "1024"
"userName": "Steve"
}
],
"subAdmin": {
"_adminKey": "55230"
"adminName": "Maverick"
},
},
{
"_id": "1",
"groupNumber": "2",
"users": [
{
"_userKey": "1023"
"userName": "Fred"
},
{
"_userKey": "4026"
"userName": "Ella"
}
],
"subAdmin": {
"_adminKey": "55230"
"adminName": "Maverick"
},
},
{
"_id": "2",
"groupNumber": "3",
"users": [
{
"_userKey": "7026"
"userName": "James"
}
],
"subAdmin": {
"_adminKey": "77780"
"adminName": "Chloe"
},
},
]
}
You can also make subAdmin an array if you need more than one subAdmin within a group.

Populated association fields on find route with blueprint option ON

My app has a "Categories" model.
Categories can be children of other categories.
So there is a "CategoriesAssociations" model.
Here is the code :
/* api/models/Categories.js */
module.exports = {
attributes: {
name: {
type: "string"
},
parents: {
collection: "categoriesassociations",
via: "child"
},
children: {
collection: "categoriesassociations",
via: "parent"
}
}
}
/* api/models/CategoriesAssociations.js */
module.exports = {
attributes: {
parent: {
model: "categories"
},
child: {
model: "categories"
}
}
}
Now when I use the find route aka /categories I get this :
[
{
"createdAt": "2015-08-24T14:16:46.662Z",
"updatedAt": "2015-08-24T14:24:23.819Z",
"name": null,
"id": "55db274e424996cc7e7512e2"
},
{
"createdAt": "2015-08-24T14:18:29.748Z",
"updatedAt": "2015-08-24T14:18:41.105Z",
"name": "test",
"id": "55db27b5424996cc7e7512e4"
}
]
So no trace of the parents and children properties.
The associations are indeed created in the database for when I request /categories/55db27b5424996cc7e7512e4/children I get this :
[
{
"parent": "55db27b5424996cc7e7512e4",
"child": "55db274e424996cc7e7512e2",
"createdAt": "2015-08-24T14:32:43.429Z",
"updatedAt": "2015-08-24T14:32:43.429Z",
"id": "55db2b0bc97cc73083017f60"
}
]
Sails docs states that the populate configuration key for blueprints defines :
Whether the blueprint controllers should populate model fetches with data from other models which are linked by associations. If you have a lot of data in one-to-many associations, leaving this on may result in very heavy api calls.
The value is true in my project but still, associations attributes don't get populated.
Did I misunderstand the docs or is there a problem with my project?
I use sails 0.11.x
The problem is I'm using sails-permissions which has overrides blueprints' populate config :
sails.config.blueprints.populate = false;
I opened an issue to know why it's done globally and how to fix the problem.

Sorting by document values in couchbase and scala

I am using couchbase and I have a document (product) that looks like:
{
"id": "5fe281c3-81b6-4eb5-96a1-331ff3b37c2c",
"defaultName": "default name",
"defaultDescription": "default description",
"references": {
"configuratorId": "1",
"seekId": "1",
"hsId": "1",
"fpId": "1"
},
"tenantProducts": {
"2": {
"adminRank": 1,
"systemRank": 15,
"categories": [
"3"
]
}
},
"docType": "product"
}
I wish to get all products (this json is product) that belong to certain category, So i've created the following view:
function (doc, meta) {
if(doc.docType == "product")
{
for (var tenant in doc.tenantProducts) {
var categories = doc.tenantProducts[tenant].categories
// emit(categories, doc);
for(i=0;i<categories.length;i++)
{
emit([tenant, categories[i]], doc);
}
}
}
}
So i can run the view with keys like:
[["tenantId", "Category1"]] //Can also have: [["tenant1", "Category1"],["tenant1", "Category2"] ]
My problem is that i receive the document, but i wish to sort the documents by their admin rank and system rank, these are 2 fields that exists in the "value".
I understand that the only solution would be to add those fields to my key, determine that my key would be from now:
[["tenantId", "Category1", "systemRank", "adminRank"]]
And after i get documents, i need to sort by the 3rd and 4th parameters of the key ?
I just want to make sure i understand this right.
Thanks

MongoDB: How to update a compound item of an array ensuring no duplicates

Here below is a hypothetical Users collection where more than one address is allowed:
{
"firstName": "Joe",
"lastName": "Grey",
...
"addresses":
[
{
"name": "Default",
"street": "..."
...
},
{
"name": "Home",
"street": "..."
...
},
{
"name": "Office",
"street": "..."
...
}
]
}
Every address has a name... which should be unique – e.g. there couldn't be two addresses named Default. If I want to update let's say the address at index 1 (Home), how do I ensure the update data does not contain names Default and Office?
I guess a two-steps approach (i.e. find and then update) wouldn't be very correct since data might be updated between the find and the subsequent update operation, isn't?
var renamed = 'Office'; // from user input
var users = getUserMongoCollection();
users.update({_id:userId, 'addresses.name': { $ne : renamed } },
{ $set : { 'addresses.1.name' : renamed } }, function(err){
//all done!
});
Find the record by ID, and only update it if the array doesn't contain the new name.