Skip to content

Instantly share code, notes, and snippets.

@jessetane
Last active August 29, 2015 14:22
Show Gist options
  • Save jessetane/7fc39edc663a4d4be1a4 to your computer and use it in GitHub Desktop.
Save jessetane/7fc39edc663a4d4be1a4 to your computer and use it in GitHub Desktop.
firebase-model
module.exports = Model
var events = require('events')
var inherits = require('inherits')
var queue = require('queue')
inherits(Model, events.EventEmitter)
function Model (storage, data) {
this.id = storage.key()
this.collectionId = storage.parent().key()
this.storage = {
public: storage,
private: storage.parent().parent().child('private/' + this.collectionId + '/' + this.id),
unique: storage.parent().parent().child('unique/' + this.collectionId + '/' + this.id)
}
this.data = data || {}
this._onupdate = this._onupdate.bind(this)
events.EventEmitter.call(this)
}
Model.prototype.watch = function () {
if (this.watching) return
this.watching = true
if (this.mine && this.privateFields) {
this.storage
.private
.on('value', this._onupdate, this._onerror)
}
this.storage
.public
.on('value', this._onupdate, this._onerror)
}
Model.prototype.unwatch = function () {
if (!this.watching) return
this.watching = false
if (this.mine && this.privateFields) {
this.storage
.private
.off('value', this._onupdate, this._onerror)
}
this.storage
.public
.off('value', this._onupdate, this._onerror)
}
Model.prototype._watchOnce = function (cb) {
var self = this
var q = queue()
var temp = {
data: {},
privateFields: this.privateFields
}
if (this.mine && this.privateFields) {
q.push(function (cb) {
self.storage
.private
.once('value', self._processSnapshot.bind(temp, cb), cb)
})
}
q.push(function (cb) {
self.storage
.public
.once('value', self._processSnapshot.bind(temp, cb), cb)
})
q.start(function (err) {
if (err) return cb(err)
cb(null, temp.data)
})
}
Model.prototype.update = function (cb) {
if (this.uniqueFields) {
this._watchOnce(this._updateUnique.bind(this, cb))
} else {
this._updatePublicAndPrivate(cb)
}
}
Model.prototype._updateUnique = function (cb, err, oldData) {
if (err) return cb(err)
var self = this
var q = queue()
for (var field in this.uniqueFields) (function (field) {
if (oldData[field] !== this.data[field]) {
var uniqueOld = this.storage
.unique
.child(field)
.child(oldData[field])
var uniqueNew = this.storage
.unique
.child(field)
.child(this.data[field])
q.push(function (cb) {
uniqueNew.set(self.id, function (err) {
if (err) return cb(err)
uniqueOld.remove(cb)
})
})
}
}).call(this, field)
q.start(function (err) {
if (err) return cb(err)
self._updatePublicAndPrivate(cb)
})
}
Model.prototype._updatePublicAndPrivate = function (cb) {
var publicData = {}
var privateData = {}
var publicStorage = this.storage.public
var privateStorage = this.storage.private
var q = queue()
if (this.mine && this.privateFields) {
for (var field in this.data) {
if (this.privateFields[field]) {
privateData[field] = this.data[field]
} else {
publicData[field] = this.data[field]
}
}
q.push(
privateStorage
.update
.bind(privateStorage, privateData)
)
} else {
publicData = this.data
}
q.push(
publicStorage
.update
.bind(publicStorage, publicData)
)
q.start(cb)
}
Model.prototype.destroy = function (cb) {
if (this.privateFields && !this.mine) {
return cb(new Error('cannot destroy a model with private fields that does not belong to you'))
} else if (this.uniqueFields) {
this._watchOnce(this._dodestroy.bind(this, cb))
} else {
this._dodestroy(cb)
}
}
Model.prototype._dodestroy = function (cb, err, oldData) {
if (err) return cb(err)
this.data = oldData || this.data
var q = queue()
var publicStorage = this.storage.public
var privateStorage = this.storage.private
for (var field in this.uniqueFields) {
var unique = this.storage
.unique
.child(field)
.child(this.data[field])
q.push(unique.remove.bind(unique))
}
if (this.mine && this.privateFields) {
q.push(
privateStorage
.remove
.bind(privateStorage)
)
}
q.push(
publicStorage
.remove
.bind(publicStorage)
)
this.unwatch()
q.start(cb)
}
Model.prototype._processSnapshot = function (cb, snapshot) {
var data = snapshot.val()
var isPrivate = snapshot.ref().parent().parent().key() === 'private'
if (isPrivate) {
for (var field in this.privateFields) {
if (data) {
this.data[field] = data[field]
} else {
delete this.data[field]
}
}
} else {
for (var field in this.data) {
if (!this.privateFields || !this.privateFields[field]) {
delete this.data[field]
}
}
for (var field in data) {
this.data[field] = data[field]
}
}
cb && cb()
}
Model.prototype._onupdate = function (snapshot) {
this._processSnapshot(null, snapshot)
this.emit('update')
}
Model.prototype._onerror = function (err) {
this.emit('error', err)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment