How to reactively execute code as soon as a client Collection grows? - javascript

I'm trying to keep track of an increment of a certain reactive value in Meteor. If the current value has increased by 1 or more, I want something to happen. I do have two problems:
First: I don't know how I can make an if-statement of this function.
Second: I don't know how I can keep track of the increases.
This is the code I have now, using the Mongo.Collection cars (which is from an API):
api = DDP.connect('url');
const currentCars = new Meteor.Collection('cars', api);
const newCars = cars.find().count()
if (Meteor.isClient) {
Template.currentCars.helpers({
carsInCity: function() {
return currentCars.find(
{
location: "city"
}).count();
},
})
}
So there's a current amount of cars in the city. Everytime when there is one more car, I want something to happen in the code. But how on earth can I do that? Maybe by keeping track of when the database has been updated?

A fairly straight-forward solution would be to store the current amount of data in that collection, then run a reactive computation to see if anything changed.
Something like this:
let currentCarsCount = cars.find().count()
Tracker.autorun(function checkForAddedCars() {
// cars.find() is our reactive source
const newCarsCount = cars.find().count()
if(newCarsCount > currentCarsCount) {
currentCarsCount = newCarsCount
// There's new cars, handle them now
// ...
}
})
You may also want to use a template-level autorun so that you don't have to manage stopping checkForAddedCars. You could also store currentCarsCount as a state on the template instance instead of as a hoisted loner.
For example:
Template.currentCars.onCreated(function() {
const templateInstance = this;
// equivalent:
const templateInstance = Template.instance();
templateInstance.currentCarsCount = cars.find().count();
templateInstance.autorun(function checkForAddedCars() {
// cars.find() is our reactive source
const newCarsCount = cars.find().count();
if(newCarsCount > templateInstance.currentCarsCount) {
templateInstance.currentCarsCount = newCarsCount;
// There's new cars, handle them now
// ...
}
});
});
It would also allow you to access currentCarsCount from other places in the template code.

Related

Sequelize restrict update if many-to-many relation established

In general I have a goal to restrict any update of an entity if it's binded to anything.
To be specific I have two models: Order and Good. They have many-to-many relation.
const Good = sequelize.define('good' , { name:Sequelize.STRING });
const Order = sequelize.define('order' , { });
//M:N relation
Good.belongsToMany(Order, { through: 'GoodsInOrders' });
Order.belongsToMany(Good, { through: 'GoodsInOrders' });
I have tried to set onUpdate: 'NO ACTION' and onUpdate: 'RESTRICT' inside belongsToMany association defining but it has no effect.
Here is the code to reproduce my manipulations with goods and order
//creating few goods
const good1 = await Good.create({ name:'Coca-Cola' });
const good2 = await Good.create({ name:'Hamburger' });
const good3 = await Good.create({ name:'Fanta' });
//creating an order
const order = await Order.create();
//adding good1 and good2 to the order
await order.addGoods([good1,good2]);
//It's ok to update good3 since no orders contains It
await good3.update( { name:'Pepsi' });
//But I need to fire an exeption if I try to update any goods belonged to order
//There should be an error because we previously added good1 to order
await good1.update( { name:'Sandwich' });
I have no clue how to restrict it in a simple way.
Surely we always can add beforeUpdate hook on Good model but I would like to avoid this kind of complications.
I will be glad to any ideas.
As I said I was looking for simpler alternative to hooks but many researches after all led me to nowhere.
My solution was to declare the beforeUpdate hook inside Good model so inital definition
const Good = sequelize.define('good' , { name : Sequelize.STRING } );
was transformed into this
const Good = sequelize.define('good', {
name: Sequelize.STRING
}, {
hooks: {
beforeUpdate: async (instance, options) => {
if ((await instance.getOrders()).length)
return Promise.reject('error');
return Promise.resolve();
}
}
});
So here we add the hook that fires every time before update particular good.
It makes inner request of list of orders that contains current good.
(await instance.getOrders()).length
And depends on the result it either fire an exception if the list isn't empty or just return resolved promise that means that everything ok and current good can be updated.

Firebase Cloudstore: how to "update all"/"delete some" docs in a collection?

For context: I have a cron-job.org that fires an https function in my firebase project.
In this function, I have to go through all docs inside a collection and update a counter (each doc might have a different counter value). If the counter reaches a limit, I'll update another collection (independent from the first one), and delete the doc entry that reached the limit. If the counter is not beyond the limit, I simply update the doc entry with the updated counter value.
I tried adapting examples from the documentation, tried using transactions, batch, but I'm not sure how to proceed. According to transactions' description, that's the way to go, but examples only show how to edit a single doc.
This is what I have (tried adapting a realtime db sample):
function updateCounter() {
var ref = db.collection('my_collection_of_counters');
return ref.get().then(snapshot => {
const updates = {};
snapshot.forEach(child => {
var docData = child.data();
var newCounter = docData.counter+1;
if (newCounter == 10) {
// TO-DO: add to stock
updates[child.key] = null;
} else {
docData.counter = newCounter;
updates[child.key] = docData;
}
});
// execute all updates in one go and return the result to end the function
return ref.update(updates);
});
}
It doesn't work, collections don't have an update method. What is the best approach to updating each doc in a collection? One-by-one? Transaction? Is there an example?
PS: updateCounter is a function being called by the https trigger. Cron+trigger is working fine.
EDIT
When an item reaches the threshold, I want to update another collection, independent from the counter one. Is nested transactions a good solution?
Modified code:
function updateCounter() {
var ref = db.collection('my_collection_of_counters');
var transaction = db.runTransaction(t => {
return t.get(ref)
.then(snapshot => {
let docs = snapshot.docs;
for (let doc of docs) {
var item = doc.data();
var newCounter = item.counter + 1;
if (newCounter == 10) {
console.log("Update my_stock");
// ADD item.quantity to stock collection
}else{
t.update(doc.ref, {counter: newCounter});
}
}
});
})
.then(result => {
console.log('Transaction success');
})
.catch(err => {
console.log('Transaction failure:', err);
});
}
As you already noted yourself, you'll want to do this in a transaction to ensure that you can update the current counter value in a single operation. You can also create the new document, and delete the existing one, in that same transaction once your counter reaches its threshold. I don't see any benefit of doing this for all documents in a single transaction, since the operation on each doc seems unrelated to the others.
In a Firestore transaction, you perform the operations on a Transaction object as shown in the documentation. In your case you'd:
Get the current document with transaction.get().
Get the counter from the document.
Increment the counter.
If the new value is below your threshold:
Call transaction.update() to write the new counter value into the database
If the new value if above your threshold:
Call transaction.create on the new collection to create the document there.
Call transaction.delete on the existing document, to delete it.
For more, I recommend scanning the reference documentation for the Transaction class.

Publishing changes to a single field of a subdocument

I have a complicated data structure being built by queries on multiple collections and published.
It is working great for the initial creation, and on my local machine all the changes observed are reflected in the client as expected. However, in my staging environment I get the following error from mini-mongo when a change is observed
Uncaught Error: When replacing document, field name may not contain '.'(…)
The publishing code looks like this, where pub is the this from a Meteor.publish and rootObj is a reference to an Object in memory which gets properties modified but never has it's reference destoryed.
function _republish(pub, rootId, rootObj, handles, startup) {
// cleanup handles
if (handles.foo) {
handles.foo.stop();
}
// some query which could depend on rootObj/other calculated values
let cursor = SubColl.find({_id: {$in: bar}});
handles.foo = cursor.observeChanges({
removed(_id) {
rootObj.bar = rootObj.bar.filter(o => o._id !== _id);
pub.changed('foobarbaz', rootId, {bar: rootObj.bar})
},
changed(_id, fields) {
const index = rootObj.bar.findIndex(line => line._id === _id);
const changed = {};
_.each(fields, (value, field) => {
rootObj.bar[index][field] = value;
changed[`bar.${index}.${field}`] = value;
});
pub.changed('foobarbaz', rootId, changed);
},
added(_id, fields) {
rootObj.bar.push(_.extend({}, fields, {_id}));
if (!startup) {
// deeper children stuff
pub.changed('foobarbaz', rootId, {bar: rootObj.bar});
}
}
});
// deeper children stuff
startup = false;
// if startup was true, expect caller to publish this
}
As we can see, the publish works fine when I'm pub.changeding on just bar, but attempting to update a specific subdocument field (e.g. bar.0.prop) results in the inconsistent behaviour
If possible I want to avoid re-publishing the whole of bar as it is huge compared to updating a simple property.
How can I publish the change to a single field of a subdocument?

Meteor - how do I make this "reactive" using Deps?

On my client side, I display a list of users and a small chart for each user's points stored in the DB (using jQuery plugin called sparklines).
Drawing the chart is done on Template.rendered method
// client/main.js
Template.listItem.rendered = function() {
var arr = this.data.userPoints // user points is an array of integers
$(this.find(".chart")).sparkline(arr);
}
Now I have a Meteor method on the server side, that is called on a regular basis to update the the user points.
Meteor.methods({
"getUserPoints" : function getUserPoints(id) {
// access some API and fetch the latest user points
}
});
Now I would like the chart to be automatically updated whenever Meteor method is called. I have a method on the template that goes and calls this Meteor method.
Template.listItem.events({
"click a.fetchData": function(e) {
e.preventDefault();
Meteor.call("getUserPoints", this._id);
}
});
How do I turn this code into a "reactive" one?
You need to use reactive data source ( Session, ReactiveVar ) together with Tracker.
Using ReactiveVar:
if (Meteor.isClient) {
Template.listItem.events({
"click a.fetchData": function(e) {
e.preventDefault();
var instance = Template.instance();
Meteor.call("getUserPoints", this._id, function(error, result) {
instance.userPoints.set(result)
});
}
});
Template.listItem.created = function() {
this.userPoints = new ReactiveVar([]);
};
Template.listItem.rendered = function() {
var self = this;
Tracker.autorun(function() {
var arr = self.userPoints.get();
$(self.find(".chart")).sparkline(arr);
})
}
}
Using Session:
if (Meteor.isClient) {
Template.listItem.events({
"click a.fetchData": function(e) {
e.preventDefault();
Meteor.call("getUserPoints", this._id, function(error, result) {
Session.set("userPoints", result);
});
}
});
Template.listItem.rendered = function() {
var self = this;
Tracker.autorun(function() {
var arr = Session.get("userPoints");
$(self.find(".chart")).sparkline(arr);
})
}
}
Difference between those implementation :
A ReactiveVar is similar to a Session variable, with a few
differences:
ReactiveVars don't have global names, like the "foo" in
Session.get("foo"). Instead, they may be created and used locally, for
example attached to a template instance, as in: this.foo.get().
ReactiveVars are not automatically migrated across hot code pushes,
whereas Session state is.
ReactiveVars can hold any value, while Session variables are limited
to JSON or EJSON.
Source
Deps is deprecated, but still can be used.
The most easily scalable solution is to store the data in a local collection - by passing a null name, the collection will be both local and sessional and so you can put what you want in it and still achieve all the benefits of reactivity. If you upsert the results of getUserPoints into this collection, you can just write a helper to get the appropriate value for each user and it will update automatically.
userData = new Meteor.Collection(null);
// whenever you need to call "getUserPoints" use:
Meteor.call("getUserPoints", this._id, function(err, res) {
userData.upsert({userId: this._id}, {$set: {userId: this._id, points: res}});
});
Template.listItem.helpers({
userPoints: function() {
var pointsDoc = userData.findOne({userId: this._id});
return pointsDoc && pointsDoc.points;
}
});
There is an alternative way using the Tracker package (formerly Deps), which would be quick to implement here, but fiddly to scale. Essentially, you could set up a new Tracker.Dependency to track changes in user points:
var pointsDep = new Tracker.Dependency();
// whenever you call "getUserPoints":
Meteor.call("getUserPoints", this._id, function(err, res) {
...
pointsDep.changed();
});
Then just add a dummy helper to your listItem template (i.e. a helper that doesn't return anything by design):
<template name="listItem">
...
{{pointsCheck}}
</template>
Template.listItem.helpers({
pointsCheck: function() {
pointsDep.depend();
}
});
Whilst that won't return anything, it will force the template to rerender when pointsDep.changed() is called (which will be when new user points data is received).

Asynchronous bulk find or create with ember.js

How can I perform a bulk find or create with ember.js? This would be simple to do synchronously (foreach... continue if exists). But working with ember's asynchronous store creates lots of overhead in keeping track of the state of the operation.
Specifically, I have a variable to keep track of the number of objects waiting to be processed (createIfNotExistTaskCounter), so I can check when the store has finished working on all of the objects to be saved. And I use an array to keep track of the items stored so far (createIfNotExistQueue) - I can't let the store handle this task, because I can't count on an item being found after it has been saved.
Here's my best solution below (also on JS Bin). Is there an easier way to do this?
App = Ember.Application.create({});
App.LSAdapter = DS.LSAdapter.extend({
namespace: 'whitespace'
});
App.Store = DS.Store.extend({
adapter: App.LSAdapter
});
App.Fruit = DS.Model.extend({
name: DS.attr("string")
});
App.IndexRoute = Ember.Route.extend({
createIfNotExistTaskCounter: 0, // store number of items waiting to be processed
createIfNotExistQueue: [], // store a list of the items being added, to prevent duplicate adds
setupController: function(controller) {
/* This is a simplified version of a real task I'm trying to acomplish. The code adds a list of objects to the store, only creating them if they don't exist. After the list has been processed, the contents of the store are shown.
To achieve this end I've used a counter and a queue to keep track of the operations' state. Is there a simpler way to do this? These overheads seem excessive for such a straightforward bulk insert operation.
*/
var fruitToStore = ["apple", "pear", "banana", "apple"],
store = this.get('store');
this.set('createIfNotExistTaskCounter', fruitToStore.length);
for(var i=0; i<fruitToStore.length; i++) {
this.createIfNotExist(fruitToStore[i]);
}
},
createListener: function() {
if(this.get('createIfNotExistTaskCounter') !== 0) return;
this.get('store').find('fruit').then(function(results) {
// should only print three fruits, one of each type
for (var i = 0; i < results.content.length; i++) {
console.log(results.content[i].get('name'));
};
});
}.observes('createIfNotExistTaskCounter'),
createIfNotExist: function(f) {
var store = this.get('store'),
queue = this.get('createIfNotExistQueue'),
that = this;
// prevent duplicate records being created by adding every (distinct) item to a queue
// the queue is used because there seems to be no way to tell if an item is already (asynchonously) being found / created / saved
if(queue.indexOf(f) !== -1) {
that.decrementProperty('createIfNotExistTaskCounter');
return;
}
queue.push(f);
// find or create
store.find('fruit', {name: f}).then(function(results) {
// found...
if(results.get('length') !== 0) {
that.decrementProperty('createIfNotExistTaskCounter');
return;
}
// ...else create
var fruit = store.createRecord('fruit', {name: f});
fruit.save().then(function() {
that.decrementProperty('createIfNotExistTaskCounter');
}, function() {
console.log("save failed!");
});
});
}
});
If you return a promise from a then callback, you can create a chain of promises that behaves quite like a queue.
First you start with an already resolved callback, then you keep replacing it with a "then"-able object.
queue: new Ember.RSVP.resolve,
addToQueue: function() {
this.queue = this.queue.then(function() {
return new Ember.RSVP.Promise(function(resolve, reject){
// something that eventually calls resolve
})
})
}
Here's my updated JSBin of your code: http://jsbin.com/OtoZowI/2/edit?html,console
There is probably a way to make that much smaller if you can work out a way to return the existing find / save promises instead of creating a new one. I played with it a bit but I need to get back to work :P
Also, you can collect together a bunch of promises with RSVP.all and resolve only once they're all resolved. Depending on your actual code, this might be a much cleaner solution - do all the finds, wait until they're all resolved, then create the missing objects.
The RSVP docs have a good example of that here: https://github.com/tildeio/rsvp.js/blob/master/README.md#arrays-of-promises

Categories