herteby:denormalize

v0.6.2Published 7 years ago

This package has not had recent updates. Please investigate it's current state before committing to using it in your project.

Denormalize

Simple denormalization for Meteor

meteor add herteby:denormalize

In this readme, parent always refers to the documents in which the cache is stored, while child refers to the documents that will be cached.

Example: You have two collections - Users and Roles. The Users store the _id of any Roles they have been assigned. If you want each User to cache information from any Roles that are assigned to it, the Users would be the parents and the Roles would be the children, and it would be either a one or many relationship, depending on if a User can have multiple Roles. If you wanted each Role to store a list of all Users which have that role, the Roles would be the parents and the Users would be the children, and it would be a inverse or many-inverse relationship.

Collection.cache(options)

1ParentCollection.cache({
2  type:'one',
3  collection:ChildCollection,
4  fields:['name','title'],
5  referenceField:'childId',
6  cacheField:'_cache'
7})

Notes about arrays:

  • When cacheField is an array (all types except "one"), the order of the children is not guaranteed.
  • When referenceField is an array, if it contains duplicate _ids, they will be ignored. The cacheField will always contain unique children.

Collection.cacheCount(options)

1ParentCollection.cacheCount({
2  collection:ChildCollection,
3  referenceField:'parentId',
4  cacheField:'count',
5  selector:{done:null, priority:{$lt:3}}
6})

cacheCount() can be used on "inverse" and "many-inverse" relationships

Collection.cacheField(options)

1Collection.cacheField({
2  fields:['profile.firstName', 'profile.lastName'],
3  cacheField:'fullname',
4  transform(doc){
5    return doc.profile.firstName + ' ' + doc.profile.lastName
6  }
7})
8

Migration

If you decide to add a new cache or change the cache options on a collection that already contains documents, those documents need to be updated. There are two options for this:

migrate(collectionName, cacheField, [selector])

1import {migrate} from 'meteor/herteby:denormalize'
2migrate('users', 'fullName')
3migrate('users', 'fullAddress', {fullAddress:{$exists:false}})

This updates the specified cacheField for all documents in the collection, or all documents matching the selector. Selector can also be an _id.

autoMigrate()

1import {autoMigrate} from 'meteor/herteby:denormalize'
2autoMigrate() //should be called last in your server code, after all caches have been declared

When automigrate() is called, it checks all the caches you have declared against a collection (called _cacheMigrations in the DB) to see wether they need to be migrated. If any do, it will run a migration on them, and then save the options to _cacheMigrations, so that it won't run again unless you change any of the options. If you later for example decide to add another field to the cache, it will rerun automatically!

Nested referenceFields

For "one" and "inverse", nested referenceFields are simply declared like referenceField:'nested.reference.field'

For "many" and "many-inverse", if the referenceField is an Array containing objects, a colon is used to show where the Array starts.

Example:

If the parent doc looks like this:

1{
2  references:{
3    users:[{_id:'user1'}, {_id:'user2'}]
4  }
5}

The referenceField string should be 'references.users:_id'

Recursive caching

You can use the output (the cacheField) of one cache function as one of the fields to be cached by another cache function, or even as the referenceField. They will all be updated correctly.

In the examples below, all cache fields start with _, which may be a good convention to follow for all caches.

Use cacheField() to cache the sum of all cached items from a purchase

1Bills.cacheField({
2  fields:['_items'],
3  cacheField:'_sum',
4  transform(doc){
5    let price = _.sum(_.map(doc._items, 'price'))
6    return price
7  }
8})

Caching the cacheFields of another cache

1Bills.cache({
2  cacheField:'_items',
3  collection:Items,
4  type:'many',
5  referenceField:'itemIds',
6  fields:['name', 'price']
7})
8Customers.cache({
9  cacheField:'_bills',
10  collection:Bills,
11  type:'inverse',
12  referenceField:'customerId',
13  fields:['_sum', '_items']
14})

Using the cacheField of another cache as referenceField

1Customers.cache({
2  cacheField:'_bills2',
3  collection:Bills,
4  type:'inverse',
5  referenceField:'customerId',
6  fields:['itemIds', '_sum']
7})
8Customers.cache({
9  cacheField:'_items',
10  collection:Items,
11  type:'many',
12  referenceField:'_bills2:itemIds',
13  fields:['name', 'price']
14})

Testing the package

meteor test-packages packages/denormalize --driver-package=practicalmeteor:mocha

(Then open localhost:3000 in your browser) The package currently has over 120 tests the "slowness warnings" in the results are just due to the asynchronous tests