I have the following document:
{
"gameName":"Shooter",
"details":[
{
"submitted":1415215991387,
"author":"XYZ",
"subPlayer":{
"members":{
"squad1":[
{
"username":"John",
"deaths":0
}
]
},
"gameSlug":"0-shooter"
}
}
],
"userId":"foL9NpoZFq9AYmXyj",
"author":"Peter",
"submitted":1415215991608,
"lastModified":1415215991608,
"participants":[
"CXRR4sGf5AdvSjdgc",
"foL9NpoZFq9AYmXyj"
],
"slug":"1-shooterConv",
"_id":"p2QQ4TBwidjeZX6YS"
}
... and the following Meteor method:
Meteor.methods({
updateDeaths: function(gameSlug, user, squad) {
Stats.update({details.subPlayer.gameSlug: gameSlug}, ...}); // ???
}
});
My goal is to update the field deaths. The method has the argument user which is the user object (username = user.username). Furthermore, the argument squad is the squad name as string, e.g. squad1.
How can I do this?
Any help would be greatly appreciated.
You should use something like this:
db.collection.update(
{'userId':'foL9NpoZFq9AYmXyj', 'details.subPlayer.gameSlug':'0-shooter'},
{'$set':
{'details': [{
'subPlayer': {
'members': {
'squad1':[{'username':'John','death':1}]
}
}
}]
}
}
)
UPD:
Also, maybe a better way is to use find().snapshot().forEach(). But I don't know how Meteor supports it. Example:
Stats.find({'userId':'foL9NpoZFq9AYmXyj', 'details.subPlayer.gameSlug':'0-shooter' }).map(function (e) {
e.details.forEach(function (d) {
var squad = 'squad1';
d.subPlayer.members[squad].forEach(function (s) {
s.deaths = 10000;
});
});
Stats.update({'_id': e._id}, e);
});
Related
I have the following form object:
{
"name":"form name",
"forms":[
{
"name":"form 1",
"forms":[
...
]
}
]
}
Forms can contain forms inside them.
The idea was to print all the form names.
I did the following:
forms.forEach(form -> {
console.log(form.name);
form.forms.forEach(f -> {
console.log(f.name);
f.forms.forEach(...);
})
});
I have no idea how many forms can be inside so how can I do this to infinity.
Thanks
You should use recursion. Could be something like this:
function logFormName(data) {
if (data.name) {
console.log(data.name)
}
if (data.forms) {
return data.forms.forEach((form) => logFormName(form))
}
return;
}
Use recursion
const pr = (data) => {
console.log(data.name);
data.forms.forEach((f) => pr(f));
};
// forms data
let forms = {
"name":"form name",
"forms":[
{
"name":"form 1",
"forms":[
]
}
]
}
// recursive function to iterate through each form
// and print name of the form
function printForms(forms) {
if(forms.hasOwnProperty("forms")){
console.log(forms.name);
// iterate over each form
forms.forms.forEach(form => {
// recursive call
printForms(form);
})
}
}
// call function to print name of each form
printForms(forms)
I have a JSON object stored in "aggRequest", this is my JSON
{
"state":{
"controllerStates":[
],
"rulesetStates":[
{
"rulesetContext":{
"dialog_stack":[
{
"dialog_node":"root"
}
],
"dialog_turn_counter":1,
"dialog_request_counter":1,
"_node_output_map":{
"node_1_1504607088493":[
0
]
},
"branch_exited":true,
"branch_exited_reason":"completed"
},
"convId":"XXXX",
"modelRef":"XXXX"
}
],
"selfCallCount":0,
"sysTurnCount":2,
"userTurnCount":2
},
"context":{
"conversationContext":{
"brand":"XXXX",
"channel":"XXX"
},
"userData":{
"tokens":[
]
}
},
"XXXXX":"2.0",
"XXXXX":"XXXXX",
"XXXXX":"XXXXX",
"XXXXX":"XXXXX",
"XXXXX":"XXXXX",
"turnStart":"2018-08-10T15:21:36.075Z",
"turnEnd":"2018-08-10T15:21:36.076Z"
}
I am trying to set the turnEnd to a new date using moment, the date is being created correctly, and I believe my function is correct:
const update = require('immutability-helper');
function updatePropertyValue() {
const momentDateChange = getRunDateAsString();
update(aggRequest, {
$set:
{
turnEnd: `${momentDateChange}`,
},
});
console.log(aggRequest)
}
When I view the addRequest console.log, the date has not been updated. Why would that be?
The whole idea of immutability is that you can not change the data. Instead you creating a new one. Like in the example below:
const update = require('immutability-helper');
const data = {
"turnEnd":"2018-08-10T15:21:36.076Z"
};
const newData = update(data, { $set: { turnEnd: 'voala!' } });
console.log(data); // it is immutable, right?
console.log(newData); // it is new version of the data
In order to solve the issue change your code to use updated (new version) of your data:
const updateAggRequest = update(aggRequest, { $set: { turnEnd: momentDateChange } });
Given a database scheme like the following:
{
"lessons": {
"subjectId1": {
"lessonId2": { ... },
"lessonId4": { ... },
"lessonId6": { ... }
},
"subjectId2": {
"lessonId1": { ... },
"lessonId3": { ... },
"lessonId5": { ... },
"lessonId7": { ... }
}
}
}
How do I retrieve the id(s) of any subject(s) that include a given lessonId?
I came up with a function like this:
const refWithParent = database.ref("lessons");
const snapshot = await refWithParent.orderByChild(lessonId).limitToLast(1).once("value");
let firebaseId = null;
if (snapshot.val() !== null) {
snapshot.forEach(entry => {
firebaseId = entry.key;
return true;
});
}
return firebaseId;
This does work but I can't create an index in the database as the children are dynamically created.
Is there any easier way to retrieve the id?
As I understood you want to retrieve one index of any subject that contains a lesson with a specific id. If so, you can add a node "contains" to each subject into which you will put a key/value of the form: lessonId: true.
{
"lessons":{
"subjectId1":{
"contains":{
"lessonId2":true,
"lessonId4":true,
"lessonId6":true
},
"lessonId2":{ ... },
"lessonId4":{ ... },
"lessonId6":{ ... }
},
"subjectId2":{
"contains":{
"lessonId1":true,
"lessonId3":true,
"lessonId5":true,
"lessonId7":true
},
"lessonId1":{ ... },
"lessonId3":{ ... },
"lessonId5":{ ... },
"lessonId7":{ ... }
}
}
}
And then make a query (assuming lessonID is a variable in your code):
refWithParent.orderByChild(`contains/${lessonID)`).equalTo(true).limitToFirst(1).once('value');
and then check the obtained snaphost as follows:
if (snapshot.val() !== null)
{
let subjectId = snapshot.key;
//TODO something with subjectId...
}
Note, you must always add corresponding pair to "contains" node when you add a lesson to a subject. The same with removing.
P.S. If you want to obtain IDs of all subjects that contain given lesson, then simply remove .limitToFirst(1) from the query. That's all.
In Nosql like firebase you can denormalise or form your database as you want. I would advise that you make all your lesson names have a concatenation of it's subject when you creating them in order to avoid your querying of the database.
The codes below can help in getting a unique lesson id with a concatenation of the subject name before you create them.
let newId = database.ref("lessons").ref.push().key; //<-- get a new unique id
let newlessonname = 'Subject2_' + newId;
So your db would look like this
{
"lessons": {
"subjectId1": {
"subjectId1_lessonId2": { ... },
"subjectId1_lessonId4": { ... },
"subjectId1_lessonId6": { ... }
},
"subjectId2": {
"subjectId2_lessonId1": { ... },
"subjectId2_lessonId3": { ... },
"subjectId2_lessonId5": { ... },
"subjectId2_lessonId7": { ... }
}
}
}
Alternatively
You can include your Subjectid in the object of each lesson like this
{
"lessons": {
"subjectId1": {
"lessonId2": {subjectid:"subjectId1" ... },
"lessonId4": { ... },
"lessonId6": { ... }
},
"subjectId2": {
"lessonId1": { ... },
"lessonId3": { ... },
"lessonId5": { ... },
"lessonId7": { ... }
}
}
}
I'm trying to publish certain fields to a page based on a parameter of a document in a mongo collection. This is from the MongoDB manual: https://docs.mongodb.com/manual/reference/operator/aggregation/cond/
Does Meteor support conditional aggregation?
return Cases.find({
subscribers: { $in: [this.userId] }
}, {
fields: { $cond: [ { $eq: ['classified', true] } , Cases.privateFields, Cases.publicFields ] }
});
So, I realized I was approaching aggregates incorrectly. After doing some research on options to implement aggregation (meteorhacks) I decided to implement my own, which keeps the reactivity of the collection:
Extend Mongo.Collection
class CasesCollection extends Mongo.Collection {
conditionalFields(selector, modifier) {
let cond = modifier.fields[0],
pos = modifier.fields[1],
neg = modifier.fields[2];
if ( cond ) {
modifier = { fields: pos };
} else {
modifier = { fields: neg };
}
const cursor = this.find(selector, modifier);
return cursor;
}
}
Usage:
Meteor.publish('cases.dashboard', function casesDashboard() {
if (!this.userId) {
return this.ready();
}
return Cases.conditionalFields({
subscribers: { $in: [this.userId] }
}, {
fields: [ { $eq: ['classified', true] } , Cases.privateDashboardFields, Cases.dashboardFields ]
});
});
I'm using publish-composite to perform a reactive join (I'm sure the specific package does not matter). And I am seeing that the intermediate data gets pushed to the client.
In the following example:
Meteor.publishComposite('messages', function(userId) {
return {
find: function() {
return Meteor.users.find(
{ 'profile.connections.$': userId }
);
},
children: [{
find: function(user) {
return Messages.find({author: user._id});
}
}]
}
});
All the users that has userId in profile.connections get exposed to the client. I know that can create a mongodb projection so the sensitive stuff is not exposed. But I was wondering if I can just prevent the first find() query cursor from getting to the client at all.
Are you trying to only publish messages for a particular user if that user is a connection with the logged on user? If so, maybe something like this would work:
Meteor.publishComposite('messages', function(userId) {
return {
find: function() {
return Meteor.users.find(this.userId);
},
children: [{
find: function(user) {
return Meteor.users.find(
{ 'profile.connections.$': userid }
);
},
children: [{
find: function(connection, user) {
return Messages.find({author: connection._id});
}
}]
}]
};
});
That would be equivalent to something like :
Meteor.publish('message',function(userId) {
var user = Meteor.users.find({_id : this.userId, 'profile.connections.$' : userId});
if (!!user) {
return Messages.find({author: userId});
}
this.ready();
});