How to reactively aggregate mongodb in meteor - javascript

I am newbie to meteor. I had established the publish / subscribe concept. I am facing the following error while performing aggregation reactively.
client code :
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import './main.html';
Template.header.helpers({
'tasks': function () {
console.log("tasks helper called : ");
Meteor.subscribe('reportTotals', function() {
console.log(clientReport.find().fetch());
});
return ['1', '2'];
},
});
server code
import { Meteor } from 'meteor/meteor';
import {ReactiveAggregate} from 'meteor/jcbernack:reactive-aggregate';
Meteor.startup(() => {
console.log("Server Started");
// code to run on server at startup
var MONGO_URL = "mongodb://127.0.0.1:27017/test";
});
Meteor.publish("reportTotals", function() {
// Remember, ReactiveAggregate doesn't return anything
this.autorun(function () {
ReactiveAggregate(this, atm_data, [{
// assuming our Reports collection have the fields: hours, books
$group: {
'_id': null,
'bottles_used': {
// In this case, we're running summation.
$sum: '$BOTTLE_USED'
// $sum: 1
}
}
}, {
$project: {
// an id can be added here, but when omitted,
// it is created automatically on the fly for you
bottles_used: '$bottles_used'
} // Send the aggregation to the 'clientReport' collection available for client use
}], { clientCollection: "clientReport" });
});
});
Exception in flushing DDP buffered writes: Error: Expected to find a document to change
at Object.update (http://localhost:3000/packages/mongo.js?hash=ed0b13aca2f180af120dd0cfdba64ac79e2a624f:246:29)
...
Thanks in advance.

You don't have a client side collection. Also, you need to subscribe before you call this helper.
Try this
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import './main.html';
var clientReport = new Mongo.Collection('clientReport');
Meteor.subscribe("reportTotals");
Template.header.helpers({
'tasks': function () {
console.log("tasks helper called : ");
console.log(clientReport.find().fetch());
},
});
You also don't need the pipeline and no autorun on server code, try this:
AtmData = new Mongo.Collection('atmdata');
Meteor.startup(() => {
// code to run on server at startup
/* AtmData.insert({
bottles_used: 123,
}); */
});
Meteor.publish("reportTotals", function() {
// Remember, ReactiveAggregate doesn't return anything
ReactiveAggregate(this, AtmData, [{
// assuming our Reports collection have the fields: hours, books
$group: {
'_id': null,
'bottles_used': {
// In this case, we're running summation.
$sum: '$bottles_used'
// $sum: 1
}
}
}], { clientCollection: "clientReport" });
});

I made an NPM package here: meteor-publish-join. Its main purpose is to publish expensive-aggregated values after a certain amount of time. Hope it will help in your case.

Related

How to keep data synchronized in ember using ember-apollo-client?

I have an app built using Ember and ember-apollo-client.
// templates/collaborators.hbs
// opens an ember-bootstrap modal
{{#bs-button type="success" onClick=(action (mut createCollaborator) true)}}Create collaborator{{/bs-button}}
// submit button in modal triggers "createCollaborator" in controller
{{#each model.collaborators as |collaborator|}}
{{collaborator.firstName}} {{collaborator.lastName}}
{{/each}}
// routes/collaborators.js
import Route from '#ember/routing/route';
import { RouteQueryManager } from 'ember-apollo-client';
import query from '../gql/collaborators/queries/listing';
export default Route.extend(RouteQueryManager, {
model() {
return this.get('apollo').watchQuery({ query });
}
});
// controllers/collaborator.js
export default Controller.extend({
apollo: service(),
actions: {
createCollaborator() {
let variables = {
firstName: this.firstName,
lastName: this.lastName,
hireDate: this.hireDate
}
return this.get('apollo').mutate({ mutation, variables }, 'createCollaborator')
.then(() => {
this.set('firstName', '');
this.set('lastName', '');
this.set('hireDate', '');
});
}
}
});
Currently, after creating a collaborator the data is stale and needs a browser refresh in order to update. I'd like the changes to be visible on the collaborators list right away.
From what I understood, in order to use GraphQL with Ember, I should use either Ember Data with ember-graphql-adapter OR just ember-apollo-client. I went on with apollo because of its better documentation.
I dont think I quite understood how to do that. Should I somehow use the store combined with watchQuery from apollo? Or is it something else?
LATER EDIT
Adi almost nailed it.
mutationResult actually needs to be the mutation itself.
second param in store.writeQuery should be either data: { cachedData } or data as below.
Leaving this here as it might help others.
return this.get('apollo').mutate({
mutation: createCollaborator,
variables,
update: (store, { data: { createCollaborator } }) => {
const data = store.readQuery({ query })
data.collaborators.push(createCollaborator);
store.writeQuery({ query, data });
}
}, createCollaborator');
You can use the apollo imperative store API similar to this:
return this.get('apollo').mutate(
{
mutation,
variables,
update: (store, { data: {mutationResult} }) => {
const cachedData = store.readyQuery({query: allCollaborators})
const newCollaborator = mutationResult; //this is the result of your mutation
store.writeQuery({query: allCollaborators, cachedData.push(newCollaborator)})
}
}, 'createCollaborator')

Can't access Mongo db using Meteor-Blaze

Using this code in a file "reports.js" in the same folder of "reports.html", I get 0 elements in the array returned. Not sure I am missing any extra declaration but also I can't use "import { Mongo } from 'meteor/mongo';" without having error "Use of reserved word 'import'".
Meteor version 1.2.1
JS
Tasks = new Mongo.Collection('tasks');
if(Meteor.isClient){
Template.reports.rendered = function(){
if(Session.get("animateChild")){
$(".reports-page").addClass("ng-enter");
setTimeout(function(){
$(".reports-page").addClass("ng-enter-active");
}, 300);
setTimeout(function(){
$(".reports-page").removeClass("ng-enter");
$(".reports-page").removeClass("ng-enter-active");
}, 600);
}
};
}
Template.dashboardd.helpers({
options() {
return Tasks.find({});
},
});
HTML
<Template name="dashboardd">
{{#each options}}
<p><label>{{text}}</label></p>
{{/each}}
</Template>
AFAIK meteor 1.2 don't supports Imports. Improrts are new module added i meteor 1.3 or 1.4 im not sure.
I allways use this 3 Elements when work with collections (4 if we take collection declaration)
You should do on server side some publish and allows like:
Meteor.publish("Tasks ", function (selector, options) {
//you can remove condition if you dont have accounts-package
if (this.userId) {
return Tasks .find(selector || {}, options || {});
}
});
And some Allows
Once again if you dont have Meteor-accounts you can simply - return true, on inserts, updates etc. But I don't must say that is not safety :P
Tasks.allow({
insert: function (userId, doc) {
return userId;
},
update: function (userId, doc) {
return userId;
},
remove: function (userId) {
return userId;
},
});
On Client side some subscribe
Template.YourTemplateName.onCreated(function () {
this.autorun(() => {
this.subscribe('Tasks', { _id: Template.currentData()._id });
//or
this.subscribe('Tasks');
//but this solution can kill Browser when you collection will have a thousand elements. Allways try to not subscribe all collection. Only Needed fields/objects
});
});

Meteor/mongoDb find and update

I'm just learning Meteor and I made a little app but I have a problem with find() and update() collection on server side
For example:
if (Meteor.isServer) {
function getCollection1(){
return collection_1.find({}).fetch({});
}
....
Meteor.methods({
start: function(id) {
datas = getCollection1();
Collection2.update({_id:id}, {$set: {datas: datas}}); //sometimes it's ok(3/4)
}
...
}
Or when I await, I have an error
if (Meteor.isServer) {
async function getCollection1(){
return await collection_1.find({}).fetch({});
}
....
Meteor.methods({
start: function(id) {
getCollection1().then(function(datas){
Rooms.update({_id: id}, {$set: {datas: datas}},function(err){
console.log(err);//error: Meteor code must always run within a fiber
});
});
}
...
}
What did I do wrong?
EDIT
it seems to work well with Fiber()
if (Meteor.isServer) {
Fiber = Npm.require('fibers')
function getCollection1(){
return collection1.find({}).fetch({});
}
function setCollection2(id){
Fiber(function() {
datas = getCollection1();
collection2.update({_id: id}, {$set: {datas: datas}},function(err){
console.log(err);
});
}).run();
}
....
Meteor.methods({
start: function(id) {
setCollection2(id);
}
...
}
With the async/await version, you do not need to use Fiber. Instead you could this Meteor function: Meteor.bindEnvironment to make it work:
// ...
getCollection1().then(Meteor.bindEnvironment(function(datas){
Rooms.update(// ...);
}));
// ...
For a more understandable explanation, you could consult this video:
What is Meteor.bindEnvironment?
Based on the code you provided, my guess is that for the first example the "Collection2.update" is taking place before GetCollection1() is completed. If this is indeed the case, then on the client side when you subscribe to the collection be sure to have a way to "wait" for the subscription to complete.
For example something like this on the client side where "start()" is called...
const handle = Meteor.subscribe( 'collection_1' );
if ( handle.ready() ) {
Meteor.call('start');
} else {
const allusers = Meteor.users.find().fetch();
onData( null, {allusers} );
}
Again, this is my best guess as you did not post the error you received with your first code chunk and I can't speak for the second you in which you've attempted to implement this.

Meteor 404 Error "Method 'messages.insert' not found"

I've been successfully calling Meteor methods until I created a new Mongo collection. Both collections are found under /imports/collections/, so I know it's available to both client and server.
Here is the Meteor.method, which is pretty much the same as my working collection.:
import { Mongo } from 'meteor/mongo';
Meteor.methods({
'messages.insert': function(data) {
return Messages.insert({
otherCollectionId: data.otherCollectionId,
type: data.type,
createdAt: new Date(),
sender: this.userId,
text: data.text
});
}
});
export const Messages = new Mongo.Collection('messages');
Here's how I call it:
import React from 'react';
import { Messages } from '../../imports/collections/messages';
// other imports
export default class Chat extends React.Component {
// other code
handleComposeClick() {
if (this.refs.text) {
let data = {
playlistId: this.props.playlist._id,
type: 'user',
text: this.refs.text.value
};
Meteor.call('messages.insert', data, (error, playlistId) => {
if (!error) {
this.setState({error: ''});
this.refs.text.value = '';
} else {
this.setState({ error });
console.log(error);
}
});
}
}
// render()
}
Whenever I click and trigger handleComposeClick(), I get this error:
errorClass {error: 404, reason: "Method 'messages.insert' not found", details: undefined, message: "Method 'messages.insert' not found [404]", errorType: "Meteor.Error"}
Remember that anything inside the /imports folder will not work unless it's actually imported, either with this syntax:
import somethingINeed from '/imports/wherever/stuff';
import { somethingElseINeed } from '/imports/wherever/things';
or:
import '/imports/server/stuff';
So for methods, you may want to set up a structure where you have the following:
/lib/main.js
import '../imports/startup/lib';
/imports/startup/lib/index.js
import './methods';
// any other shared code you need to import
/imports/startup/lib/methods.js
import { Meteor } from 'meteor/meteor';
import { Messages } from '/imports/collections/messages'; // don't define your collections in a methods file
Meteor.methods({
'messages.insert': function(data) {
return Messages.insert({
otherCollectionId: data.otherCollectionId,
type: data.type,
createdAt: new Date(),
sender: this.userId,
text: data.text
});
}
});
Though if I were you, I'd use validated methods, where you actually import the method you want to use in order to use it, e.g.:
import { messagesInsert } from '/imports/common-methods/message-methods';
// code...
messagesInsert.call({ arg1, arg2 }, (error, result) => { /*...*/ });
With this structure, you would instead have /server/main.js import ../imports/startup/server which would import ./methods, which (in my particular project) looks like this:
// All methods have to be imported here, so they exist on the server
import '../../common-methods/attachment-methods';
import '../../common-methods/comment-methods';
import '../../common-methods/tag-methods';
import '../../common-methods/notification-methods';
import '../../features/Admin/methods/index';
import '../../features/Insights/methods';
import '../../features/Messages/methods';
Keep in mind, this doesn't actually execute the methods, it just makes sure they're defined on the server, so that when you import these validated methods on the client side and run them, it doesn't bomb out saying the method can't be found on the server side.
Import 'Messages' in your server side (/server/main.js)
Shout out to #MasterAM for helping me find my typo. Turns out I just had an incorrect path in /server/main.js

How to get data from node.js to static javascript file in public folder

I am newbie in node.js. I have to send data from node to static javascript file. I have made a collection of questions which I want to sent to this javascript file :
var model = {
questionSummary:[],
noOfQuestions:0,
init: function () {
// if (!localStorage.getItem("questions")) {
// localStorage.setItem("questions", JSON.stringify([]));
// }
if(!localStorage.getItem("noOfQuestions")){
localStorage.setItem("noOfQuestions",JSON.stringify(0));
}
else{
model.noOfQuestions = localStorage.getItem("noOfQuestions");
}
if (!localStorage.getItem("author")) { //todo
localStorage.setItem("author","Anonymus");
}
if (!localStorage.getItem("currentQuestionId")) {
localStorage.setItem("currentQuestionId", '1');
}
for(var index=1;index<=this.noOfQuestions;index++)
this.questionSummary.push(JSON.parse(localStorage.getItem("question"+index)));
},
getAllQuestions: function () { //todo
return this.questionSummary;
},
increment: function(questionBlockId) {
this.questionSummary[questionBlockId].views++;
},
setter: function(property,value){
localStorage.setItem(property, JSON.stringify(value));
}
};
Instead of localStorage, I want to use that data in my code. I have used mongoose as the database storage and express as node framework.
I haven't made any route for this purpose, so any solution(advice) from scratch will be appreciated.

Categories