Adding additional children when setting in GeoFire Firebase? - firebase

I'm trying to set additional info when setting geoFire object in FB.
Something like:
geoFire.set(uniqueId, [latitude, longitude],{'message': message, 'iconUrl': iconURL, lastUpdate: Firebase.ServerValue.TIMESTAMP}).then(function () {
//some stuff
};
I can do it like this and it mostly works (depends on db roundtrip time):
geoFire.set(uniqueId, [latitude, longitude],).then(function () {
firebaseRef.child(uniqueId).update({'message': message, 'iconUrl': iconURL, lastUpdate: Firebase.ServerValue.TIMESTAMP});
})
but my listener triggers too fast and doesn't capture all the data so it renders without icon path.

I had the same problem, didn't want to solve this with a workaround, especially by introducing the delay so kept digging and found this: https://github.com/firebase/geofire-js/issues/40
This basically means we need to restructure the application, where one collection is dedicated to the GeoFire information, another uses the same key to store attached information. Example from the GitHub.
{
geofire: {
"-J9aZZl94Sx6h": { "g": <geohash>, "l": {} }
"-Jhacf97x4S3h": { "g": <geohash>, "l": {} },
...
},
"locations": {
"-J9aZZl94Sx6h": { <location-data> },
"-Jhacf97x4S3h": { <location-data> }
}
}

Related

Meteor.user with Additional Fields on Client

In Meteor, one can add additional fields to the root-level of the new user document like so:
// See: https://guide.meteor.com/accounts.html#adding-fields-on-registration
Accounts.onCreateUser((options, user) =>
// Add custom field to user document...
user.customField = "custom data";
return user;
});
On the client, one can retrieve some data about the current user like so:
// { _id: "...", emails: [...] }
Meteor.user()
By default, the customField does not exist on the returned user. How can one retrieve that additional field via the Meteor.user() call such that we get { _id: "...", emails: [...], customField: "..." }? At present, the documentation on publishing custom data appears to suggest publishing an additional collection. This is undesired for reasons of overhead in code and traffic. Can one override the default fields for Meteor.user() calls to provide additional fields?
You have a couple of solutions that you can use to solve this.
Null Publication
Meteor.publish(null, function () {
if (this.userId !== null) {
return Meteor.users.find({ _id: this.userId }, { fields: { customField: 1 } });
} else {
return this.ready();
}
}, { is_auto: true });
This will give you the desired result but will also result in an additional database lookup.. While this is don't by _id and is extremely efficient, I still find this to be an unnecessary overhead.
2.Updating the fields the Meteor publishes for the user by default.
Accounts._defaultPublishFields.projection = { customField: 1, ...Accounts._defaultPublishFields.projection };
This has to be ran outside of any Meteor.startup blocks. If ran within one, this will not work. This method will not result in extra calls to your database and is my preferred method of accomplishing this.
You are actually misunderstanding the documentation. It is not suggesting to populate and publish a separate collection, just a separate publication. That's different. You can have multiple publications/subscriptions that all feed the same collection. So all you need to do is:
Server:
Meteor.publish('my-custom-user-data', function() {
return Meteor.users.find(this.userId, {fields: {customField: 1}});
});
Client:
Meteor.subscribe('my-custom-user-data');

Vuejs & Firestore - How to Update when Data Changes in Firestore

I've gone through a bunch of tutorials and docs but cannot seem to be able to update on page when data changes in Firestore (NOTE: not Firebase)
Heres what I have currently which is working fine except if data changes in the DB it is not reflected on the page itself unless I refresh. Code below is within script tags:
import { recipeRef } from '../../firebase';
export default {
data() {
return {
recipes: []
}
},
firestore: {
recipes: recipeRef
},
created() {
db.collection('recipes').get().then((onSnapshot) => {
this.loading = false
onSnapshot.forEach((doc) => {
let data = {
'id': doc.id,
'name': doc.data().name
}
this.recipes.push(data)
})
})
}
I'm not using Vuex. Adding data, editing and reading works fine. Just not reflecting changes once data has changed. Maybe there is a life cycle hook Im supposed to be using? For "onSnapshot" - Ive tried "snap", "querySnapshot" etc. No luck.
Thanks in advance.
Remove the get() and just replace with snapshot - like so
created() {
db.collection('recipes').onSnapshot(snap => {
let foo = [];
snap.forEach(doc => {
foo.push({id: doc.id, name: doc.data().name})
});
}
});
I am not familiar with the firestore API, but glancing through the docs, it looks like calling get() is how you query a single time. Where you have onSnapshot should really be querySnapshot -- that is, the results of a one query. See:
https://firebase.google.com/docs/firestore/query-data/get-data
versus:
https://firebase.google.com/docs/firestore/query-data/listen
So to get live updates, it looks like you need to create a listener, like so:
db.collection('recipes')
.onSnapshot(function(snapshot) {
snapshot.forEach(function(doc) {
// Find existing recipe in this.recipes
// and swap in the new data
});
}, function(error) {
// handle errors
});
I think you will need to add that listener in addition to the get() query you are currently doing. Hope this helps!

Publish (send) values from server in Meteor?

I have a collection that I publish to client, limiting number of documents and sending ones that matter to client. Like this:
Meteor.publish('list', function() {
return someList.find( { matters: true }, { limit: 50 } );
});
But I also want to send to clients some other values from that collection, like total count of documents, average value of some field, etc.
How to make server to calculate it and send value to client how will just display it?
For simple counts, you can use the publish-counts package.
For something more advanced (like an average) could:
Use an observer - see my answer to this question.
Write the statistics back into a collection and publish that. A good way to accomplish that is with collection-hooks. Essentially, after every update you could modify a document containing the averages and other stats.
Write a method to compute the values and return them to the client. This has the advantage of being the simplest solution, but it lacks reactivity.
Personally I use a global variable, and return it in a method.
collec_count = 0;
Meteor.publish('list', function() {
var coll = someList.find( { matters: true }, { limit: 50 } );
collec_count = coll.count();
return coll;
});
Meteor.methods({
findDB_count: function () {
return collec_count;
},
Then on the client side I just call the method inside an helper/event:
Meteor.call('findDB_count', function (error, result) {
if (error) {
alert(error.reason);
} else {
Session.set("count", result);
}
});

Sails.js oneToOne pointing to last on oneToMany

A design wonder in sails.js and waterline models.
I have a "bear" model and a "location" one. They hold a oneToMany association ("bear can have multiple location over time").
bear.js
module.exports = {
attributes: {
location: {
collection: 'location',
via: 'bear'
}
}
};
location.js
module.exports = {
attributes: {
timestamp: {
type: 'datetime',
required: true
},
bear: {
model: 'bear'
}
}
};
I'm trying to make a "oneToOne" association grabbing the last location of a bear (lastLocation), and wondering what's the best solution :
Trying to override ToJSON on bear, fetching last location (doesn't seem to work, because ToJSON is hardly synchronous so I can't make it wait for an asynchronous find)
Creating a real oneToOne relationship and adding a hook AfterCreate on location to update that "lastLocation" association ?
Override blueprint create on location ?
Override blueprint add on bear ?
Any other idea ?
Thanks =)
I think this is your answer here?
Creating a real oneToOne relationship and adding a hook AfterCreate on location to update that "lastLocation" association ?
You should create an additional attribute on bear called lastLocation:{model:'location'}, and automatically update that after each location is created on the associated bear. Their are otherways to do this, but if the last location is something that is read a lot, then this will probably be your best approach.
Now this is a little bit of opinion based as there would be many factors involved in your design to say for sure, but I think you will be fine using this approach.
Here is what I ended up with :
model/Bear.js - last location association
lastLocation: {
model: 'location'
}
model/Location.js - afterCreate hook
afterCreate: function(insertedLocation, callback) {
if(!insertedLocation.bear) {
return callback();
}
Bear.findOneById(insertedLocation.bear, function(err, bear) {
if(err) {
return callback(err);
}
Location.findOneById(bear.lastLocation, function(err, oldLastLocation) {
bear.lastLocation = insertedLocation.id;
bear.save(function(err) {
return callback(err);
});
});
});
}
the if(!insertedLocation.bear) test is the key
I repeated similar code inside afterUpdate hook
afterUpdate: function(updatedLocation, callback) {
if(!updatedLocation.bear) {
return callback();
}
Bear.findOneById(updatedLocation.bear, function(err, bear) {
if(err) {
return callback(err);
}
Location.findOneById(bear.lastLocation, function(err, oldLastLocation) {
bear.lastLocation = updatedLocation.id;
bear.save(function(err) {
return callback(err);
});
});
});
}
adding a test in order to prevent updating bear each time we update lastLocation
One could add some PublishUpdate() logic

Different URLs for Model and Collection in Titanium Alloy

Env: Titanium 3.1.3, Alloy 1.2.2.
I'm using the following adapter for persistence on the models/collections: https://github.com/viezel/napp.alloy.adapter.restapi
I have an API that has a different URL structure for a collection than it does a single model. Consider the following:
To get a single record: [GET] /files/:id
To get all the files for a user: [GET] /users/:id/files
I have the following schema for files.js:
exports.definition = {
config: {
"URL": "https://my.api.here/files",
//"debug": 1,
"adapter": {
"type": "restapi",
"collection_name": "files",
"idAttribute": "id"
}
},
extendModel: function(Model) {
_.extend(Model.prototype, {});
return Model;
},
extendCollection: function(Collection) {
_.extend(Collection.prototype, {
initialize: function(){
this.url = "http://my.api.here/users/"+this.user_id+"/files";
}
});
return Collection;
}
}
What I'm trying to do in the above is override the collection initialize method to change the URL structure for the collection. I then call this accordingly:
var currentUserFiles = Alloy.createCollection("files", {user_id:"12345"});
currentUserFiles.fetch({
success: function(files){
console.log("Woo! Got the user's files!");
console.log(JSON.stringify(files.models));
},
error: function(){
console.log("Nope");
}
});
This doesn't work. The fetch() method just continues to try to call /files. I've tried setting url as a property on the collection after it's created, that also don't work.
Ideally, I'd like to do this for both local instances as well as the singleton version of the collection.
So - the question is: can I utilize a different URL for a collection than I do for a model? Obviously, I don't want to just call /files and sort/filter client-side - that'd be a nightmare with a lot of records. What am I missing here?
It's a bit late but for anyone else that comes across this. I problem is where/how the url is specified for model and collection. The model needs a specific id (eg: primary key) passed into it because the model can only be one object. If you need more than one object, then use the collection. Hope this helps :)
extendModel : function(Model) {
_.extend(Model.prototype, {
url : function() {
return "http://my.api.here/users/"+this.user_id+"/files/"+ FILE_ID
},
});
return Model;
},
extendCollection : function(Collection) {
_.extend(Collection.prototype, {
url : function() {
return "http://my.api.here/users/"+this.user_id+"/files"
},
});
},

Resources