bookshelf-jsonapi-params
Version:
Automatically applies relations, filters, and more from the JSON API spec to your Bookshelf.js results
775 lines (636 loc) • 166 kB
JavaScript
'use strict';Object.defineProperty(exports, "__esModule", { value: true });var _slicedToArray = function () {function sliceIterator(arr, i) {var _arr = [];var _n = true;var _d = false;var _e = undefined;try {for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {_arr.push(_s.value);if (i && _arr.length === i) break;}} catch (err) {_d = true;_e = err;} finally {try {if (!_n && _i["return"]) _i["return"]();} finally {if (_d) throw _e;}}return _arr;}return function (arr, i) {if (Array.isArray(arr)) {return arr;} else if (Symbol.iterator in Object(arr)) {return sliceIterator(arr, i);} else {throw new TypeError("Invalid attempt to destructure non-iterable instance");}};}(); // Load modules
/* eslint-disable prefer-const */
var _lodash = require('lodash');
var _splitString = require('split-string');var _splitString2 = _interopRequireDefault(_splitString);
var _inflection = require('inflection');var _inflection2 = _interopRequireDefault(_inflection);
var _bookshelfPage = require('bookshelf-page');var _bookshelfPage2 = _interopRequireDefault(_bookshelfPage);
var _jsonFields = require('./json-fields');var _jsonFields2 = _interopRequireDefault(_jsonFields);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _defineProperty(obj, key, value) {if (key in obj) {Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true });} else {obj[key] = value;}return obj;}function _toConsumableArray(arr) {if (Array.isArray(arr)) {for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) {arr2[i] = arr[i];}return arr2;} else {return Array.from(arr);}}
/**
* Exports a plugin to pass into the bookshelf instance, i.e.:
*
* import config from './knexfile';
* import knex from 'knex';
* import bookshelf from 'bookshelf';
*
* const Bookshelf = bookshelf(knex(config));
*
* Bookshelf.plugin('bookshelf-jsonapi-params');
*
* export default Bookshelf;
*
* The plugin attaches the `fetchJsonApi` instance method to
* the Bookshelf Model object.
*
* See methods below for details.
*/exports.default =
function (Bookshelf) {var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
(0, _lodash.defaults)(options, { nullString: 'null' });
// Load the pagination plugin if its not already there
if (!(0, _lodash.get)(Bookshelf, 'Model.fetchPage')) {
Bookshelf.plugin(_bookshelfPage2.default);
}
/**
* Similar to {@link Model#fetch} and {@link Model#fetchAll}, but specifically
* uses parameters defined by the {@link https://jsonapi.org|JSON API spec} to
* build a query to further refine a result set.
*
* @param opts {object}
* Currently supports the `include`, `fields`, `sort`, `page` and `filter`
* parameters from the {@link https://jsonapi.org|JSON API spec}.
* @param type {string}
* An optional string that specifies the type of resource being retrieved.
* If not specified, type will default to the name of the table associated
* with the model.
* @return {Promise<Model|Collection|null>}
*/
var fetchJsonApi = function fetchJsonApi(opts) {var isCollection = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;var _this = this;var type = arguments[2];var additionalQuery = arguments[3];
opts = (0, _lodash.cloneDeep)(opts) || {};
var internals = {};var _opts =
opts,include = _opts.include,fields = _opts.fields,sort = _opts.sort,_opts$page = _opts.page,page = _opts$page === undefined ? {} : _opts$page,filter = _opts.filter,group = _opts.group;
var filterTypes = ['like', 'not', 'lt', 'gt', 'lte', 'gte', 'or', 'and'];
// Do not add the global flag. The global flag will influence String.prototype.match and will
// return a list of matches instead of matching groups. Changing this will break existing code.
var aggregateFunctionRegex = /(count|sum|avg|max|min)\((.+)\)/;
// Get a reference to the field being used as the id
internals.idAttribute = this.constructor.prototype.idAttribute ?
this.constructor.prototype.idAttribute : 'id';
// Get a reference to the current model name. Note that if no type is
// explicitly passed, the tableName will be used
internals.modelName = this.constructor.prototype.tableName;
internals.modelType = type;
// Used to determine which casting syntax is valid
internals.client = Bookshelf.knex.client.config.client;
// Initialize an instance of the current model and clone the initial query
internals.model =
this.constructor.forge().query(function (qb) {return (0, _lodash.assign)(qb, _this.query().clone());});
/**
* Build a query for single filtering dependency object
* @param value {object}
* @param relationHash {object}
*/
internals.buildObjectLikeFilterDependencies = function (value, relationHash) {
if (!(0, _lodash.isEmpty)(value)) {
(0, _lodash.forEach)(value, function (_, typeKey) {
// Add relations to the relationHash
internals.buildDependenciesHelper(typeKey, relationHash);
});
}
};
/**
* Build a query for array of relational dependencies of filtering
* @param filterList {array}
* @param relationHash {object}
*/
internals.buildOrFilterDependencies = function (filterList, relationHash) {
if (!(0, _lodash.isEmpty)(filterList)) {
(0, _lodash.forEach)(filterList, function (value) {
if ((0, _lodash.isPlainObject)(value)) {
internals.buildDependenciesLoopHelper(value, relationHash);
}
});
}
};
internals.buildDependenciesLoopHelper = function (filterValues, relationHash) {
// Loop through each filter value
(0, _lodash.forEach)(filterValues, function (value, key) {
// If the filter is "OR" or "AND" filter fragments array
if ((0, _lodash.isArray)(value) && (key === 'or' || key === 'and')) {
internals.buildOrFilterDependencies(value, relationHash);
}
// If the filter is not an equality filter
if ((0, _lodash.isObjectLike)(value) && !(0, _lodash.isArray)(value)) {
internals.buildObjectLikeFilterDependencies(value, relationHash);
}
// If the filter is an equality filter
else {
internals.buildDependenciesHelper(key, relationHash);
}
});
};
/**
* Build a query for relational dependencies of filtering, grouping and sorting
* @param filterValues {object}
* @param groupValues {object}
* @param sortValues {object}
*/
internals.buildDependencies = function (filterValues, groupValues, sortValues) {
var relationHash = {};
// Find relations in filterValues
if ((0, _lodash.isObjectLike)(filterValues) && !(0, _lodash.isEmpty)(filterValues)) {
internals.buildDependenciesLoopHelper(filterValues, relationHash);
}
// Find relations in sortValues
if ((0, _lodash.isObjectLike)(sortValues) && !(0, _lodash.isEmpty)(sortValues)) {
// Loop through each sort value
(0, _lodash.forEach)(sortValues, function (value) {
// If the sort value is descending, remove the dash
if (value.indexOf('-') === 0) {
value = value.substr(1);
}
// Add relations to the relationHash
internals.buildDependenciesHelper(value, relationHash);
});
}
// Find relations in groupValues
if ((0, _lodash.isObjectLike)(groupValues) && !(0, _lodash.isEmpty)(groupValues)) {
// Loop through each group value
(0, _lodash.forEach)(groupValues, function (value) {
// Add relations to the relationHash
internals.buildDependenciesHelper(value, relationHash);
});
}
// Need to select model.* so all of the relations are not returned, also check if there is anything in fields object
if ((0, _lodash.keys)(relationHash).length && !(0, _lodash.keys)(fields).length) {
internals.model.query(function (qb) {
qb.select(internals.modelName + '.*');
});
}
// Recurse on each of the relations in relationHash
(0, _lodash.forIn)(relationHash, function (value, key) {
return internals.queryRelations(value, key, _this, internals.modelName);
});
};
/**
* Recursive funtion to add relationships to main query to allow filtering and sorting
* on relationships by using left outer joins
* @param relation {object}
* @param relationKey {string}
* @param parent {object}
* @param parentKey {string}
*/
internals.queryRelations = function (relation, relationKey, parentModel, parentKey) {
// Add left outer joins for the relation
var relatedData = parentModel[relationKey]().relatedData;
internals.model.query(function (qb) {
var foreignKey = relatedData.foreignKey ? relatedData.foreignKey : _inflection2.default.singularize(relatedData.parentTableName) + '_' + relatedData.parentIdAttribute;
if (relatedData.type === 'hasOne' || relatedData.type === 'hasMany') {
qb.leftOuterJoin(relatedData.targetTableName + ' as ' + relationKey,
parentKey + '.' + relatedData.parentIdAttribute,
relationKey + '.' + foreignKey);
} else
if (relatedData.type === 'belongsTo') {
if (relatedData.throughTableName) {
var throughTableAlias = (relationKey + '_' + relatedData.throughTableName + '_pivot').replace(/\./g, '_');
qb.leftOuterJoin(relatedData.throughTableName + ' as ' + throughTableAlias,
parentKey + '.' + relatedData.parentIdAttribute,
throughTableAlias + '.' + relatedData.throughIdAttribute);
qb.leftOuterJoin(relatedData.targetTableName + ' as ' + relationKey,
throughTableAlias + '.' + foreignKey,
relationKey + '.' + relatedData.targetIdAttribute);
} else
{
qb.leftOuterJoin(relatedData.targetTableName + ' as ' + relationKey,
parentKey + '.' + foreignKey,
relationKey + '.' + relatedData.targetIdAttribute);
}
} else
if (relatedData.type === 'belongsToMany') {
var otherKey = relatedData.otherKey ? relatedData.otherKey : _inflection2.default.singularize(relatedData.targetTableName) + '_id';
var joinTableName = relatedData.joinTableName ? relatedData.joinTableName : relatedData.throughTableName;
var joinTableAlias = (relationKey + '_' + joinTableName).replace(/\./g, '_');
qb.leftOuterJoin(joinTableName + ' as ' + joinTableAlias,
parentKey + '.' + relatedData.parentIdAttribute,
joinTableAlias + '.' + foreignKey);
qb.leftOuterJoin(relatedData.targetTableName + ' as ' + relationKey,
joinTableAlias + '.' + otherKey,
relationKey + '.' + relatedData.targetIdAttribute);
} else
if ((0, _lodash.includes)(relatedData.type, 'morph')) {
// Get the morph type and id
var morphType = relatedData.columnNames[0] ? relatedData.columnNames[0] : relatedData.morphName + '_type';
var morphId = relatedData.columnNames[1] ? relatedData.columnNames[0] : relatedData.morphName + '_id';
if (relatedData.type === 'morphOne' || relatedData.type === 'morphMany') {
qb.leftOuterJoin(relatedData.targetTableName + ' as ' + relationKey, function (qbJoin) {
qbJoin.on(relationKey + '.' + morphId, '=', parentKey + '.' + relatedData.parentIdAttribute);
}).where(relationKey + '.' + morphType, '=', relatedData.morphValue);
} else
if (relatedData.type === 'morphTo') {
// Not implemented
}
}
});
if (!(0, _lodash.keys)(relation).length) {
return;
}
(0, _lodash.forIn)(relation, function (value, key) {
return internals.queryRelations(value, key, parentModel[relationKey]().relatedData.target.forge(), relationKey);
});
};
/**
* Adds relations included in the column to the relationHash, used in buildDependencies
* @param column {string}
* @param relationHash {object}
*/
internals.buildDependenciesHelper = function (column, relationHash) {
// Split column on colons, example of column: 'relation.column:jsonbColumn.property:dataType'
var _column$split = column.split(':'),_column$split2 = _slicedToArray(_column$split, 1),key = _column$split2[0];
if ((0, _lodash.includes)(key, '.')) {
// The last item in the chain is a column name, not a table. Do not include column name in relationHash
key = key.substring(0, key.lastIndexOf('.'));
if (!(0, _lodash.has)(relationHash, key)) {
var level = relationHash;
var relations = key.split('.');
var relationModel = _this.clone();
// Traverse the relationHash object and set new relation if it does not exist
(0, _lodash.forEach)(relations, function (relation) {
// Check if valid relationship
if (typeof relationModel[relation] === 'function' && relationModel[relation]().relatedData.type) {
if (!level[relation]) {
level[relation] = {};
}
level = level[relation];
// Set relation model to the next item in the chain
relationModel = relationModel.related(relation).relatedData.target.forge();
} else
{
return false;
}
});
}
}
};
/**
* Build a query based on the `fields` parameter.
* @param fieldNames {object}
*/
internals.buildFields = function () {var fieldNames = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};var expandedIncludes = arguments[1];var includesMap = arguments[2];
if ((0, _lodash.isObject)(fieldNames) && !(0, _lodash.isEmpty)(fieldNames)) {
// Format column names
fieldNames = internals.formatColumnNames(fieldNames);
// Process fields for each type/relation
(0, _lodash.forEach)(fieldNames, function (fieldValue, fieldKey) {
// Add qualifying table name to avoid ambiguous columns
fieldNames[fieldKey] = (0, _lodash.map)(fieldNames[fieldKey], function (value) {
// Extract any aggregate function around the column name
var _value$split = value.split(':'),_value$split2 = _slicedToArray(_value$split, 3),column = _value$split2[0],jsonColumn = _value$split2[1],dataType = _value$split2[2];
var aggregateFunction = null;
var match = aggregateFunctionRegex.exec(value);
if (match) {
aggregateFunction = match[1];
column = match[2];
}
if (!fieldKey || fieldKey === internals.modelType) {
if (!(0, _lodash.includes)(column, '.')) {
column = internals.modelName + '.' + column;
}
} else
{
column = fieldKey + '.' + column;
}
column = (0, _lodash.filter)([column, jsonColumn, dataType]).join(':');
return aggregateFunction ? { aggregateFunction: aggregateFunction, column: column } : column;
});
// Only process the field if it's not a relation. Fields
// for relations are processed in `buildIncludes()`
if (!(0, _lodash.includes)(expandedIncludes, fieldKey)) {
// Add columns to query
internals.model.query(function (qb) {
if (!fieldKey) {
qb.distinct();
}
var fieldsToSelect = fieldNames[fieldKey];
// JSON API considers relationships as fields, so we
// need to make sure the id of the relation is selected
if (!(0, _lodash.isEmpty)(includesMap.relations)) {
fieldsToSelect = (0, _lodash.uniq)((0, _lodash.union)(fieldsToSelect, includesMap.requiredColumns.map(function (column) {return internals.modelName + '.' + column;})));
}
(0, _lodash.forEach)(fieldsToSelect, function (column) {
if (column.aggregateFunction) {
qb[column.aggregateFunction](column.column + ' as ' + column.aggregateFunction);
} else
{var _column$split3 =
column.split(':'),_column$split4 = _slicedToArray(_column$split3, 3),columnToSelect = _column$split4[0],jsonColumn = _column$split4[1],dataType = _column$split4[2];
if (jsonColumn) {
_jsonFields2.default.buildSelect(qb, Bookshelf.knex, columnToSelect, jsonColumn, dataType);
} else
{
qb.select([columnToSelect]);
}
}
});
});
}
});
}
};
/**
* Build a query based on the `filters` parameter.
* @param filterValues {object|array}
*/
internals.buildFilters = function (filterValues) {
if ((0, _lodash.isObjectLike)(filterValues) && !(0, _lodash.isEmpty)(filterValues)) {
// format the column names of the filters
//filterValues = this.format(filterValues);
// build the filter query
internals.model.query(internals.applyFilterFragment(filterValues));
}
};
/**
* Takes in filterValues (fragment of filter configuration) and returns function to create filter
* @param filterValues {object|array} fragment of filter params
*/
internals.applyFilterFragment = function (filterValues) {
return function (qb) {
(0, _lodash.forEach)(filterValues, function (value, key) {
// If the value is a filter type
if ((0, _lodash.isObjectLike)(value) && !(0, _lodash.isArray)(value)) {
// Format column names of filter types
var filterTypeValues = value;
// Check if filter type is valid
if ((0, _lodash.includes)(filterTypes, key)) {
// Loop through each value for the valid filter type
(0, _lodash.forEach)(filterTypeValues, function (typeValue, typeKey) {var _typeKey$split =
typeKey.split(':'),_typeKey$split2 = _slicedToArray(_typeKey$split, 3),column = _typeKey$split2[0],jsonColumn = _typeKey$split2[1],dataType = _typeKey$split2[2];
// Remove all but the last table name, need to get number of dots
column = internals.formatRelation(internals.formatColumnNames([column])[0]);
var valueArray = typeValue;
if (!(0, _lodash.isArray)(valueArray)) {
if (valueArray === null) {
valueArray = [valueArray];
} else
{
valueArray = (0, _splitString2.default)(String(valueArray), { keepQuotes: true, sep: ',' });
}
}
if (jsonColumn) {
// Pass in the an equality filter for the same column name as last parameter for OR filtering with `like` and `equals`
var extraEqualityFilter = filterValues[typeKey];
if (extraEqualityFilter) {
if (!(0, _lodash.isArray)(extraEqualityFilter)) {
extraEqualityFilter = (0, _splitString2.default)(String(extraEqualityFilter), { keepQuotes: true, sep: ',' });
}
}
var filterObj = {
nullString: options.nullString,
qb: qb,
knex: Bookshelf.knex,
filterType: key,
values: valueArray,
column: column,
jsonColumn: jsonColumn,
dataType: dataType,
extraEqualityFilterValues: extraEqualityFilter };
_jsonFields2.default.buildFilterWithType(filterObj);
} else
{
// Attach different query for each type
if (key === 'like') {
qb.where(function (qbWhere) {
var where = 'where';
var textType = 'text';
if (internals.client === 'mysql' || internals.client === 'mssql') {
textType = 'char';
}
(0, _lodash.forEach)(valueArray, function (val) {
var likeQuery = 'LOWER(CAST(:column: AS ' + textType + ')) like LOWER(:value)';
if (internals.client === 'pg') {
likeQuery = 'CAST(:column: AS ' + textType + ') ilike :value';
}
qbWhere[where](Bookshelf.knex.raw(likeQuery, {
value: '%' + val + '%',
column: column }));
// Change to orWhere after the first where
if (where === 'where') {
where = 'orWhere';
}
});
// If the key is in the top level filter, filter on orWhereIn
if ((0, _lodash.hasIn)(filterValues, typeKey)) {
// Determine if there are multiple filters to be applied
var equalityValue = filterValues[typeKey];
if (!(0, _lodash.isArray)(equalityValue)) {
equalityValue = (0, _splitString2.default)(String(equalityValue), { keepQuotes: true, sep: ',' });
}
internals.equalityFilter(qbWhere, column, equalityValue, 'orWhere');
}
});
} else
if (key === 'not') {
var hasNull = valueArray.length !== (0, _lodash.pull)(valueArray, null, options.nullString).length;
if (hasNull) {
qb.whereNotNull(column);
}
if (!(0, _lodash.isEmpty)(valueArray)) {
qb.whereNotIn(column, valueArray);
}
} else
if (key === 'lt') {
qb.where(column, '<', typeValue);
} else
if (key === 'gt') {
qb.where(column, '>', typeValue);
} else
if (key === 'lte') {
qb.where(column, '<=', typeValue);
} else
if (key === 'gte') {
qb.where(column, '>=', typeValue);
}
}
});
}
}
// If key is or and value is array
else if ((0, _lodash.isArray)(value) && key === 'or') {
qb.where(function (orStatements) {
(0, _lodash.forEach)(value, function (fragment) {
orStatements.orWhere(internals.applyFilterFragment(fragment));
});
});
}
// If key is and and value is array
else if ((0, _lodash.isArray)(value) && key === 'and') {
qb.where(function (andStatements) {
(0, _lodash.forEach)(value, function (fragment) {
andStatements.where(internals.applyFilterFragment(fragment));
});
});
}
// If the value is an equality filter
else {
// If the key is in the like filter, ignore the filter
if (!(0, _lodash.hasIn)(filterValues.like, key)) {var _key$split =
key.split(':'),_key$split2 = _slicedToArray(_key$split, 3),column = _key$split2[0],jsonColumn = _key$split2[1],dataType = _key$split2[2];
// Remove all but the last table name, need to get number of dots
column = internals.formatRelation(internals.formatColumnNames([column])[0]);
if (!(0, _lodash.isArray)(value)) {
if (value === null) {
value = [value];
} else
{
value = (0, _splitString2.default)(String(value), { keepQuotes: true, sep: ',' });
}
}
if (jsonColumn) {
var eqFilterObj = {
nullString: options.nullString,
qb: qb,
knex: Bookshelf.knex,
filterType: 'equal',
values: value,
column: column,
jsonColumn: jsonColumn,
dataType: dataType,
extraEqualityFilterValues: null };
_jsonFields2.default.buildFilterWithType(eqFilterObj);
} else
{
internals.equalityFilter(qb, column, value);
}
}
}
});
};
};
/**
* Takes in value, query builder, and column name for creating an equality filter
* @param value {array}
* @param qb {object} The query builder
* @param column
*/
internals.equalityFilter = function (qb, column, value) {var whereType = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'where';
var hasNull = value.length !== (0, _lodash.pull)(value, options.nullString, null).length;
if (hasNull) {
qb[whereType](function (qbWhere) {
qbWhere.whereNull(column);
if (!(0, _lodash.isEmpty)(value)) {
qbWhere.orWhereIn(column, value);
}
});
} else
{
qb[whereType + 'In'](column, value);
}
};
/**
* Takes in an attribute string like a.b.c.d and returns c.d, also if attribute
* looks like 'a', it will return tableName.a where tableName is the top layer table name
* @param attribute {string}
* @return {string}
*/
internals.formatRelation = function (attribute) {var _attribute$split =
attribute.split(':'),_attribute$split2 = _slicedToArray(_attribute$split, 3),column = _attribute$split2[0],jsonColumn = _attribute$split2[1],dataType = _attribute$split2[2];
if ((0, _lodash.includes)(column, '.')) {
var splitKey = column.split('.');
column = splitKey[splitKey.length - 2] + '.' + splitKey[splitKey.length - 1];
}
// Add table name to before column name if no relation to avoid ambiguous columns
else {
column = internals.modelName + '.' + column;
}
return (0, _lodash.filter)([column, jsonColumn, dataType]).join(':');
};
/**
* Takes an array from attributes and returns the only the columns and removes the table names
* @param attributes {array}
* @return {array}
*/
internals.getColumnNames = function (attributes) {
return (0, _lodash.map)(attributes, function (attribute) {
return attribute.substr(attribute.lastIndexOf('.') + 1);
});
};
/**
* Build a query based on the `include` parameter.
* @param includeValues {array}
*/
internals.buildIncludes = function (expandedIncludes, includesMap) {
if ((0, _lodash.isArray)(expandedIncludes) && !(0, _lodash.isEmpty)(expandedIncludes)) {
var relations = [];
var includeFunctions = (0, _lodash.last)(expandedIncludes);
if ((0, _lodash.isObject)(includeFunctions)) {
expandedIncludes = (0, _lodash.initial)(expandedIncludes, includeFunctions);
}
(0, _lodash.forEach)(expandedIncludes, function (relation) {
if ((0, _lodash.has)(fields, relation) || (0, _lodash.has)(includeFunctions, relation)) {
var fieldNames = internals.formatColumnNames((0, _lodash.get)(fields, relation));
var relationGetter = 'relations.' + relation.split('.').join('.relations.');
var relationObject = (0, _lodash.get)(includesMap, relationGetter);
relations.push(_defineProperty({},
relation, function (qb) {
if ((0, _lodash.has)(includeFunctions, relation)) {
includeFunctions[relation](qb);
}
// Fetch existing columns from query builder and combine them with fieldNames
var selectFromQB = (0, _lodash.filter)(qb._statements, { grouping: 'columns' });
if (selectFromQB.length || fieldNames.length) {
// Combine the required columns with the desired columns
var requiredRelationColumns = (0, _lodash.get)(relationObject, 'requiredColumns');
var columnsToSelect = (0, _lodash.uniq)(_lodash.union.apply(undefined, _toConsumableArray((0, _lodash.map)(selectFromQB, 'value')).concat([fieldNames, requiredRelationColumns])));
// Clear existing columns
qb.clearSelect();
// Put the table name in front of each column in cases where there are joins in the subquery
columnsToSelect = (0, _lodash.map)(columnsToSelect, function (fieldName) {
// If there is already an existing table name in the query, do not replace it
if ((0, _lodash.includes)(fieldName, '.')) {
return fieldName;
}
return relationObject.tableName + '.' + fieldName;
});
// Select the columns
if (columnsToSelect.length) {
qb.columns(columnsToSelect);
}
}
}));
} else
{
relations.push(relation);
}
});
// Assign the relations to the options passed to fetch/All
(0, _lodash.assign)(opts, { withRelated: relations });
}
};
/* *
* Used for generating columns to automatically include on a relationship
* @returns {Object} Object nested map of included relationships, where each relationship has all required columns for mapping relationships to their parents
*
* example:
* {
* model: personModel,
* tableName: 'person',
* requiredColumns: ['id'],
* relations: {
* pet: {
* model: petModel,
* tableName: 'pet',
* requiredColumns: ['id', 'pet_owner_id'], // id is required for the toy relationship
* relations: {
* toy: {
* model: toyModel,
* tableName: 'toy',
* requiredColumns: ['id', 'pet_id']
* }
* }
* }
* }
* }
*/
internals.setupIncludesMap = function (expandedIncludes) {
var includesMap = {
model: internals.model,
requiredColumns: [internals.model.idAttribute],
relations: {} };
// expandedIncludes is ordered by the lowest amount of '.' characters
// Each item in expandedIncludes can be assumed to already be in the includes map by the time the forEach iteration reaches that item
(0, _lodash.forEach)(expandedIncludes, function (includeValue) {
// Ignore the value if it's not a string, there is another string to represent the includes that are functions
if ((0, _lodash.isObject)(includeValue)) {
return;
}
var splitIncludeValue = includeValue.split('.');
// Create a getter/setter string
// Getter string for 'a.b.c' will look like 'relations.a.relations.b.relations.c
var getterString = 'relations.' + splitIncludeValue.join('.relations.');
// The parent model object, the one before this new relation
var parent = includesMap;
// If this relation is not on the first level, get the parent
if (splitIncludeValue.length > 1) {
parent = (0, _lodash.get)(includesMap, 'relations.' + (0, _lodash.initial)(splitIncludeValue).join('.relations.'));
}
// Get the model and relationship
var relationName = (0, _lodash.last)(splitIncludeValue);
var relatedData = parent.model[relationName]().relatedData;
var relationTableName = relatedData.target.prototype.tableName;
var relationModel = relatedData.target.forg