objection
Advanced tools
Comparing version 0.1.6 to 0.2.0
@@ -49,5 +49,4 @@ 'use strict'; | ||
var self = this; | ||
var queryBuilder = QueryBuilder.forClass(relation.relatedModelClass); | ||
return relation.find(queryBuilder, this.models).then(function (related) { | ||
return relation.find(relation.relatedModelClass.query(), this.models).then(function (related) { | ||
return self._fetchNextEager(relation, related, nextEager); | ||
@@ -54,0 +53,0 @@ }); |
@@ -189,3 +189,3 @@ 'use strict'; | ||
return QueryBuilder | ||
return ModelClass.QueryBuilder | ||
.forClass(ModelClass) | ||
@@ -340,3 +340,3 @@ .findImpl(function () { | ||
return QueryBuilder | ||
return ModelClass.QueryBuilder | ||
.forClass(ModelClass) | ||
@@ -550,2 +550,10 @@ .findImpl(function () { | ||
/** | ||
* QueryBuilder subclass to use. | ||
* | ||
* This constructor is used whenever a query builder is created. You can override this | ||
* to use your own `QueryBuilder` subclass. | ||
*/ | ||
Model.QueryBuilder = QueryBuilder; | ||
/** | ||
* one-to-many relation type. | ||
@@ -810,3 +818,3 @@ * | ||
return QueryBuilder | ||
return ModelClass.QueryBuilder | ||
.forClass(ModelClass) | ||
@@ -872,2 +880,19 @@ .insertImpl(function (models) { | ||
/** | ||
* Shortcut for `SomeModel.knex().raw()`. | ||
*/ | ||
Model.raw = function () { | ||
var knex = this.knex(); | ||
return knex.raw.apply(knex, arguments); | ||
}; | ||
/** | ||
* Shortcut for `SomeModel.knex().client.formatter()`. | ||
* | ||
* @return {Formatter} | ||
*/ | ||
Model.formatter = function () { | ||
return this.knex().client.formatter(); | ||
}; | ||
/** | ||
* Shortcut for `SomeModel.knex().table(SomeModel.tableName)`. | ||
@@ -999,3 +1024,3 @@ * | ||
if (!input) { | ||
return null; | ||
return []; | ||
} | ||
@@ -1190,2 +1215,3 @@ | ||
model.$id(ids[idx]); | ||
model.$afterInsert(); | ||
}); | ||
@@ -1198,12 +1224,2 @@ | ||
} | ||
}).runAfter(function (model) { | ||
if (_.isArray(model)) { | ||
_.each(model, function (model) { | ||
model.$afterInsert(); | ||
}); | ||
} else { | ||
model.$afterInsert(); | ||
} | ||
return model; | ||
}); | ||
@@ -1246,6 +1262,4 @@ }; | ||
return builder.update(update.$toDatabaseJson()).runAfterModelCreatePushFront(function () { | ||
$update.$afterUpdate(options); | ||
return $update; | ||
}).runAfter(function (model) { | ||
model.$afterUpdate(options); | ||
return model; | ||
}); | ||
@@ -1252,0 +1266,0 @@ }; |
@@ -23,4 +23,3 @@ 'use strict'; | ||
* The query is executed when one of its promise methods `then()`, `catch()`, `map()`, | ||
* `bind()` or `return()` is called. Also calling either of the paging methods `page()` | ||
* or `range()` will execute the query. | ||
* `bind()` or `return()` is called. | ||
* | ||
@@ -31,3 +30,4 @@ * @constructor | ||
this._modelClass = modelClass; | ||
this._knexCalls = []; | ||
this._queryMethodCalls = []; | ||
this._formatter = modelClass.formatter(); | ||
@@ -42,9 +42,11 @@ this._explicitResolveValue = null; | ||
this._findImpl = null; | ||
this._insertImpl = null; | ||
this._updateImpl = null; | ||
this._patchImpl = null; | ||
this._deleteImpl = null; | ||
this._relateImpl = null; | ||
this._unrelateImpl = null; | ||
this._customImpl = { | ||
find: null, | ||
insert: null, | ||
update: null, | ||
patch: null, | ||
delete: null, | ||
relate: null, | ||
unrelate: null | ||
}; | ||
@@ -56,2 +58,13 @@ this._eagerExpression = null; | ||
/** | ||
* Makes the given constructor a subclass of this class. | ||
* | ||
* @param {function=} subclassConstructor | ||
* @return {function} | ||
*/ | ||
QueryBuilder.extend = function (subclassConstructor) { | ||
utils.inherits(subclassConstructor, this); | ||
return subclassConstructor; | ||
}; | ||
/** | ||
* Create QueryBuilder for a Model subclass. | ||
@@ -318,3 +331,3 @@ * | ||
QueryBuilder.prototype.findImpl = function (findImpl) { | ||
this._findImpl = findImpl; | ||
this._customImpl.find = findImpl || null; | ||
return this; | ||
@@ -333,3 +346,3 @@ }; | ||
QueryBuilder.prototype.insertImpl = function (insertImpl) { | ||
this._insertImpl = insertImpl; | ||
this._customImpl.insert = insertImpl || null; | ||
return this; | ||
@@ -348,3 +361,3 @@ }; | ||
QueryBuilder.prototype.updateImpl = function (updateImpl) { | ||
this._updateImpl = updateImpl; | ||
this._customImpl.update = updateImpl || null; | ||
return this; | ||
@@ -363,3 +376,3 @@ }; | ||
QueryBuilder.prototype.patchImpl = function (patchImpl) { | ||
this._patchImpl = patchImpl; | ||
this._customImpl.patch = patchImpl || null; | ||
return this; | ||
@@ -379,3 +392,3 @@ }; | ||
QueryBuilder.prototype.deleteImpl = function (deleteImpl) { | ||
this._deleteImpl = deleteImpl; | ||
this._customImpl.delete = deleteImpl || null; | ||
return this; | ||
@@ -391,3 +404,3 @@ }; | ||
QueryBuilder.prototype.relateImpl = function (relateImpl) { | ||
this._relateImpl = relateImpl; | ||
this._customImpl.relate = relateImpl || null; | ||
return this; | ||
@@ -403,3 +416,3 @@ }; | ||
QueryBuilder.prototype.unrelateImpl = function (unrelateImpl) { | ||
this._unrelateImpl = unrelateImpl; | ||
this._customImpl.unrelate = unrelateImpl || null; | ||
return this; | ||
@@ -582,3 +595,3 @@ }; | ||
clone._knexCalls = this._knexCalls.slice(); | ||
clone._queryMethodCalls = this._queryMethodCalls.slice(); | ||
clone._explicitResolveValue = this._explicitResolveValue; | ||
@@ -590,9 +603,3 @@ clone._explicitRejectValue = this._explicitRejectValue; | ||
clone._runAfter = this._runAfter.slice(); | ||
clone._findImpl = this._findImpl; | ||
clone._insertImpl = this._insertImpl; | ||
clone._updateImpl = this._updateImpl; | ||
clone._patchImpl = this._patchImpl; | ||
clone._deleteImpl = this._deleteImpl; | ||
clone._relateImpl = this._relateImpl; | ||
clone._unrelateImpl = this._unrelateImpl; | ||
clone._customImpl = _.clone(this._customImpl); | ||
clone._eagerExpression = this._eagerExpression; | ||
@@ -608,9 +615,5 @@ clone._allowedEagerExpression = this._allowedEagerExpression; | ||
QueryBuilder.prototype.clearCustomImpl = function () { | ||
this._findImpl = null; | ||
this._insertImpl = null; | ||
this._updateImpl = null; | ||
this._patchImpl = null; | ||
this._deleteImpl = null; | ||
this._relateImpl = null; | ||
this._unrelateImpl = null; | ||
for (var key in this._customImpl) { | ||
this._customImpl[key] = null; | ||
} | ||
return this; | ||
@@ -625,7 +628,7 @@ }; | ||
var args = _.toArray(arguments); | ||
this._knexCalls = _.reject(this._knexCalls, function (call) { | ||
this._queryMethodCalls = _.reject(this._queryMethodCalls, function (call) { | ||
return _.contains(args, call.method); | ||
}); | ||
} else { | ||
this._knexCalls = []; | ||
this._queryMethodCalls = []; | ||
} | ||
@@ -640,3 +643,3 @@ | ||
QueryBuilder.prototype.has = function (methodName) { | ||
return !!_.find(this._knexCalls, {method: methodName}); | ||
return !!_.find(this._queryMethodCalls, {method: methodName}); | ||
}; | ||
@@ -701,3 +704,3 @@ | ||
/** | ||
* Returns the amount of rows the current query would produce. | ||
* Returns the amount of rows the current query would produce without `limit` and `offset` applied. | ||
* | ||
@@ -726,4 +729,7 @@ * Note that this executes a query (not the one we are building) and returns a Promise. Use it | ||
// orderBy is useless here and it can make things a lot slower (at least with postgresql 9.3). | ||
// Remove it from the count query. | ||
var query = this.clone().clear('orderBy').build(); | ||
// Remove it from the count query. We also remove the offset and limit | ||
var query = this | ||
.clone() | ||
.clear('orderBy', 'offset', 'limit') | ||
.build(); | ||
@@ -739,3 +745,3 @@ var rawQuery = knex.raw(query).wrap('(', ') as temp'); | ||
/** | ||
* Executes the query and returns a page of the results along with the total count. | ||
* Only returns the given page of results. | ||
* | ||
@@ -759,3 +765,3 @@ * ```js | ||
* | ||
* @returns {Promise} | ||
* @returns {QueryBuilder} | ||
*/ | ||
@@ -767,3 +773,3 @@ QueryBuilder.prototype.page = function (page, pageSize) { | ||
/** | ||
* Executes the query and returns a range of the results along with the total count. | ||
* Only returns the given range of results. | ||
* | ||
@@ -787,110 +793,37 @@ * ```js | ||
* | ||
* @returns {Promise} | ||
* @returns {QueryBuilder} | ||
*/ | ||
QueryBuilder.prototype.range = function (start, end) { | ||
return Promise.all([ | ||
this.resultSize(), | ||
this.limit(end - start + 1).offset(start) | ||
]).spread(function (total, results) { | ||
return { | ||
results: results, | ||
total: total | ||
}; | ||
}); | ||
}; | ||
var self = this; | ||
var resultSizePromise; | ||
/** | ||
* @protected | ||
*/ | ||
QueryBuilder.prototype.build = function () { | ||
return this.constructor.build(this.clone()); | ||
return this | ||
.limit(end - start + 1) | ||
.offset(start) | ||
.runBefore(function (result) { | ||
// Don't return the promise so that it is executed | ||
// in parallel with the actual query. | ||
resultSizePromise = self.resultSize(); | ||
}) | ||
.runAfter(function (results) { | ||
// Now that the actual query is finished, wait until the | ||
// result size has been calculated. | ||
return Promise.all([results, resultSizePromise]); | ||
}) | ||
.runAfter(function (arr) { | ||
return { | ||
results: arr[0], | ||
total: _.parseInt(arr[1]) | ||
}; | ||
}); | ||
}; | ||
/** | ||
* @protected | ||
* Builds the query into a knex query builder. | ||
* | ||
* @returns {knex.QueryBuilder} | ||
* The built knex query builder. | ||
*/ | ||
QueryBuilder.build = function (builder) { | ||
var isFindQuery = builder.isFindQuery(); | ||
var inserts = _.where(builder._knexCalls, {method: 'insert'}); | ||
var updates = _.where(builder._knexCalls, {method: 'update'}); | ||
var patches = _.where(builder._knexCalls, {method: 'patch'}); | ||
var deletes = _.where(builder._knexCalls, {method: 'delete'}); | ||
var relates = _.where(builder._knexCalls, {method: 'relate'}); | ||
var unrelates = _.where(builder._knexCalls, {method: 'unrelate'}); | ||
if (builder._insertImpl && inserts.length) { | ||
builder._knexCalls = _.reject(builder._knexCalls, {method: 'insert'}); | ||
} | ||
if (builder._updateImpl && updates.length) { | ||
builder._knexCalls = _.reject(builder._knexCalls, {method: 'update'}); | ||
} | ||
if (builder._patchImpl && patches.length) { | ||
builder._knexCalls = _.reject(builder._knexCalls, {method: 'patch'}); | ||
} | ||
if (builder._deleteImpl && deletes.length) { | ||
builder._knexCalls = _.reject(builder._knexCalls, {method: 'delete'}); | ||
} | ||
if (builder._relateImpl && relates.length) { | ||
builder._knexCalls = _.reject(builder._knexCalls, {method: 'relate'}); | ||
} | ||
if (builder._unrelateImpl && unrelates.length) { | ||
builder._knexCalls = _.reject(builder._knexCalls, {method: 'unrelate'}); | ||
} | ||
if (builder._insertImpl) { | ||
_.each(inserts, function (insert) { | ||
builder._insertImpl.apply(builder, insert.args); | ||
}); | ||
} | ||
if (builder._updateImpl) { | ||
_.each(updates, function (update) { | ||
builder._updateImpl.apply(builder, update.args); | ||
}); | ||
} | ||
if (builder._patchImpl) { | ||
_.each(patches, function (patch) { | ||
builder._patchImpl.apply(builder, patch.args); | ||
}); | ||
} | ||
if (builder._deleteImpl) { | ||
_.each(deletes, function (del) { | ||
builder._deleteImpl.apply(builder, del.args); | ||
}); | ||
} | ||
if (builder._relateImpl) { | ||
_.each(relates, function (relate) { | ||
builder._relateImpl.apply(builder, relate.args); | ||
}); | ||
} | ||
if (builder._unrelateImpl) { | ||
_.each(unrelates, function (unrelate) { | ||
builder._unrelateImpl.apply(builder, unrelate.args); | ||
}); | ||
} | ||
if (builder._findImpl && isFindQuery) { | ||
builder._findImpl.call(builder); | ||
} | ||
var knexBuilder = builder._modelClass.knexQuery(); | ||
_.each(builder._knexCalls, function (call) { | ||
if (_.isFunction(knexBuilder[call.method])) { | ||
knexBuilder[call.method].apply(knexBuilder, call.args); | ||
} | ||
}); | ||
return knexBuilder; | ||
QueryBuilder.prototype.build = function () { | ||
return build(this.clone()); | ||
}; | ||
@@ -919,3 +852,3 @@ | ||
promise = promise.then(function () { | ||
throw builder._explicitRejectValue; | ||
return Promise.reject(builder._explicitRejectValue); | ||
}); | ||
@@ -926,2 +859,4 @@ } | ||
// Either return the explicit resolve value or execute the knex builder. | ||
// (the knex builder is executed when the `then` method is invoked by | ||
// the promise chaining) | ||
return builder._explicitResolveValue || knexBuilder; | ||
@@ -1603,2 +1538,24 @@ }); | ||
/** | ||
* Compares a column reference to another | ||
* | ||
* ```js | ||
* builder.whereRef('Person.id', '=', 'Animal.ownerId'); | ||
* ``` | ||
*/ | ||
QueryBuilder.prototype.whereRef = function (lhs, op, rhs) { | ||
return this._whereRef('and', lhs, op, rhs); | ||
}; | ||
/** | ||
* Compares a column reference to another | ||
* | ||
* ```js | ||
* builder.orWhereRef('Person.id', '=', 'Animal.ownerId'); | ||
* ``` | ||
*/ | ||
QueryBuilder.prototype.orWhereRef = function (lhs, op, rhs) { | ||
return this._whereRef('or', lhs, op, rhs); | ||
}; | ||
/** | ||
* Json query APIs | ||
@@ -1960,2 +1917,22 @@ _*/ | ||
/** | ||
* @private | ||
*/ | ||
QueryBuilder.prototype._whereRef = function (bool, lhs, op, rhs) { | ||
var func = (bool === 'and') ? this.whereRaw : this.orWhereRaw; | ||
if (_.isUndefined(rhs)) { | ||
rhs = op; | ||
op = '='; | ||
} | ||
op = this._formatter.operator(op); | ||
if (!_.isString(lhs) || !_.isString(rhs) || !_.isString(op)) { | ||
throw new Error('whereRef: invalid operands or operator'); | ||
} | ||
return func.call(this, this._formatter.wrap(lhs) + ' ' + op + ' ' + this._formatter.wrap(rhs)); | ||
}; | ||
/** | ||
* @returns {Function} | ||
@@ -1977,2 +1954,5 @@ */ | ||
return this; | ||
} else if (arguments[i] instanceof QueryBuilder) { | ||
// Convert QueryBuilders into knex query builders. | ||
args[i] = arguments[i].build(); | ||
} else { | ||
@@ -1983,3 +1963,3 @@ args[i] = arguments[i]; | ||
this._knexCalls.push({ | ||
this._queryMethodCalls.push({ | ||
method: methodName, | ||
@@ -2025,3 +2005,3 @@ args: args | ||
try { | ||
return builder.constructor.build(builder); | ||
return build(builder); | ||
} catch (err) { | ||
@@ -2032,2 +2012,50 @@ builder.reject(err); | ||
function build(builder) { | ||
var customImpl = null; | ||
var customArgs = null; | ||
var isWriteQuery = false; | ||
var nonWriteCalls = []; | ||
_.each(builder._queryMethodCalls, function (call) { | ||
var isWriteMethod = _.has(builder._customImpl, call.method) && call.method !== 'find'; | ||
if (isWriteMethod) { | ||
var impl = builder._customImpl[call.method]; | ||
if (_.isFunction(impl)) { | ||
customImpl = impl; | ||
customArgs = call.args; | ||
} | ||
isWriteQuery = true; | ||
} else { | ||
nonWriteCalls.push(call); | ||
} | ||
}); | ||
if (isWriteQuery) { | ||
if (customImpl) { | ||
// We replace the `_queryMethodCalls` of the builder with the non-write calls so that the | ||
// custom implementation call is not called again for the knex builder later in | ||
// this function. | ||
builder._queryMethodCalls = nonWriteCalls; | ||
customImpl.apply(builder, customArgs); | ||
} | ||
} else { | ||
if (builder._customImpl.find) { | ||
builder._customImpl.find.call(builder); | ||
} | ||
} | ||
var knexBuilder = builder._modelClass.knexQuery(); | ||
_.each(builder._queryMethodCalls, function (call) { | ||
if (_.isFunction(knexBuilder[call.method])) { | ||
knexBuilder[call.method].apply(knexBuilder, call.args); | ||
} | ||
}); | ||
return knexBuilder; | ||
} | ||
/** | ||
@@ -2034,0 +2062,0 @@ * Field expression how to refer certain nested field inside jsonb column. |
@@ -18,2 +18,6 @@ 'use strict'; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
*/ | ||
ManyToManyRelation.prototype.setMapping = function (mapping) { | ||
@@ -29,2 +33,28 @@ var retVal = Relation.prototype.setMapping.call(this, mapping); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
ManyToManyRelation.prototype.findQuery = function (builder, ownerCol, isColumnRef) { | ||
builder.join(this.joinTable, this.fullJoinTableRelatedCol(), this.fullRelatedCol()); | ||
if (isColumnRef) { | ||
builder.whereRef(this.fullJoinTableOwnerCol(), ownerCol); | ||
} else { | ||
if (_.isArray(ownerCol)) { | ||
builder.whereIn(this.fullJoinTableOwnerCol(), ownerCol); | ||
} else { | ||
builder.where(this.fullJoinTableOwnerCol(), ownerCol); | ||
} | ||
} | ||
return builder.call(this.filter); | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
ManyToManyRelation.prototype.find = function (builder, $owners) { | ||
@@ -39,3 +69,3 @@ var self = this; | ||
// Add the statements that select the owners' rows. | ||
this._makeFindQuery(builder, _.unique(_.pluck(owners, this.ownerProp))); | ||
this.findQuery(builder, _.unique(_.pluck(owners, this.ownerProp))); | ||
@@ -45,3 +75,3 @@ // Select the joined identifier of the owner model. | ||
return builder.runAfterModelCreatePushFront(function (related) { | ||
return builder.runAfterModelCreate(function (related) { | ||
var relatedByOwnerId = _.groupBy(related, ownerJoinColumnAlias); | ||
@@ -61,2 +91,27 @@ | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
ManyToManyRelation.prototype.join = function (builder, joinMethod) { | ||
joinMethod = joinMethod || 'join'; | ||
var joinTable = this.joinTable; | ||
var relatedTable = this.relatedModelClass.tableName; | ||
var joinTableAlias = this.joinTableAlias(); | ||
var relatedTableAlias = this.relatedTableAlias(); | ||
return builder | ||
[joinMethod](joinTable + ' as ' + joinTableAlias, joinTableAlias + '.' + this.joinTableOwnerCol, this.fullOwnerCol()) | ||
[joinMethod](relatedTable + ' as ' + relatedTableAlias, joinTableAlias + '.' + this.joinTableRelatedCol, relatedTableAlias + '.' + this.relatedCol) | ||
.call(this.filter); | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
ManyToManyRelation.prototype.insert = function (builder, $owner, $insertion) { | ||
@@ -84,2 +139,7 @@ var self = this; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
ManyToManyRelation.prototype.update = function (builder, $owner, $update) { | ||
@@ -97,2 +157,7 @@ var owner = this.ownerModelClass.ensureModel($owner); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
ManyToManyRelation.prototype.patch = function (builder, $owner, $patch) { | ||
@@ -110,2 +175,7 @@ var owner = this.ownerModelClass.ensureModel($owner); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
ManyToManyRelation.prototype.delete = function (builder, $owner) { | ||
@@ -123,2 +193,7 @@ var owner = this.ownerModelClass.ensureModel($owner); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
ManyToManyRelation.prototype.relate = function (builder, $owner, $ids) { | ||
@@ -130,3 +205,3 @@ var owner = this.ownerModelClass.ensureModel($owner); | ||
// Insert join rows into the join table. | ||
return builder.insert(joinRows).into(this.joinTable).returning('id').runAfterModelCreatePushFront(function (ids) { | ||
return builder.insert(joinRows).into(this.joinTable).returning('id').runAfterModelCreate(function (ids) { | ||
_.each(joinRows, function (row, idx) { | ||
@@ -139,2 +214,7 @@ row.id = ids[idx] || null; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
ManyToManyRelation.prototype.unrelate = function (builder, $owner) { | ||
@@ -158,12 +238,5 @@ var self = this; | ||
.whereIn(self.fullJoinTableRelatedCol(), idSelectQuery.build()) | ||
.runAfterModelCreatePushFront(_.constant({})); | ||
.runAfterModelCreate(_.constant({})); | ||
}; | ||
ManyToManyRelation.prototype._makeFindQuery = function (builder, ownerIds) { | ||
return builder | ||
.join(this.joinTable, this.fullJoinTableRelatedCol(), this.fullRelatedCol()) | ||
.whereIn(this.fullJoinTableOwnerCol(), ownerIds) | ||
.call(this.filter); | ||
}; | ||
ManyToManyRelation.prototype._makeFindIdQuery = function (ownerId) { | ||
@@ -177,9 +250,8 @@ return this.ownerModelClass | ||
/** | ||
* @private | ||
*/ | ||
ManyToManyRelation.prototype._createJoinRows = function (ownerId, relatedIds) { | ||
var self = this; | ||
if (!_.isArray(relatedIds)) { | ||
relatedIds = [relatedIds]; | ||
} | ||
return _.map(relatedIds, function (relatedId) { | ||
@@ -186,0 +258,0 @@ var joinRow = {}; |
@@ -17,2 +17,26 @@ 'use strict'; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToManyRelation.prototype.findQuery = function (builder, ownerCol, isColumnRef) { | ||
if (isColumnRef) { | ||
builder.whereRef(this.fullRelatedCol(), ownerCol); | ||
} else { | ||
if (_.isArray(ownerCol)) { | ||
builder.whereIn(this.fullRelatedCol(), ownerCol); | ||
} else { | ||
builder.where(this.fullRelatedCol(), ownerCol); | ||
} | ||
} | ||
return builder.call(this.filter); | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToManyRelation.prototype.find = function (builder, $owners) { | ||
@@ -23,3 +47,3 @@ var self = this; | ||
return this._makeFindQuery(builder, ownerIds).runAfterModelCreatePushFront(function (related) { | ||
return this.findQuery(builder, ownerIds).runAfterModelCreate(function (related) { | ||
var relatedByOwnerId = _.groupBy(related, self.relatedProp); | ||
@@ -35,2 +59,23 @@ | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToManyRelation.prototype.join = function (builder, joinMethod) { | ||
joinMethod = joinMethod || 'join'; | ||
var relatedTable = this.relatedModelClass.tableName; | ||
var relatedTableAlias = this.relatedTableAlias(); | ||
return builder | ||
[joinMethod](relatedTable + ' as ' + relatedTableAlias, relatedTableAlias + '.' + this.relatedCol, this.fullOwnerCol()) | ||
.call(this.filter); | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToManyRelation.prototype.insert = function (builder, $owner, $insertion) { | ||
@@ -51,6 +96,11 @@ var self = this; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToManyRelation.prototype.update = function (builder, $owner, $update) { | ||
var owner = this.ownerModelClass.ensureModel($owner); | ||
this._makeFindQuery(builder, [owner[this.ownerProp]]); | ||
this.findQuery(builder, [owner[this.ownerProp]]); | ||
this.relatedModelClass.$$update(builder, $update); | ||
@@ -61,6 +111,11 @@ | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToManyRelation.prototype.patch = function (builder, $owner, $patch) { | ||
var owner = this.ownerModelClass.ensureModel($owner); | ||
this._makeFindQuery(builder, [owner[this.ownerProp]]); | ||
this.findQuery(builder, [owner[this.ownerProp]]); | ||
this.relatedModelClass.$$patch(builder, $patch); | ||
@@ -71,6 +126,11 @@ | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToManyRelation.prototype.delete = function (builder, $owner) { | ||
var owner = this.ownerModelClass.ensureModel($owner); | ||
this._makeFindQuery(builder, [owner[this.ownerProp]]); | ||
this.findQuery(builder, [owner[this.ownerProp]]); | ||
this.relatedModelClass.$$delete(builder); | ||
@@ -81,2 +141,7 @@ | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToManyRelation.prototype.relate = function (builder, $owner, $ids) { | ||
@@ -91,3 +156,3 @@ var owner = this.ownerModelClass.ensureModel($owner); | ||
.whereIn(this.relatedModelClass.getFullIdColumn(), _.flatten([$ids])) | ||
.runAfter(function () { | ||
.runAfterModelCreate(function () { | ||
return $ids; | ||
@@ -97,2 +162,7 @@ }); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToManyRelation.prototype.unrelate = function (builder, $owner) { | ||
@@ -108,3 +178,3 @@ var owner = this.ownerModelClass.ensureModel($owner); | ||
.call(this.filter) | ||
.runAfter(function () { | ||
.runAfterModelCreate(function () { | ||
return {}; | ||
@@ -114,6 +184,2 @@ }); | ||
OneToManyRelation.prototype._makeFindQuery = function (builder, ownerIds) { | ||
return builder.whereIn(this.fullRelatedCol(), _.compact(ownerIds)).call(this.filter); | ||
}; | ||
module.exports = OneToManyRelation; |
@@ -17,2 +17,26 @@ 'use strict'; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToOneRelation.prototype.findQuery = function (builder, ownerCol, isColumnRef) { | ||
if (isColumnRef) { | ||
builder.whereRef(this.fullRelatedCol(), ownerCol); | ||
} else { | ||
if (_.isArray(ownerCol)) { | ||
builder.whereIn(this.fullRelatedCol(), ownerCol); | ||
} else { | ||
builder.where(this.fullRelatedCol(), ownerCol) | ||
} | ||
} | ||
return builder.call(this.filter); | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToOneRelation.prototype.find = function (builder, $owners) { | ||
@@ -34,2 +58,23 @@ var self = this; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToOneRelation.prototype.join = function (builder, joinMethod) { | ||
joinMethod = joinMethod || 'join'; | ||
var relatedTable = this.relatedModelClass.tableName; | ||
var relatedTableAlias = this.relatedTableAlias(); | ||
return builder | ||
[joinMethod](relatedTable + ' as ' + relatedTableAlias, relatedTableAlias + '.' + this.relatedCol, this.fullOwnerCol()) | ||
.call(this.filter); | ||
}; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToOneRelation.prototype.insert = function (builder, $owner, $insertion) { | ||
@@ -62,2 +107,7 @@ var self = this; | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToOneRelation.prototype.update = function (builder, $owner, $update) { | ||
@@ -72,2 +122,7 @@ var owner = this.ownerModelClass.ensureModel($owner); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToOneRelation.prototype.patch = function (builder, $owner, $patch) { | ||
@@ -82,2 +137,7 @@ var owner = this.ownerModelClass.ensureModel($owner); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToOneRelation.prototype.delete = function (builder, $owner) { | ||
@@ -92,2 +152,7 @@ var owner = this.ownerModelClass.ensureModel($owner); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToOneRelation.prototype.relate = function (builder, $owner, $ids) { | ||
@@ -109,3 +174,3 @@ var owner = this.ownerModelClass.ensureModel($owner); | ||
.where(this.ownerModelClass.getFullIdColumn(), owner.$id()) | ||
.runAfter(function () { | ||
.runAfterModelCreate(function () { | ||
return $ids; | ||
@@ -115,2 +180,7 @@ }); | ||
/** | ||
* @override | ||
* @inheritDoc | ||
* @returns {QueryBuilder} | ||
*/ | ||
OneToOneRelation.prototype.unrelate = function (builder, $owner) { | ||
@@ -127,3 +197,3 @@ var owner = this.ownerModelClass.ensureModel($owner); | ||
.where(this.ownerModelClass.getFullIdColumn(), owner.$id()) | ||
.runAfter(function () { | ||
.runAfterModelCreate(function () { | ||
return {}; | ||
@@ -133,2 +203,5 @@ }); | ||
/** | ||
* @private | ||
*/ | ||
OneToOneRelation.prototype._makeFindQuery = function (builder, relatedIds) { | ||
@@ -140,3 +213,3 @@ relatedIds = _.compact(relatedIds); | ||
} else { | ||
return builder.whereIn(this.fullRelatedCol(), relatedIds).call(this.filter); | ||
return this.findQuery(builder, relatedIds); | ||
} | ||
@@ -143,0 +216,0 @@ }; |
@@ -10,7 +10,44 @@ 'use strict'; | ||
* | ||
* An object literal that describes how two tables are related to one another. For example: | ||
* | ||
* ```js | ||
* { | ||
* from: 'Animal.ownerId', | ||
* to: 'Person.id' | ||
* } | ||
* ``` | ||
* | ||
* or in the case of a many-to-many relation: | ||
* | ||
* ```js | ||
* { | ||
* from: 'Person.id', | ||
* through: { | ||
* from: 'Person_Movie.actorId', | ||
* to: 'Person_Movie.movieId' | ||
* }, | ||
* to: 'Movie.id' | ||
* } | ||
* ``` | ||
* | ||
* @property {String} from | ||
* The relation column in the owner table. Must be given with the table name. | ||
* For example `Person.id`. Note that neither this nor `to` need to be foreign | ||
* keys or primary keys. You can join any column to any column. | ||
* | ||
* @property {String} to | ||
* The relation column in the related table. Must be given with the table name. | ||
* For example `Movie.id`. Note that neither this nor `from` need to be foreign | ||
* keys or primary keys. You can join any column to any column. | ||
* | ||
* @property {Object} through | ||
* Describes the join table if the models are related through one. | ||
* | ||
* @property {String} through.from | ||
* The column that is joined to `from` property of the `RelationJoin`. For example | ||
* `Person_Movie.actorId` where `Person_Movie` is the join table. | ||
* | ||
* @property {String} through.to | ||
* The column that is joined to `to` property of the `RelationJoin`. For example | ||
* `Person_Movie.movieId` where `Person_Movie` is the join table. | ||
*/ | ||
@@ -39,2 +76,4 @@ | ||
* | ||
* This is an abstract base class and should never be instantiated. | ||
* | ||
* @param {String} relationName | ||
@@ -47,2 +86,3 @@ * Name of the relation. | ||
* @ignore | ||
* @abstract | ||
* @constructor | ||
@@ -144,4 +184,10 @@ */ | ||
/** | ||
* Constructs the instance based on a mapping data. | ||
* | ||
* @param {RelationMapping} mapping | ||
*/ | ||
Relation.prototype.setMapping = function (mapping) { | ||
var Model = require('../Model'); | ||
// Avoid require loop and import here. | ||
var Model = require(__dirname + '/../Model'); | ||
@@ -254,2 +300,9 @@ if (!utils.isSubclassOf(this.ownerModelClass, Model)) { | ||
/** | ||
* Reference to the relation column in the owner model's table. | ||
* | ||
* For example: `Person.id`. | ||
* | ||
* @returns {string} | ||
*/ | ||
Relation.prototype.fullOwnerCol = function () { | ||
@@ -259,2 +312,9 @@ return this.ownerModelClass.tableName + '.' + this.ownerCol; | ||
/** | ||
* Reference to the relation column in the related model's table. | ||
* | ||
* For example: `Movie.id`. | ||
* | ||
* @returns {string} | ||
*/ | ||
Relation.prototype.fullRelatedCol = function () { | ||
@@ -264,2 +324,9 @@ return this.relatedModelClass.tableName + '.' + this.relatedCol; | ||
/** | ||
* Reference to the column in the join table that is joined with `fullOwnerCol()`. | ||
* | ||
* For example: `Person_Movie.actorId`. | ||
* | ||
* @returns {string} | ||
*/ | ||
Relation.prototype.fullJoinTableOwnerCol = function () { | ||
@@ -269,2 +336,9 @@ return this.joinTable + '.' + this.joinTableOwnerCol; | ||
/** | ||
* Reference to the column in the join table that is joined with `fullRelatedCol()`. | ||
* | ||
* For example: `Person_Movie.movieId`. | ||
* | ||
* @returns {string} | ||
*/ | ||
Relation.prototype.fullJoinTableRelatedCol = function () { | ||
@@ -274,2 +348,29 @@ return this.joinTable + '.' + this.joinTableRelatedCol; | ||
/** | ||
* Alias to use for the related table when joining with the owner table. | ||
* | ||
* For example: `Movie_rel_movies`. | ||
* | ||
* @returns {string} | ||
*/ | ||
Relation.prototype.relatedTableAlias = function () { | ||
return this.relatedModelClass.tableName + '_rel_' + this.name; | ||
}; | ||
/** | ||
* Alias to use for the join table when joining with the owner table. | ||
* | ||
* For example: `Person_Movie_rel_movies`. | ||
* | ||
* @returns {string} | ||
*/ | ||
Relation.prototype.joinTableAlias = function () { | ||
return this.joinTable + '_rel_' + this.name; | ||
}; | ||
/** | ||
* Clones this relation. | ||
* | ||
* @returns {Relation} | ||
*/ | ||
Relation.prototype.clone = function () { | ||
@@ -291,2 +392,10 @@ var clone = new this.constructor(this.name, this.ownerModelClass); | ||
/** | ||
* Returns a clone of this relation with `relatedModelClass` and `ownerModelClass` bound to the given knex. | ||
* | ||
* See `Model.bindKnex`. | ||
* | ||
* @param knex | ||
* @returns {Relation} | ||
*/ | ||
Relation.prototype.bindKnex = function (knex) { | ||
@@ -301,10 +410,55 @@ var bound = this.clone(); | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {number|string} ownerCol | ||
* @param {boolean} isColumnRef | ||
* @returns {QueryBuilder} | ||
*/ | ||
Relation.prototype.findQuery = function (builder, ownerCol, isColumnRef) { | ||
throw new Error('not implemented'); | ||
}; | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object|Array.<Model>|Array.<Object>} $owners | ||
* @returns {QueryBuilder} | ||
*/ | ||
Relation.prototype.find = function (builder, $owners) { | ||
return builder; | ||
throw new Error('not implemented'); | ||
}; | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {string} joinMethod | ||
* @returns {QueryBuilder} | ||
*/ | ||
Relation.prototype.join = function (builder, joinMethod) { | ||
throw new Error('not implemented'); | ||
}; | ||
/* istanbul ignore next */ | ||
/** | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} $owner | ||
* @param {Model|Object|Array.<Model>|Array.<Object>} $insertion | ||
* @returns {QueryBuilder} | ||
*/ | ||
Relation.prototype.insert = function (builder, $owner, $insertion) { | ||
return builder; | ||
throw new Error('not implemented'); | ||
}; | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} $owner | ||
* @param {Model|Object} $update | ||
* @returns {QueryBuilder} | ||
*/ | ||
Relation.prototype.update = function (builder, $owner, $update) { | ||
@@ -314,18 +468,51 @@ return builder; | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} $owner | ||
* @param {Model|Object} $patch | ||
* @returns {QueryBuilder} | ||
*/ | ||
Relation.prototype.patch = function (builder, $owner, $patch) { | ||
return builder; | ||
throw new Error('not implemented'); | ||
}; | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} $owner | ||
* @returns {QueryBuilder} | ||
*/ | ||
Relation.prototype.delete = function (builder, $owner) { | ||
return builder; | ||
throw new Error('not implemented'); | ||
}; | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} $owner | ||
* @param {number|string|Array.<number>|Array.<string>} ids | ||
* @returns {QueryBuilder} | ||
*/ | ||
Relation.prototype.relate = function (builder, $owner, ids) { | ||
return builder; | ||
throw new Error('not implemented'); | ||
}; | ||
/* istanbul ignore next */ | ||
/** | ||
* @abstract | ||
* @param {QueryBuilder} builder | ||
* @param {Model|Object} $owner | ||
* @returns {QueryBuilder} | ||
*/ | ||
Relation.prototype.unrelate = function (builder, $owner) { | ||
return builder; | ||
throw new Error('not implemented'); | ||
}; | ||
/** | ||
* @private | ||
*/ | ||
Relation.prototype._propertyName = function (column, modelClass) { | ||
@@ -344,2 +531,5 @@ var propertyName = modelClass.columnNameToPropertyName(column.name); | ||
/** | ||
* @private | ||
*/ | ||
function parseFilter(mapping) { | ||
@@ -357,2 +547,5 @@ if (_.isFunction(mapping.filter)) { | ||
/** | ||
* @private | ||
*/ | ||
function parseColumn(column) { | ||
@@ -359,0 +552,0 @@ var parts = column.split('.'); |
{ | ||
"name": "objection", | ||
"version": "0.1.6", | ||
"version": "0.2.0", | ||
"description": "An SQL-friendly ORM for Node.js", | ||
@@ -9,3 +9,3 @@ "main": "objection.js", | ||
"test": "istanbul --config=.istanbul.yml cover _mocha -- --slow 10 --timeout 5000 --reporter spec --recursive tests", | ||
"just-tests": "mocha --slow 10 --timeout 5000 --reporter spec --recursive tests", | ||
"test-only": "mocha --slow 10 --timeout 5000 --reporter spec --recursive tests", | ||
"coveralls": "cat ./test-coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" | ||
@@ -12,0 +12,0 @@ }, |
196
README.md
@@ -6,3 +6,3 @@ [![Build Status](https://travis-ci.org/Vincit/objection.js.svg?branch=master)](https://travis-ci.org/Vincit/objection.js) [![Coverage Status](https://coveralls.io/repos/Vincit/objection.js/badge.svg)](https://coveralls.io/r/Vincit/objection.js) | ||
Objection.js is a Node.js ORM built around the wonderful SQL query builder [knex](http://knexjs.org). All databases | ||
supported by knex are supported by objection.js. **SQLite3**, **Postgres** and **MySQL** are [fully tested](https://travis-ci.org/Vincit/objection.js). | ||
supported by knex are supported by objection.js. **SQLite3**, **Postgres** and **MySQL** are [thoroughly tested](https://travis-ci.org/Vincit/objection.js). | ||
@@ -16,8 +16,8 @@ What objection.js gives you: | ||
* Completely [Promise](https://github.com/petkaantonov/bluebird) based API | ||
* Simple [transactions](#transactions) | ||
* Easy to use [transactions](#transactions) | ||
* [JSON schema](#validation) validation | ||
What objection.js doesn't give you: | ||
What objection.js **doesn't** give you: | ||
* A custom query DSL. SQL is used everywhere | ||
* A custom query DSL. SQL is used as a query language. | ||
* Automatic database schema creation and migration. | ||
@@ -28,4 +28,5 @@ It is useful for the simple things, but usually just gets in your way when doing anything non-trivial. | ||
Objection.js uses Promises and coding practices that make it ready for future. You can already use things like ES7 [async/await](http://jakearchibald.com/2014/es7-async-functions/) | ||
and ES6 classes using a transpiler such as [Babel](https://babeljs.io/). Check out our [ES7 example project](https://github.com/Vincit/objection.js/tree/master/examples/express-es7). | ||
Objection.js uses Promises and coding practices that make it ready for the future. You can already use things like ES7 | ||
[async/await](http://jakearchibald.com/2014/es7-async-functions/) and ES6 classes using a transpiler such as | ||
[Babel](https://babeljs.io/). Check out our [ES7 example project](https://github.com/Vincit/objection.js/tree/master/examples/express-es7). | ||
@@ -45,2 +46,3 @@ # Topics | ||
- [Recipe book](RECIPES.md) | ||
- [Changelog](#changelog) | ||
@@ -50,9 +52,38 @@ # Installation | ||
```sh | ||
npm install objection | ||
npm install knex objection | ||
``` | ||
You also need to install one of the following depending on the database you want to use: | ||
```sh | ||
npm install pg | ||
npm install sqlite3 | ||
npm install mysql | ||
npm install mysql2 | ||
npm install mariasql | ||
``` | ||
# Getting started | ||
Best way to get started is to use one of the example projects: | ||
To use objection.js all you need to do is [initialize knex](http://knexjs.org/#Installation-node) and give the | ||
connection to objection.js using `Model.knex(knex)`: | ||
```js | ||
var Knex = require('knex'); | ||
var Model = require('objection').Model; | ||
var knex = Knex({ | ||
client: 'postgres', | ||
connection: { | ||
host: '127.0.0.1', | ||
database: 'your_database' | ||
} | ||
}); | ||
Model.knex(knex); | ||
``` | ||
The next step is to create some migrations and models and start using objection.js. The best way to get started is to | ||
use the [example project](https://github.com/Vincit/objection.js/tree/master/examples/express): | ||
```sh | ||
@@ -93,3 +124,3 @@ git clone git@github.com:Vincit/objection.js.git objection | ||
The Person model used in the examples is defined [here](#models). | ||
The `Person` model used in the examples is defined [here](#models). | ||
@@ -101,3 +132,3 @@ All queries are started with one of the [Model](http://vincit.github.io/objection.js/Model.html) methods [query()](http://vincit.github.io/objection.js/Model.html#_P_query), | ||
Insert a Person model to the database: | ||
Insert a person to the database: | ||
@@ -118,4 +149,8 @@ ```js | ||
Fetch all Persons from the database: | ||
```sql | ||
insert into "Person" ("firstName", "lastName") values ('Jennifer', 'Lawrence') | ||
``` | ||
Fetch all persons from the database: | ||
```js | ||
@@ -133,2 +168,6 @@ Person | ||
```sql | ||
select * from "Person" | ||
``` | ||
The return value of the `.query()` method is an instance of [QueryBuilder](http://vincit.github.io/objection.js/QueryBuilder.html) | ||
@@ -150,2 +189,34 @@ that has all the methods a [knex QueryBuilder](http://knexjs.org/#Builder) has. Here is a simple example that uses some of them: | ||
```sql | ||
select * from "Person" | ||
where "age" > 40 | ||
and "age" < 60 | ||
and "firstName" = 'Jennifer' | ||
order by "lastName" asc | ||
``` | ||
The next example shows how easy it is to build complex queries: | ||
```js | ||
Person | ||
.query() | ||
.select('Person.*', 'Parent.firstName as parentFirstName') | ||
.join('Person as Parent', 'Person.parentId', 'Parent.id') | ||
.where('Person.age', '<', Person.query().avg('Person.age')) | ||
.whereExists(Animal.query().select(1).whereRef('Person.id', 'Animal.ownerId')) | ||
.orderBy('Person.lastName') | ||
.then(function (persons) { | ||
console.log(persons[0].parentFirstName); | ||
}); | ||
``` | ||
```sql | ||
select "Person".*, "Parent"."firstName" as "parentFirstName" | ||
from "Person" | ||
inner join "Person" as "Parent" on "Person"."parentId" = "Parent"."id" | ||
where "Person"."age" < (select avg("Person"."age") from "Person") | ||
and exists (select 1 from "Animal" where "Person"."id" = "Animal"."ownerId") | ||
order by "Person"."lastName" asc | ||
``` | ||
Update models: | ||
@@ -156,3 +227,3 @@ | ||
.query() | ||
.patch({lastName: 'Dinosaur'}); | ||
.patch({lastName: 'Dinosaur'}) | ||
.where('age', '>', 60) | ||
@@ -168,2 +239,6 @@ .then(function (patch) { | ||
```sql | ||
update "Person" set "lastName" = 'Dinosaur' where "age" > 60 | ||
``` | ||
While the static `.query()` method can be used to create a query to a whole table `.$relatedQuery()` method | ||
@@ -196,2 +271,11 @@ can be used to query a single relation. `.$relatedQuery()` returns an instance of [QueryBuilder](http://vincit.github.io/objection.js/QueryBuilder.html) | ||
```sql | ||
select * from "Person" where "firstName" = 'Jennifer' | ||
select * from "Animal" | ||
where "species" = 'dog' | ||
and "Animal"."ownerId" = 1 | ||
order by "name" asc | ||
``` | ||
Insert a related model: | ||
@@ -202,3 +286,3 @@ | ||
.query() | ||
.where('id', 100) | ||
.where('id', 1) | ||
.first() | ||
@@ -209,3 +293,3 @@ .then(function (person) { | ||
.then(function (fluffy) { | ||
console.log(fully.id); | ||
console.log(fluffy.id); | ||
}) | ||
@@ -218,6 +302,12 @@ .catch(function (err) { | ||
```sql | ||
select * from "Person" where "id" = 1 | ||
insert into "Animal" ("name", "ownerId") values ('Fluffy', 1) | ||
``` | ||
# Eager queries | ||
Okay I said there is no custom DSL but actually we have teeny-tiny one for fetching relations eagerly. The following | ||
examples demonstrate how to use it: | ||
Okay I said there is no custom DSL but actually we have teeny-tiny one for fetching relations eagerly, as it isn't | ||
something that can be done easily using SQL. The following examples demonstrate how to use it: | ||
@@ -287,5 +377,17 @@ Fetch one relation: | ||
The example above allows `req.query.eager` to be one of `'pets'`, `'children'`, `'children.pets'`, `'[pets, children]'` and | ||
`'[pets, children.pets]'`. Examples of failing eager expressions are `'movies'`, `'children.children'` and `'notEvenAnExistingRelation'`. | ||
The example above allows `req.query.eager` to be one of: | ||
* `'pets'` | ||
* `'children'` | ||
* `'children.pets'` | ||
* `'[pets, children]'` | ||
* `'[pets, children.pets]'`. | ||
Examples of failing eager expressions are: | ||
* `'movies'` | ||
* `'children.children'` | ||
* `'[pets, children.children]'` | ||
* `'notEvenAnExistingRelation'`. | ||
In addition to the `.eager` method, relations can be fetched using the `loadRelated` and `$loadRelated` methods of | ||
@@ -442,5 +544,30 @@ [Model](http://vincit.github.io/objection.js/Model.html). | ||
## Minimal model | ||
A working model with minimal amount of code: | ||
```js | ||
var Model = require('objection').Model; | ||
function MinimalModel() { | ||
Model.apply(this, arguments); | ||
} | ||
// Inherit `Model`. This gives your model all those methods like `MinimalModel.query()` | ||
// and `MinimalModel.fromJson()`. | ||
Model.extend(MinimalModel); | ||
// After the js class boilerplate, all you need to do is set the table name. | ||
MinimalModel.tableName = 'SomeTableName'; | ||
module.exports = MinimalModel; | ||
``` | ||
## A model with custom methods, json schema validation and relations | ||
This is the model used in the examples: | ||
```js | ||
var Model = require('objection').Model; | ||
function Person() { | ||
@@ -515,2 +642,11 @@ Model.apply(this, arguments); | ||
parent: { | ||
relation: Model.OneToOneRelation, | ||
modelClass: Person, | ||
join: { | ||
from: 'Person.parentId', | ||
to: 'Person.id' | ||
} | ||
}, | ||
children: { | ||
@@ -534,1 +670,25 @@ relation: Model.OneToManyRelation, | ||
# Changelog | ||
## 0.2.0 | ||
### New features | ||
* New name `objection.js`. | ||
* `$beforeInsert`, `$afterInsert`, `$beforeUpdate` and `$afterUpdate` hooks for `Model`. | ||
* Postgres jsonb query methods: `whereJsonEquals`, `whereJsonSupersetOf`, `whereJsonSubsetOf` and friends. | ||
* `whereRef` query method. | ||
* Expose `knex.raw()` through `Model.raw()`. | ||
* Expose `knex.client.formatter()` through `Model.formatter()`. | ||
* `QueryBuilder` can be used to make sub queries just like knex's `QueryBuilder`. | ||
* Possibility to use a custom `QueryBuilder` subclass by overriding `Model.QueryBuilder`. | ||
* Filter queries/objects for relations. | ||
* A pile of bug fixes. | ||
### Breaking changes | ||
* Project was renamed to objection.js. Migrate simply by replacing `moron` with `objection`. | ||
## 0.1.0 | ||
First release. |
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
204816
5968
675
2