rethinkdb / horizon-docs

Other
24 stars 36 forks source link

Document .aggregate and .model #99

Closed deontologician closed 8 years ago

deontologician commented 8 years ago

Placeholder issue, I'll put in the rough cut of this tomorrow

deontologician commented 8 years ago

Aggregate

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:

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: { ... }
})

Model

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()
deontologician commented 8 years ago

Marshall also made Promises work in aggregates, so that's allowed now too

mlucy commented 8 years ago

@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, ...].)

mlucy commented 8 years ago

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.

deontologician commented 8 years ago

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

chipotle commented 8 years ago

So just so I'm clear, this is a method on the Horizon object, not Collection (because it operates on multiple collections).

deontologician commented 8 years ago

Right, the horizon toplevel object itself

geddski commented 8 years ago

So. Freaking. Great.

chipotle commented 8 years ago

(Since this is merged into the horizon-2.0 branch I'm closing this issue.)