firebase / geofire-js

GeoFire for JavaScript - Realtime location queries with Firebase
MIT License
1.45k stars 345 forks source link

Multi-paths update and Geofire #76

Open cmellinas opened 9 years ago

cmellinas commented 9 years ago

Hi,

As mentioned here, I think it should be great to easily multi update data paths and a geofire path at the same time. As the set of Geofire is pretty complex, it's hard to directly copy/paste the code to make it works.

Here the use case : (I use the same as the link above but add a path to know where a post is located). So the structure :

{
  posts: {
    post1: {
      from : user1,
      message: "Hi",
    },
    post2: {
      from : user2,
      message: "Hey",
    }
  },
  posts-location: {
    post1: {
      g : 'xxxxxxxx',
      l: {
         0 : latitude,
         1 : longitude
      }
    },
    post2: {
       g : 'xxxxxxxx',
       l: {
         0 : latitude,
         1 : longitude
      }
    }
  },
  users: {
    user1: {
      posts: {
        post1: true
      }
    },
    user2: {
      posts: {
        post2: true
      }
    }
  }
}

Now I can add a post at the same time at users and posts (with multi-path update). But I need to set the position after. In my use case, a post without a position is not valid data, it's for that the best should be to do the 3 updates at the same times (if one fails, all fail).

Deleting Geofire path is easy (using update(null)), we can multi-update for delete, but for set/update, I loop for a way to do that easily,

Cheers, Clément

jwngr commented 9 years ago

I think this is a great suggestion and I think we can find a way to work this into the API. It will be a little while before I have time to implement this, but I'm happy to code review PRs from the community. Otherwise, once I have some time to take a crack at this, I'll go ahead and see how we can support this kind of feature. Thanks for opening this issue!

cmellinas commented 9 years ago

Ah great Jacob Hope you will found a solution

Many thanks, Clément

kz commented 8 years ago

+1. Atomic updates are really important to ensure the integrity of the database.

vikas-anvaya commented 8 years ago

Do we have a solution for the problem now.

ghost commented 8 years ago

+1

maybe instead return _firebaseRef.update(newData); use return newData; ... then we can use ref().update function with other data

jcheroske commented 8 years ago

Playing fast and loose here, so disregard if this is totally whacked, but couldn't we just have a method that returns the geofire data that would be updated? Then we could just use the existing atomic update api.

mversteeg3 commented 5 years ago

Any movement on this? I like @jcheroske's suggestion as long as there isn't any behind the scenes wizardry in the "set" method that would confound this solution

deflexable commented 2 years ago

checking the set function inside module at ../node_modules/geofire/dist/geofire/index.cjs.js" there wasn't really sophisticated method or external library that help update geofire hash to firebase. It's just plan method and validation.

as @ghost suggested earlier, instead of returning a promise for updating the data, you can simple return newData, then do the uploading yourself.

you can simply copy and paste below function, then you can do something like

`var map = {}; map['eventIndex/' + v] = getGeoFireHashes(eventKey, [location.lat, location.lon]);

firebase.database().ref('/').update(map);`

`function getGeoFireHashes(keyOrLocations, location) { var locations; if (typeof keyOrLocations === 'string' && keyOrLocations.length !== 0) { // If this is a set for a single location, convert it into a object locations = {}; locations[keyOrLocations] = location; } else if (typeof keyOrLocations === 'object') { if (typeof location !== 'undefined') { throw new Error('The location argument should not be used if you pass an object to set().'); } locations = keyOrLocations; } else { throw new Error('keyOrLocations must be a string or a mapping of key - location pairs.'); } var newData = {}; Object.keys(locations).forEach(function (key) { validateKey(key); var location = locations[key]; if (location === null) { // Setting location to null is valid since it will remove the key newData[key] = null; } else { validateLocation(location); var geohash = geohashForLocation(location); newData[key] = encodeGeoFireObject(location, geohash); } }); return newData; }

/**

/**

/**

function validateKey(key) { var error; if (typeof key !== 'string') { error = 'key must be a string'; } else if (key.length === 0) { error = 'key cannot be the empty string'; } else if (1 + GEOHASH_PRECISION + key.length > 755) { // Firebase can only stored child paths up to 768 characters // The child path for this key is at the least: 'i/key' error = 'key is too long to be stored in Firebase'; } else if (/[[].#$\/\u0000-\u001F\u007F]/.test(key)) { // Firebase does not allow node keys to contain the following characters error = 'key cannot contain any of the following characters: . # $ ] [ /'; } if (typeof error !== 'undefined') { throw new Error('Invalid GeoFire key \'' + key + '\': ' + error); } }

function validateLocation(location) { var error; if (!Array.isArray(location)) { error = 'location must be an array'; } else if (location.length !== 2) { error = 'expected array of length 2, got length ' + location.length; } else { var latitude = location[0]; var longitude = location[1]; if (typeof latitude !== 'number' || isNaN(latitude)) { error = 'latitude must be a number'; } else if (latitude < -90 || latitude > 90) { error = 'latitude must be within the range [-90, 90]'; } else if (typeof longitude !== 'number' || isNaN(longitude)) { error = 'longitude must be a number'; } else if (longitude < -180 || longitude > 180) { error = 'longitude must be within the range [-180, 180]'; } } if (typeof error !== 'undefined') { throw new Error('Invalid GeoFire location \'' + location + '\': ' + error); } }`