General question: in Meteor, what's the best way to implement business logic that triggers whenever a model is updated -- e.g., for updating dependent fields or validations or...
Specific example: I'd like to add a "slug" field to Lists collection in the Meteor todos example. The slug needs to automatically update whenever a list's name is changed.
Here's what I've got... I'm observing every change to a list to see if its slug needs to be created/updated. This is in a shared models.js (runs server and client-side, to get the benefits of latency compensation):
// Lists -- {name: String}
Lists = new Meteor.Collection("lists");
var listsObserver = Lists.find().observe({
added: updateSlug,
changed: updateSlug
});
function updateSlug(doc, idx) {
var slug = (doc.name || '').replace(/\W+/g, '-').toLowerCase();
if (slug !== doc.slug) {
console.log("Updating slug for '" + doc.name + "' to " + slug);
Lists.update(doc._id, {$set: {slug: slug}});
}
}
(And as in the original todos example, server/publish.js publishes all of Lists.find()
as "lists", and client/todos.js subscribes to that collection.)
The code above seems to work, but somehow doesn't look quite right to me. Questions:
listsObserver.stop()
at some point to dispose the observer?
And if so, when?(I'm just getting started with Meteor, so perhaps my biases from other environments are leaking through. The implied meta-question here is, am I even thinking about this problem in the right way?)
I would suggest using the Collection-Hooks package. It extends the collection operations with before and after hooks. This is better than having a lot of collection Observes or ObserveChanges, especially on the server where the overhead for collection observes can get very large.
This works on both the client and the server. If you implement it on the client you will get the benefit of updating the local collection (latency compensation) and the change will be pushed to the server so no need to do it again.
You also get the benefit of only doing one MongoDB operation instead of two or more like you would with observes or observeChanges.
You might use it like so:
var beforeInsertSlug = function(userId, doc) {
var slug = (doc.name || '').replace(/\W+/g, '-').toLowerCase();
if (slug !== doc.slug) {
console.log("Updating slug for '" + doc.name + "' to " + slug);
doc.slug = slug;
}
};
var beforeUpdateSlug = function(userId, doc, fieldNames, modifier, options){
if(modifier && modifier.$set && modifier.$set.doc && _.isString(modifier.$set.doc.name)){
var slug = (modifier.$set.doc.name || '').replace(/\W+/g, '-').toLowerCase();
if (slug !== doc.slug) {
console.log("Updating slug for '" + modifier.$set.doc.name + "' to " + slug);
modifier.$set.doc.slug = slug;
}
}
};
Lists.before.insert(beforeInsertSlug);
Lists.before.update(beforeUpdateSlug);
You can find the package here: https://atmospherejs.com/matb33/collection-hooks
I did a similar thing in server code. Basically put this code in Meteor.methods(), along with any other checks and updates you want making to the Lists Collection.
Although the code below looks a bit messy, and certainly hard to understand with the line starting with var slug:
Meteor.methods({
myupdate: function (doc) {
var slug = (doc.name || '').replace(/\W+/g, '-').toLowerCase();
if (slug !== doc.slug) {
console.log("Updating slug for '" + doc.name + "' to " + slug);
Lists.update(doc._id, {$set: {slug: slug}});
}
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With