How to enable reactivity within a document?


#1

I have a Meteor collection, and following the meteor docs I’ve made models on the client for the collection documents as they are loaded

Animal = function (doc) {
  _.extend(this, doc);
};
_.extend(Animal.prototype, {
  feverCallback: function () {
    console.log('this animal has a fever !');
  }
});

Animals = new Mongo.Collection("Animals", {
  transform: function (doc) { return new Animal(doc); }
});

myAnimal = Animals.findOne(); // e.g. { _id : 'foo', temperature : 95 }

I want myAnimal.feverCallback to run whenever myAnimal.temperature jumps over 100… by that i mean another user changes myAnimal’s temperature to 101 for example.

what is the ‘right/best/meteor’ way to set this up ? Collection hooks ? Some kind of Tracker.autorun ? Some kind of query.observe ?

Thanks in advance !


#2

I would go with myAnimal.observe changed: part
http://docs.meteor.com/#/full/observe


#3

I don’t think you need reactivity to do that.
You could simply put a check in the temperature setter and run the callback when the temperature get over 100.

However, the point of reactivity is to get rid of callbacks.

You could transform you model into a reactive data source.

In the constructor you create a dependency :

this.highTempDep = new Tracker.dependency();

In the setter you do you check and notify if themp is beyond your thresold point:

Animal.prototype.setTemp = function(temp){
  if (temp > 100){
    this.highTempDep.changed();
  }
  this.temp = temp;
}

And you create a method that will be you actual reactive source :

Animal.prototype.isTempBeyondThresold = function(){
  this.highTempDep.depend();
  return this.temp > 100;
};

An in a template helper (or in an autorun), you can do :

FooTemplate.helpers({
  isHighTemp : function(){
    console.log("isHighTemp called !");
    return animal.isTempBeyondThresold();
  };
};

#4

Thanks @vjau - your idea would work fine if i wanted to capture changes to temperature made by me, but it wouldn’t pick up changes made by other users i think.
I’m pretty sure i have to use observeChanges api to get what i need…


#5

Ok, sorry, i didn’t understand your requirements.

So you can do it like that, with you defined callback :

Tracker.autorun(function(){
  var myAnimal = Animals.findOne();
  if (myAnimal && myAnimal.temp > 100){
    callBack(); 
  };
});

That’s one of the way to do it, but there are many others.
I you give us more information about what your callback is doing, we could probably provide you with a more meteorish way.


#6

Thanks again @vjau !

So I’ll try to be a bit clearer -

  • at any time, I might have 20, 30, 50… instances of Animal on a given client, each one needs to be able to monitor its own temperature and run a callback if e.g. temperature > 100

Ideally, following the Template pattern, I would love to be able to do something like this

 Animal = function (doc) {
  var self = this;
  _.extend(self, doc);
  self.autorun(function(){
    if(self.temperature > 100){
      self.feverCallback();
    }
};

where the autorun would be invalidated when temperature changes, and it would be destroyed when the instance is destroyed.

But at the moment I have to deal with (1) self.temperature is not reactive, and (2) I’m pretty sure my autorun will not get destroyed with the model instance.

For the moment I’m doing it like this

Animals.find().observeChanges({
  changed : function(id, fields){
    if(_.contains(_.keys(fields), 'temperature')){
      var myAnimal = Animals.findOne(id);
      if(myAnimal.temperature > 100){
        myAnimal.feverCallback();
      }
    }
  }
});

which is basically what you proposed… but it’s not very modular and not very easy to test.


#7

What is the callback doing ?


#8

There’s a list widget ‘animals with fever’ that should update reactively.


#9
Template.SickAnimals.helpers({
  sickAnimals: function () {
  	return Animals.find({temperature: { $gt: 100}});
  }
})

#10

:smile:
Most of the time, when you are thinking “callback”, you are doing it wrong.


#11

Thanks @vjau and @shock, that was a lot easier than expected :wink:


#12

It still feels a bit funny to me, though.

When I think of ‘reactivity’, I think I should have a model and I should be able to ask it ‘do you have a fever ?’, and he should tell me reactively true or false. By that I mean I should be able to couple a template to this model and just have a helper

Tempate.animal_template.helpers({
  hasFever : function(){
    return this.hasFever();
  }
});

that will then update the view according to the state of the model. Instead I guess I have to do this if I want reactivity

Template.animal_template.helpers({
  hasFever : function(){
    return Animals.findOne(this._id).temperature > 100;
  }
});

#13

Well, I would expect you are still being able to do

Tempate.animal_template.helpers({
  hasFever : function(){
    return this.hasFever();
  }
});

Just data contect of that template have to be directly that 1 animal, not whole cursor.


#14

In the past, i have tried to implement “traditionnal” OO patterns with Meteor.
When you are implementing a real domain model with pools of objects, it doesn’t play well with meteor reactivity.
You end up fighting against the framework and getting cache invalidating problems, which Meteor aims to solve.
The way shown by Shock is the real meteor way of doing things.
However with very complex apps, the impossibility to use traditionnal oo patterns (a real domain model instead of what Martin Fowler calls “transaction scripts”), could be the limitation of the framework.
We have still to discover lot of patterns to build complex apps with Meteor.


#15

I’m not sure I understand… could you explain what you mean by this ?