Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Meteor, how to update one db field when another field changes?

Tags:

meteor

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:

  1. Is observing the Lists collection like this a reasonable approach? It seems like it could be inefficient -- any change to a Lists document will trigger this code.
  2. Should I be doing a different (simulated) update client-side, or is it OK to let this same Mongo/Minimongo update run on both?
  3. Do I need to call 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?)

like image 366
medmunds Avatar asked Sep 17 '12 17:09

medmunds


2 Answers

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

like image 161
Dsyko Avatar answered Jan 01 '23 08:01

Dsyko


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}});
     }
   }
});
like image 20
Giant Elk Avatar answered Jan 01 '23 08:01

Giant Elk