Closed deontologician closed 8 years ago
You can now aggregate horizon queries together like this:
let horizon = Horizon()
let aggregated = horizon.aggregate({
owner: horizon('people').find('bob'),
pet: horizon('animals').find('spot'),
related: {
friends: horizon('people').findAll({friendOf: 'bob'}).limit(10),
petFriends: horizon('animals').findAll({friendOf: 'spot'}).limit(10),
},
someConstant: "Always have this here",
petsAndOwners: [ hz('pets'), hz('owners') ] // this is nonsensical, but you know, example
})
You can now call .fetch()
or .watch()
on the aggregate, and it will do what you expect. With .watch()
, it will internally emit a new document any time any sub-query receives a change, it caches the results of all other subqueries and re-emits them.
Aggregates can be composed of several things:
aggregate([hz('foo'), hz('bar')])
will result in an array that has the results of the foo
and bar
tables flattened into one. There's no guarantee on ordering.null
, numbers
, strings
, boolean
s, Date
s and ArrayBuffer
s can be put into aggregates and they'll be emitted as-is in the final datastructure. (Arrays and objects that contain literals will be emitted as-is, so this works as you'd expect: hz.aggregate([1, true, 'bob']).fetch()
-> [1, true, 'bob']
hz.aggregate(hz('foo').find(2))
is equivalent to hz('foo').find(2)
.fetch()
and .watch()
. This can be helpful if you want to update a counter or the like:hz.aggregate({
counter: Observable.timer(0, 1000),
someQuery: hz('foo').find('bar'),
}).watch().subscribe({ next(x){ console.log(x) }})
Every second (or whenever the someQuery
gets a change), the entire datastructure will be re-emitted with an incremented counter.
You can also nest aggregates, like:
hz.aggregate({
foo: hz.aggregate({ ... })
})
But this isn't super useful, since it's equivalent to just doing:
hz.aggregate({
foo: { ... }
})
You can also parameterize aggregates using the model
method. It's fairly simple and just allows a convenient syntax for creating templates for aggregates:
const MyModel = hz.model((a, b, c) => {
return {
foo: hz('foo').find(a),
bar: hz('bar').find(b),
baz: hz('baz').find(c),
}
})
// Sometime later, use it
var modelA = MyModel(1, 2, 3)
var modelB = MyModel(2, 3, 4)
modelInstance.fetch().subscribe(x => console.log(x))
You can do anything you can do with aggregates here, but it's worth mentioning you can also nest models (which will seem maybe more useful than nesting aggregates, even though it's equivalent):
const Pet = hz.model(petId => hz('pets').find(petId)) // note you can aggregate a bare query
const Owner = hz.model( ownerId => hz('owners').find(ownerId) )
const PetAndOwner = hz.model((petId, ownerId) => {
pet: Pet(petId),
owner: Owner(ownerId),
})
var marcAndSkyla = PetAndOwner('skyla', 'marc')
var dalanAndRes = PetAndOwner('res', 'dalan')
marcAndSkyla.fetch()
dalanAndRes.watch()
Marshall also made Promises work in aggregates, so that's allowed now too
@deontologician --
Arrays: aggregate([hz('foo'), hz('bar')]) will result in an array that has the results of the foo and bar tables flattened into one. There's no guarantee on ordering.
What's the behavior .aggregate([1, hz('bar')])
?
What about.aggregate([[1, 2], hz('bar')])
?
What about.aggregate([[1, 2], hz('bar'), hz('foo')])
?
What about.aggregate([[1, 2], hz('bar'), [3, 4], hz('foo')])
?
Is there any way to get back an array containing two arrays each of which comes from a Horizon query (i.e. to get [[foo1, foo2, ...], [bar1, bar2, ...]]
instead of [foo1, bar1, foo2, bar2, ...]
)?
(Also, are we super attached to the fact thathorizon.aggregate([FOO, BAR])
does an implicit union of the two streams? That seems kind of wacky to me, especially because it looks like we aren't implicitly unioning non-Horizon objects when they're put inside the same syntax. I would expect hz.aggregate([FOO, BAR])
to give me the [[foo1, foo2, ...], [bar1, bar2, ...]]
shape. Another option would be to make hz.aggregate([FOO, BAR])
return [[foo1, foo2, ...], [bar1, bar2, ...]]
and hz.aggregate(FOO, BAR)
return [foo1, bar2, foo2, bar2, ...]
.)
Actually, thinking about it more, it seems kinda bad that hz.aggregate([FOO, BAR])
does an implicit union. For everything else we seem to have the property the hz.aggregate(f(FOO))
is analogous to FOO.fetch().subscribe(x => hz.aggregate(f(x)))
(ignoring the updating). Doing an implicit union for Horizon queries but not for other objects breaks that property.
Sounds good, I'll change it. We already have a way to union queries anyway (hz('foo').fetch().merge(hz('bar').fetch())
), so a special syntax for it isn't really warranted probably.
I would like to take this moment to grumble that it's more work for me
So just so I'm clear, this is a method on the Horizon
object, not Collection
(because it operates on multiple collections).
Right, the horizon toplevel object itself
So. Freaking. Great.
(Since this is merged into the horizon-2.0
branch I'm closing this issue.)
Placeholder issue, I'll put in the rough cut of this tomorrow