joinr

Joins related documents efficiently with MongoDB and compatible databases

npm install joinr
4 downloads in the last day
40 downloads in the last week
194 downloads in the last month

joinr

joinr makes it easy to fetch related documents when working with MongoDB collections, or documents in a similar database.

joinr allows joins to be performed via IDs stored in a regular property (byOne) or in an array property (byArray). Joins can be performed when the ID of the related document is in the document you already have (byOne or byArray) and also when the related documents contain the IDs of documents you already have (byOneReverse and byArrayReverse).

joinr can also fetch information about the relationship between two objects. For instance, if each person can work for several departments and must have a separate job title in each department, joinr can handle that without the need for a third MongoDB collection.

joinr emphasizes performance. Fetch the relevant documents first; that gives you a chance to limit the results. Then use joinr to fetch related documents with a single MongoDB query. If the same object is related to several other objects, they will all refer to the same copy to save memory.

Installation

npm install joinr

Tip: we recommend --save so that joinr is automatically added to your package.json.

Quick Example

var joinr = require('joinr');

// Fetch events, then join them with places
eventsCollection.find({ ... }, function(err, events) {
  joinr.oneToOne(events, 'placeId', '_place', function(ids, callback) {
    return eventsCollection.find({ _id: { $in: ids } }, callback);
  }, function(err) {
    // Do something with your events now that they have their
    // related place in a ._place property
  }
});

Requirements

Technically, none. Typically you'll want to use it with MongoDB. joinr assumes that objects have an _id property.

API

joinr.byOne

Performs a one-to-one join with related documents, based on an ID stored in the documents you already have.

If you have events and wish to bring a place object into a ._place property of each event based on a .placeId property of each event, this is what you want.

The first argument should be an array of documents already fetched.

The second argument is the property in each of those documents that identifies a related document (for instance, placeId).

The third argument is the property name in which to store the related document after fetching it (for instance, _place).

The fourth argument is the function to call to fetch the related documents. This function will receive an array of IDs and a callback.

The fifth argument is the callback, which will receive an error if any. The related documents will be attached directly to items under the property name objectField, so there is no need to return values.

Example

joinr.byOne(events, 'placeId', '_place', function(ids, callback) {
  return placesCollection.find({ _id: { $in: ids } }, callback);
}, callback);

joinr.byOneReverse

Join with related documents where the id of documents in your collection is stored in a property of the related documents. Since more than one related document might refer to each of your documents, the results will be stored in an array property of each of your documents.

If you have places and wish to retrieve all the events which have a placeId property referring to those places, this is what you want.

The first argument should be an array of documents already fetched.

The second argument is the property in each of the related documents that identifies documents in your original collection (for instance, placeId).

The third argument is the array property name in which to store the related documents after fetching them (for instance, _events).

The fourth argument is the function to call to fetch the related documents. This function will receive an array of IDs and a callback. These IDs refer to documents in your original collection.

The fifth argument is the callback, which will receive an error if any. The related documents will be attached directly to items under the property name objectField, so there is no need to return values.

Example

joinr.byOneReverse(places, 'placeId', '_place', function(ids, callback) {
  return eventsCollection.find({ placeId: { $in: ids } }, callback);
}, callback);

joinr.byArray

Perform a one-to-many join with related documents via an array property of your documents.

If you have users and wish to bring all associated groups into a ._groups property based on a .groupIds array property, this is what you want.

The first argument should be an array of documents already fetched.

The second argument is the array property in each of those documents that identifies related documents (for instance, groupIds).

The optional third argument is the name of an object property in each of those documents that describes the relationship between the document and each of the related documents. This object is expected to be structured like this:

personRelationships: {
  idOfPerson1: {
    jobTitle: 'Chief Cook'
  },
  idOfPerson2: {
    jobTitle: 'Chief Bottle Washer'
  }
}

The fourth argument is the array property name in which to store the related documents after fetching them (for instance, _groups).

The fifth argument is the function to call to fetch the related documents. This function will receive an array of IDs and a callback. This function should invoke its callback with an error, if any, and if no error, an array of retrieved documents.

The final argument is the main callback, which will receive an error if any. The related documents will be attached directly to the items under the property name specified by objectsField, so there is no need to return values.

If the relationshipsField argument is present, then the related documents are not attached directly in the array property. Instead each entry of the array is an object with two properties, item and relationship. The item property points to the actual object, and the relationship property points to the relationship data for that object. For instance:

person._groups[0].item.name <-- Group's name person._groups[0].relationship.jobTitle <-- Person's job title in this specific department; they may have other titles in other departments

The main callback receives an error object if any.

Example

joinr.byArray(users, 'groupIds', '_groups', function(ids, callback) {
  return groupsCollection.find({ _id: { $in: ids } }, callback);
}, callback);

Or:

joinr.byArray(users, 'groupIds', 'groupRelationships', '_groups', function(ids, callback) {
  return groupsCollection.find({ _id: { $in: ids } }, callback);
}, callback);

joinr.byArrayReverse

Performs a one-to-many join with related documents via an array property of the related documents.

If you have groups and wish to bring all associated users into a ._users property based on a .groupIds array property of those users, this is what you want.

The first argument should be an array of documents already fetched.

The second argument is the array property in each of the related documents that identifies documents in your original collection (for instance, groupIds).

The optional third argument is the name of an object property in each of those documents that describes the relationship between the document and each of the related documents. This object is expected to be structured like this:

personRelationships: {
  idOfPerson1: {
    jobTitle: 'Chief Cook'
  },
  idOfPerson2: {
    jobTitle: 'Chief Bottle Washer'
  }
}

The fourth argument is the array property name in which to store the related documents after fetching them (for instance, _users).

The fifth argument is the function to call to fetch the related documents. This function will receive an array of IDs referring to documents in your original collection, and a callback. This function should invoke its callback with an error if any, and if no error, an array of retrieved documents.

The sixth argument is the main callback, which will receive an error if any. The related documents will be attached directly to the items under the property name specified by objectsField, so there is no need to return values.

The main callback receives an error, if any.

Example

joinr.byArrayReverse(groups, 'groupIds', '_users', function(ids, callback) {
  return usersCollection.find({ placeIds: { $in: ids } }, callback);
}, callback);

Accessing Nested Properties With Dot Notation

If you have departments and the IDs of the people are stored in a nested property, like this:

{
  name: 'Chemistry',
  settings: {
    personIds: [ 5, 7, 9 ]
  }
}

Then you may use that property with joinr by writing this:

joinr.byArray(departments, 'settings.personIds', 'people', function(ids, callback) {
  return peopleCollection.find({ _id: { $in: ids } }, callback);
}, callback);

Note the use of settings.personIds.

You may nest properties as deeply as desired.

You may also pass a function that returns the desired property of any object passed to it.

This feature is available for the idField, idsField and relationshipsField options only. It is not currently possible to store the results in a nested property. Of course you could easily move them there after using joinr.

This syntax is identical to that supported by MongoDB for the same purpose.

Changelog

Version 0.1.3 throws a meaningful error rather than a confusing one if undefined is passed for idField or idsField (most likely to happen because that property was left out when configuring an Apostrophe site).

Version 0.1.2 of joinr added support for relationship properties via the relationshipsField option to joinByArray and joinByArrayReverse. This allows, for instance, employees to have separate job titles in each of the departments they are associated with. Version 0.1.1 of joinr added support for dot notation when specifying idField and idsField.

About P'unk Avenue and Apostrophe

joinr was created at P'unk Avenue for use in Apostrophe, an open-source content management system built on node.js. If you like joinr you should definitely check out apostrophenow.org. Also be sure to visit us on github.

Support

Feel free to open issues on github.

npm loves you