Collection before hook called twice - meteor

I am using matb33:collection-hooks
I have difficulties to understand why the versions.insert is called one time (only one console log is displayed) but there is 2 records with 2 different _id inserted in the versions collection.
Requirements = new Meteor.Collection('requirements');
var versions = new Meteor.Collection('requirements_versions');
Requirements.before.update(function(userId, doc, fieldNames, modifier, options) {
// copy doc to versions collection
var savedDoc = _.extend({}, doc); // shallow copy
if(typeof(savedDoc._id) != 'undefined') delete savedDoc._id;
console.log(versions.insert(savedDoc)); // FIXME: why inserted 2 times ???
});
If I add a return false at the end of the hook, the record is inserted only once, but of course the original requirement update is not called.

You can do this by omitting _id
const versions = new Meteor.Collection('requirements_versions');
const Requirements = new Meteor.Collection('requirements');
if (Meteor.isServer) {
Requirements.before.update(function(userId, doc, fieldNames, modifier, options) {
// insert version doc
versions.insert(_.omit(doc, '_id'));
});
}

Related

Error occurred while parsing your function triggers

The following error is shown while deploying firebase function.
I tried initializing the firebase functions.
I also double-checked the index.js file.
I'm new to deploying firebase functions so please help me for the same.
index.js is as follows:
const functions = require('firebase-functions');
// replaces keywords with emoji in the "text" key of messages
// pushed to /messages
exports.emojify =
functions.database.ref('/messages/{pushId}/text')
.onWrite(event => {
// Database write events include new, modified, or deleted
// database nodes. All three types of events at the specific
// database path trigger this cloud function.
// For this function we only want to emojify new database nodes,
// so we'll first check to exit out of the function early if
// this isn't a new message.
// !event.data.val() is a deleted event
// event.data.previous.val() is a modified event
if (!event.data.val() || event.data.previous.val()) {
console.log("not a new write event");
return;
}
// Now we begin the emoji transformation
console.log("emojifying!");
// Get the value from the 'text' key of the message
const originalText = event.data.val();
const emojifiedText = emojifyText(originalText);
// Return a JavaScript Promise to update the database node
return event.data.ref.set(emojifiedText);
});
// Returns text with keywords replaced by emoji
// Replacing with the regular expression /.../ig does a case-insensitive
// search (i flag) for all occurrences (g flag) in the string
function emojifyText(text) {
var emojifiedText = text;
emojifiedText = emojifiedText.replace(/\blol\b/ig, "😂");
emojifiedText = emojifiedText.replace(/\bcat\b/ig, "😸");
return emojifiedText;
}
Please check the current documentation on triggers, and specifically on migration from Beta to Version 1.0 .
event.data.previous.val() has changed to change.before.val()
event.data.val() has changed to change.after.val()
Also, the Promise statement changes to:
return change.after.ref.parent.child('text').set(emojifiedText);
The complete index.js looks like:
const functions = require('firebase-functions');
// replaces keywords with emoji in the "text" key of messages
// pushed to /messages
exports.emojify=
functions.database.ref('/messages/{pushId}/text')
.onWrite((change,context)=>{
// Database write events include new, modified, or deleted
// database nodes. All three types of events at the specific
// database path trigger this cloud function.
// For this function we only want to emojify new database nodes,
// so we'll first check to exit out of the function early if
// this isn't a new message.
// Only edit data when it is first created.
if (change.before.exists()){
return null;
}
// Exit when the data is deleted.
if (!change.after.exists()){
return null;
}
// Now we begin the emoji transformation
console.log("emojifying!");
//Get the value from the 'text' key of the message
const originalText = change.after.val();
const emojifiedText = emojifyText(originalText);
//Return a JavaScript Promise to update the database nodeName
return change.after.ref.parent.child('text').set(emojifiedText);
});
// Returns text with keywords replaced by emoji
// Replacing with the regular expression /.../ig does a case-insensitive
// search (i flag) for all occurrences (g flag) in the string
function emojifyText(text){
var emojifiedText=text;
emojifiedText=emojifiedText.replace(/\blol\b/ig,"😂");
emojifiedText=emojifiedText.replace(/\bcat\b/ig,"😸");
return emojifiedText;
}

Why is this collection empty? (Collection.find() is successful om server)

Ok this is what I've got.
The collection called Posts has content and I want to publish this under the name Merchs, the find() in the publish-function finds data but that is not shared to the client where Merchs is always empty.
//shared
Merchs = new Meteor.Collection('merchs');
// Posts has data I want to publish as "Merchs"
this.Posts = new Meteor.Collection('posts');
//server
Merchs.allow({
insert: function(userId, doc) {
return true;
},
update: function(userId, doc, fields, modifier) {
return true;
},
remove: function(userId, doc) {
return true;
}
});
Meteor.publish('merchs', function(data) {
return Posts.find();
});
//client
Deps.autorun( function() {
Session.get('selectedCategories');
subs.subscribe('merchs');
});
When creating your collection, the name in parentheses should be the name of the Mongo collection.
Merchs = new Meteor.Collection('merchs');
Should be:
Merchs = new Mongo.Collection('Posts');
That is, unless you already have a Posts variable defined in code that you didn't show. If you've already defined Posts and you're just looking to make another subscription to the same collection then you don't need this line at all:
Merchs = new Meteor.Collection('merchs');
You also don't need your allow() method (you can just use the one defined for Posts). All you need is the publish() method that you defined.
On the client side you also need:
Meteor.subscribe('merchs');
Also note the use of Mongo.Collection instead of Meteor.Collection which was renamed in Meteor 0.9.1.
You might want to read this excellent answer regarding publish/subscribe: https://stackoverflow.com/a/21853298/4665459

Most efficient way to ensure user owns document on update?

I'm using Meteor methods to update documents so I can share them easier and have more control. However i've ran into a problem with checking ownership.
How should I check to make sure the user calling the update method is the owner of the document? Currently i'm grabbing the document first then running the update.
Is there a better pattern to accomplish this?
Meteor.methods({
'Listing.update': function(docId, data) {
var doc = db.listings.findOne({_id: docId}) || {};
if (doc.userId !== this.userId) {
throw new Meteor.Error(504, "You don't own post");
}
// ensure data is the type we expect
check(data, {
title: String,
desc: String
});
return db.listings.update(docId, {$set: data});
}
});
You don't need the additional db call to fetch the original doc, just make the userId an additional criteria in the update selector. If no doc exists with the correct _id and userId no update will be done. update returns the number of docs updated so it will return 1 on success and 0 on failure.
like this:
'Listing.update': function(docId, data) {
var self = this;
check(data, {
title: String,
desc: String
});
if ( ! self.userId )
throw new Meteor.Error(500, 'Must be logged in to update listing');
res = db.listings.update({_id: docId, userId: self.userId}, {$set: data});
if ( res === 0 )
throw new Meteor.Error( 504, "You do not own a post with that id" );
return res;
}
Also, if you use findOne to check a document's existence, use the fields option to limit what you return from the db. Usually just {fields: {_id:1}}.

How do I do a parameter based publication in Meteor and remove old subscription document?

I want to implement a parameter based publication in Meteor but I am running into some problems.
Here is what I have.
As the user types the keyup event that subscribes to publication and passes the value of the input.
'keyup #customerSearch': function(event, template){
var keyword = template.find('#customerSearch').value;
if(keyword){
if(keyword.length >= 3){
Meteor.subscribe('sessioncustomers', keyword);
}
}
}
The publication uses this keyword to return the records.
Meteor.publish("sessioncustomers", function(keyword){
if(keyword ){
if(keyword.length >= 3){
query.name = new RegExp(regExpQuoted(keyword), 'i' );
Customers.find(query);
} else {
return null;
}
}else{
return null;
}
});
The problem.
It works and documents are received except when the client changes the keyword or rather as the keywords changes the publication publishes additional documents that match the keywords but the client collection never removes the old documents.
How do I get the old documents that no longer match out of the client collection?
I thought that because the parameters of the subscription had changed that the non-matching documents would be unsubscribed and only the new matching documents would be subscribed.
In your keyup callback you need to "unsubscribe" to the previous publication,
otherwise you'll keep the old documents.
var sessionCustomersHandler = false;
'keyup #customerSearch': function(event, template) {
var keyword = template.find('#customerSearch').value;
if (keyword && keyword.length >= 3)
var newSessionCustomersHandler = Meteor.subscribe('sessioncustomers', keyword);
if (sessionCustomersHandler)
sessionCustomersHandler.stop();
sessionCustomersHandler = newSessionCustomersHandler;
}
Moreover, don't forget to check(keyword, String) in your publish function, for security.
Meteor.publish("sessioncustomers", function(keyword){
check(keyword, String)
if (keyword.length >= 3)
return Customers.find({
name: new RegExp(regExpQuoted(keyword), 'i' )
});
});
Make a local unnamed client collection
this.SessionCustomers = new Meteor.Collection(null);
Call a server method to get the results you want. Make the callback clear (remove all) and then insert to that local collection.
return Meteor.call('sessioncustomers', query, function(err, data) {
if (err) {
return console.log(err.message);
} else {
SessionCustomers.remove({});
var item, _i, _len;
for (_i = 0, _len = data.length; _i < _len; _i++) {
item = array[_i];
SessionCustomers.insert(item);
}
}
});

Detecting which reactive query was triggered

I have an probably not so unique issue of having a complicated meteor app.
I have several actions which are causing parts of the page to refresh which really aren't needed. But I'm having trouble locating which find() (or multiple find()'s ) is the one being triggered. I know the Collection in question, just not which find().
I could use observeChanges on every find I use, but that would be a lot of extra code.
Is there an easy way to see what is being triggered and by what?
Thanks!
Here is a render logging function you might find useful. It logs the number of times each template is rendered to the console. You know if a template is re-rendered after initial page load, it's because a reactive data source that it relies on has changed. Either this reactive data source could have been accessed in a helper method, or the template is a list item (i.e. inside an {{#each ...}} block helper) and a list item was added/moved/removed/changed. Also keep in mind that child templates call their parent's rendered callback when the child is rendered or re-rendered. So, this might confuse you into thinking the parent has actually been taken off the DOM and put back, but that's not true.
So, you can call this function at the end of your client code to see the render counts:
function logRenders () {
_.each(Template, function (template, name) {
var oldRender = template.rendered;
var counter = 0;
template.rendered = function () {
console.log(name, "render count: ", ++counter);
oldRender && oldRender.apply(this, arguments);
};
});
}
EDIT: Here is a way to wrap the find cursor to log all changes to a cursor to the console. I just wrote a similar function to this for a new package I'm working on called reactive-vision. Hopefully released soon.
var wrappedFind = Meteor.Collection.prototype.find;
Meteor.Collection.prototype.find = function () {
var cursor = wrappedFind.apply(this, arguments);
var collectionName = this._name;
cursor.observeChanges({
added: function (id, fields) {
console.log(collectionName, 'added', id, fields);
},
changed: function (id, fields) {
console.log(collectionName, 'changed', id, fields);
},
movedBefore: function (id, before) {
console.log(collectionName, 'movedBefore', id, before);
},
removed: function (id) {
console.log(collectionName, 'removed', id);
}
});
return cursor;
};
Thank you #cmather for the idea.
Her is Meteor 1.3 adapted and more advanced version of logRenders
// Log all rendered templates
// If filter is set, only templates in filter will be logged
// #params filter - name or names of template to filter
logRenders = function logRenders (filter) {
for (name in Object(Template)){
if (filter && !Array.isArray(filter)) filter = [filter];
var template = Template[name];
if (!template) continue;
if (filter && filter.indexOf(name) == -1){
// Clear previous logRenders
if ('oldRender' in template) template.rendered = template.oldRender;
delete template.oldRender;
continue;
}
var t = function(name, template){
if (!('oldRender' in template)) template.oldRender = template.rendered;
var counter = 0;
template.rendered = function () {
console.log(name, ++counter, this);
this.oldRender && this.oldRender.apply(this, arguments);
};
}(name, template);
};
};

Resources