Meteor ReactiveDict MongoDB Find onCreate - node.js

I'm trying to use a mongodb find items and stored in ReactiveDict, but I'm just getting the error:
{{#each}} currently only accepts arrays, cursors or falsey...
What am I doing wrong here?
Template.body.onCreated(function(){
Meteor.subscribe('itemFind');
this.global = new ReactiveDict();
this.global.set('items',Items.find());
});
Template.body.helpers({
items(){
console.log(Template.instance().global.get('items'));
return Template.instance().global.get('items');
}
});
Further, I figured if I added a .fetch() to the original find statement this would be fixed, but apparently not.
I'm new to Meteor, so what am I doing wrong here?

On the onCreated the subscription is not ready yet. A good practice is to define your Reactive vars on onCreated and assign them on onRendered.
In any case, you need to wait for the subscription to be ready. To do so you can use an autorun(). The autorun() will rerun every time its dependencies are updated.
Template.body.onCreated(function() {
this.global = new ReactiveDict({});
});
Template.body.onRendered(function()
this.autorun(() => {
const subs = Meteor.subscribe('itemFind');
if(subs.ready()) {
this.global.set('items', Items.find({}));
}
});
});

Related

Meteor Client calling findOne in Server Method

I have a client-side form that can create a document upon submission. I want to see if one of the input fields already exists on a Document in the DB though. This would then alert the user and ask them if they want to continue creating the record.
Client-side event
Template.createDoc.events({
'click button[type=submit]'(e, template) {
//This particular example is checking to see if a Doc with its `name` property set to `value` already exists
const value = $('#name');
const fieldName = 'name';
const exists = Meteor.call('checkIfFieldExistsOnDoc', fieldName, value);
if (exists) {
if (confirm(`Doc with ${value} as its ${fieldName} already exists. Are you sure you want to continue creating Doc?`) {
//db.Docs.insert....
}
}
}
});
Server-side Meteor Method
'checkIfFieldExistsOnDoc'(field, val) {
if (this.isServer) {
this.unblock();
check(field, String);
check(val, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You are not authorized.');
}
const findObj = {};
findObj[field] = val;
const fieldsObj = {};
fieldsObj[fieldsObj] = 1;
const doc = Docs.findOne(findObj, {fields: fieldsObj});
return doc;
}
},
My issue is that the client-side code always gets undefined back when calling the Server method. I now understand why, however, I'm not keen on wrapping all of my subsequent client-code into a callback yet.
So - any other ideas on how I can attempt to do this simple feature?
Also - I was thinking of having the client-side page's onCreated do a 1-time server call to get ALL names for all Docs, storing this in memory, and then doing the check upon form submission using this. Obviously, this is inefficient and not-scalable, although it would work
Meteor.call in the client side is always an async call. Then you need implement a callback.
See docs: https://docs.meteor.com/api/methods.html#Meteor-call
Meteor.call('checkIfFieldExistsOnDoc', fieldName, value, function(error, result) {
if (result) {
if (confirm(`Doc with ${value} as its ${fieldName} already exists. Are you sure you want to continue creating Doc?`) {
//db.Docs.insert....
}
}
});
On the client, you can wrap any Meteor.call with a Promise and then use it with async/await. There are some packages on Atmosphere that do this for you to.
I've used this package for years: https://atmospherejs.com/deanius/promise
On the client I often just use await Meteor.callPromise() which returns a response nicely.
Here are a couple of the best write-ups on the many options available to you:
https://blog.meteor.com/using-promises-on-the-client-in-meteor-fb4f1c155f84
https://forums.meteor.com/t/meteor-methods-return-values-via-promise-async/42060
https://dev.to/jankapunkt/async-meteor-method-calls-24f9

NodeJS array and firebase reference getting garbage collected

I want to be able to create some firebase references when the first user connects to a socket.io room, and then listen to changes on those firebase references, and write to files.
Now my approach was to have a global Map object, then assign firebase references and filenames to arrays as part of an object. Then store the object, to the global map.
Unfortunately, the references are lost as soon as the function for first connection ends. I can see this by letting another user connect / refreshing the page and logging the global map.
Here is what my code looks like.
io.on('connect', (socket) => {
io.to(socket.id).emit('create-room');
socket.on('join-room', (roomId: string) => {
socket.join(roomId);
try {
console.log(activeWorkers)
if (!activeWorkers.get(roomId)) {
// if not in global map, tell user he is first in room so that we can get additional info
io.to(socket.id).emit('first-in-room');
} else {
console.log(activeWorkers.get(roomId));
}
} catch(err) {
console.log(err);
}
});
socket.on('create-worker', async (roomId: string, additionalInfo: string) => {
// first user sent back additional info;
let firebaseRefObj = new FirebaseRefObj(roomId, additionalInfo); // an object that stores the references and file paths
activeWorkers.set(roomId, {
id: roomId,
worker: firebaseRefObj
}); // store in global map
firebaseRefObj.createRefs(); // create references, the references add initial values to firebase, then watch for changes to update local files.
})
socket.on("disconnect", () => {
socket.removeAllListeners();
});
})
This is because socket.io functions don't persist their variables from within on events, if you are trying to access the firebaseRefObj at a later point, you must store it into an array or an object map so you can re-associate that user with it.
You could use the Room ID as you have and pass it as a name of the value in your list of references {[roomId]: firebaseRefObj}
Personally, I highly recommend an enmap for this use case, an enmap is like a C# dictionary for node.js. When you initiate your script and declare your variables, add a new enmap at the top to store your ref objects.
I believe the following will work:
const Enmap = require("enmap");
const FirebaseRefs = new Enmap();
// ...
FirebaseRefs.set(RoomId, FirebaseRefsObj);
You can read the basics here: https://enmap.evie.dev/usage/basic
Source: https://www.npmjs.com/package/enmap
Enmap was a really good solution, but my problem lied with what I was using my firebase references with. I was internally using firepad, and it took itself along with the firebase reference out of scope.
Removing firepad fixed my issue.

AWS CDK CfnDistribution attrDomainName '<unresolved-token>'

Having an issue where I can’t get CfnDistribution.attrDomainName to resolve. The value is always '’. I’m trying to inject the value into a Lambda to achieve something like https://github.com/aws-samples/cloudfront-authorization-at-edge/blob/master/template.yaml . Anyone have any guidance? I’ve tried manually setting dependencies and adding a while loop w/ Token.isUnresolved just hangs indefinitely. Thanks in advance for any insight
const cloudfrontDistro = new cloudfront.CfnDistribution(this,'CloudFront', {...})
...
new cfn.CustomResource(this, 'CustomCR', {
provider: cfn.CustomResourceProvider.fromLambda(myProvider),
properties: {
CognitoAuthDomain: userPoolDomain.getAttString('DomainName')
}
});
was able to fix this issue by getting reference to the variable outside of the properties field of the CustomResource. Not sure if the call to Lazy was necessary.
const cognitoAuthDomain = cdk.Lazy.stringValue({
produce: () => {
return userPoolDomain.getAttString('DomainName')
}
});

Meteor: Creating a collections: Reference Error when debug in chrome console

I follow a tutorial with Meteor I try to create a collection, both for client and server. Here is my code:
var lists = new Meteor.Collection("Lists");
if (Meteor.isClient) {
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}
As tutorial I have read, when run on server, if I open chrome console and type lists I will receive Meteor.Collection. But when I tried on my machine, I received error:
Reference error. lists is not define
Have I done something wrong? Please tell me.
Thanks :)
Also you can put all your collections inside the /lib/collection.js route (for better practices).
So with that we ensure that meteor loads first the collections, and they will be available on both client/server.
you should remove Autopublish/insecure package, to avoid meteor sends all the collections when load and to control who can or not insert/remove/update on the collections.
meteor remove autopublish
meteor remove insecure.
So a simple collection will look like this.
//lib/collection.js
Example = new Mongo.Collection("Example") //we create collection global
if(Meteor.isClient) {
Meteor.subscribe('Example') //we subscribe both after meteor loads client and server folders
}
now on /server/collections.js
Meteor.publish('Example', function(){
return Example.find(); //here you can control whatever you want to send to the client, you can change the return to just return Example.find({}, {fields: {stuff: 1}});
});
// Here we control the security of the collections.
Example.allow({
insert: function(userId, doc) {
if(Meteor.userId()){
return true; //if the user is connected he can insert
} else{
return false// not connected no insert
}
},
update: function(userId, doc, fields, modifier) { //other validation },
remove: function(userId, doc) { //other validation },
});
Just to try to explain a little more deep the Collection here on meteor, hope it help you GL
I think you have autopulish/autosubscribe turned off. Try
if (Meteor.isClient) {
Meteor.subscribe('lists');
}
if (Meteor.isServer){
Meteor.publish('lists',function(){
return Lists.find();
});
}
For your naming, I'd also recommend you reverse the way you're capitalizing your collections. So instead it would be
var Lists = new Meteor.Collection("lists");
And finally, look at https://github.com/matteodem/meteor-boilerplate for your directory structure so you don't have to do the if meteor.is stuff anymore.
Edit
Full code should look like:
var Lists = new Meteor.Collection("lists");
if (Meteor.isClient) {
Meteor.subscribe('lists');
}
if (Meteor.isServer){
Meteor.publish('lists',function(){
return Lists.find();
});
}
All of your script source files are wrapped in a function closure as part of the build process. In order for your collection to be visible outside of that file (or in your case - attached to the window object) you will need to declare it as a global variable:
Lists = new Meteor.Collection('lists');
Note the lack of var. As #thatgibbyguy pointed out, the accepted pattern is to capitalize collection variables, and camelcase collection names.

Model.create retrive how many documents have been created

I am using Model.create(Array) in Mongoose.
I want to provide user a feedback about how many documents have been created and how many of them haven't (i.e. they didn't validate).
I created a callback like this
User.create(usersToImport, function(err, docs) {
console.log(err);
console.log(docs);
}
The problem is that if any document does not validate, I only receive a validation error on the single non-valid document, while I cannot retrieve any information about the inserted documents.
Is there any way to get this information?
I think, you need something like .settle() method from when.js module.
Here is an example of doing it using when.js with mongoose 3.8.x:
when = require('when');
promises = usersToImport.map(function(user) {
return User.create(user); // returns Promise
});
when.settle(promises).then(function(results) {
// results is an array, containing following elements:
// { state: 'fulfilled', value: <document> }
// { state: 'rejected', value: <error> }
});
It's possible to do it without Promises (e.g. using async module), but the code will be much more complicated.
Model.create() isn't going to return that information for you. The best option would be to roll your own version of Model.create(), which is essentially a convenience method for calling Model.save() multiple times, and collate the information yourself. A good way to get started is the code for Model.create().

Resources