Skip to content

Commit

Permalink
refactor(relations): refactor relations code
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Mar 7, 2016
1 parent c4d8812 commit 6892543
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 27 deletions.
10 changes: 7 additions & 3 deletions src/Lucid/Model/Mixins/Dates.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ const moment = require('moment')
* sets create timestamp on an object of values only
* if create timestamps are enabled by a given
* model.
*
* @method setCreateTimestamp
* @param {Obhect} values
*
* @param {Object} values
* @return {Object}
*
* @public
*/
Dates.setCreateTimestamp = function (values) {
if (this.constructor.createTimestamp) {
Expand All @@ -34,7 +38,7 @@ Dates.setCreateTimestamp = function (values) {
*
* @method setUpdateTimestamp
*
* @param {Obhect} values
* @param {Object} values
* @return {Object}
*
* @public
Expand All @@ -53,7 +57,7 @@ Dates.setUpdateTimestamp = function (values) {
*
* @method setDeleteTimestamp
*
* @param {Obhect} values
* @param {Object} values
* @return {Object}
*
* @public
Expand Down
2 changes: 1 addition & 1 deletion src/Lucid/Model/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ class Model {
*/
get $dirty () {
return _.pickBy(this.attributes, (value, key) => {
return typeof (this.original[key]) === 'undefined' || this.original[key] !== value
return (typeof (this.original[key]) === 'undefined' || this.original[key] !== value) && !key.startsWith('_pivot_')
})
}

Expand Down
11 changes: 11 additions & 0 deletions src/Lucid/QueryBuilder/methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,24 @@ methods.fetch = function (target) {
*/
const globalScope = target.HostModel.globalScope
let eagerlyFetched = []

/**
* call all global scopes before executing the query
* chain. This is the last time someone can modify
* the existing query chain
*/
if (_.size(globalScope)) {
_.each(globalScope, (scopeMethod) => {
scopeMethod(this)
})
}

let results = yield target.modelQueryBuilder

/**
* eagerly fetch all relations which are set for eagerLoad and
* also the previous query execution returned some results.
*/
if (_.size(target.eagerLoad.withRelations) && _.size(results)) {
eagerlyFetched = yield target.eagerLoad.load(results, target.HostModel)
}
Expand Down
9 changes: 7 additions & 2 deletions src/Lucid/Relations/BelongsTo.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

const Relation = require('./Relation')
const NE = require('node-exceptions')
const CatLog = require('cat-log')
const logger = new CatLog('adonis:lucid')
class ModelRelationAssociateException extends NE.LogicalException {}
class ModelRelationSaveException extends NE.LogicalException {}

Expand Down Expand Up @@ -67,11 +69,14 @@ class BelongsTo extends Relation {
*/
associate (relatedInstance) {
if (relatedInstance instanceof this.related === false) {
throw new ModelRelationAssociateException('associate accepts an instance of related model')
throw new ModelRelationAssociateException('Associate accepts an instance of related model')
}
if (!relatedInstance[this.toKey]) {
if (relatedInstance.isNew()) {
throw new ModelRelationAssociateException('Cannot associate an unsaved related model')
}
if (!relatedInstance[this.toKey]) {
logger.warn(`Trying to associate relationship with ${this.toKey} as foriegnKey, whose value is falsy`)
}
this.parent[this.fromKey] = relatedInstance[this.toKey]
}

Expand Down
102 changes: 93 additions & 9 deletions src/Lucid/Relations/BelongsToMany.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const Relation = require('./Relation')
const NE = require('node-exceptions')
const _ = require('lodash')
const helpers = require('../QueryBuilder/helpers')
const CatLog = require('cat-log')
const logger = new CatLog('adonis:lucid')
class ModelRelationException extends NE.LogicalException {}
class ModelRelationSaveException extends NE.LogicalException {}
class ModelRelationAttachException extends NE.LogicalException {}
Expand All @@ -25,20 +27,32 @@ class BelongsToMany extends Relation {
this.parent = parent
this.related = related
this.relatedQuery = this.related.query()
this.pivotTable = pivotTable
this.toKey = relatedPrimaryKey
this.fromKey = primaryKey
this.pivotLocalKey = pivotLocalKey
this.pivotOtherKey = pivotOtherKey
this.pivotTable = pivotTable // post_comment
this.toKey = relatedPrimaryKey // comments -> id
this.fromKey = primaryKey // post -> id
this.pivotLocalKey = pivotLocalKey // post_id
this.pivotOtherKey = pivotOtherKey // comment_id
this.pivotPrefix = '_pivot_'
this.pivotItems = []

/**
* helper method to query the pivot table. One
* can also do it manually by prefixing the
* pivot table name.
*/
this.relatedQuery.wherePivot = function () {
const args = _.toArray(arguments)
args[0] = `${self.pivotTable}.${args[0]}`
this.where.apply(this, args)
}
}

/**
* makes the join query to be used by other
* methods.
*
* @public
*/
_makeJoinQuery () {
const self = this
const selectionKeys = [
Expand Down Expand Up @@ -66,8 +80,11 @@ class BelongsToMany extends Relation {
* @public
*/
fetch () {
if (this.parent.isNew()) {
throw new ModelRelationException('Cannot fetch related model from an unsaved model instance')
}
if (!this.parent[this.fromKey]) {
throw new ModelRelationException('cannot fetch related model from an unsaved model instance')
logger.warn(`Trying to fetch relationship with ${this.fromKey} as primaryKey, whose value is falsy`)
}
this._makeJoinQuery()
this.relatedQuery.where(`${this.pivotTable}.${this.pivotLocalKey}`, this.parent[this.fromKey])
Expand All @@ -84,8 +101,11 @@ class BelongsToMany extends Relation {
* @public
*/
first () {
if (this.parent.isNew()) {
throw new ModelRelationException('Cannot fetch related model from an unsaved model instance')
}
if (!this.parent[this.fromKey]) {
throw new ModelRelationException('cannot fetch related model from an unsaved model instance')
logger.warn(`Trying to fetch relationship with ${this.fromKey} as primaryKey, whose value is falsy`)
}
this._makeJoinQuery()
this.relatedQuery.where(`${this.pivotTable}.${this.pivotLocalKey}`, this.parent[this.fromKey])
Expand Down Expand Up @@ -157,8 +177,17 @@ class BelongsToMany extends Relation {
*/
* attach (references, pivotValues) {
pivotValues = pivotValues || {}

if (!_.isArray(references) && !_.isObject(references)) {
throw new ModelRelationAttachException('attach expects an array or an object of values to be attached')
}

if (this.parent.isNew()) {
throw new ModelRelationAttachException('Cannot attach values for an unsaved model instance')
}

if (!this.parent[this.fromKey]) {
throw new ModelRelationAttachException('cannot attach values for an unsaved model')
logger.warn(`Trying to attach values with ${this.fromKey} as primaryKey, whose value is falsy`)
}

if (_.isArray(references)) {
Expand All @@ -178,17 +207,57 @@ class BelongsToMany extends Relation {
yield this.relatedQuery.queryBuilder.table(this.pivotTable).insert(values)
}

/**
* removes the relationship stored inside a pivot table. If
* references are not defined all relationships will be
* deleted
* @method detach
* @param {Array} [references]
* @return {Number}
*
* @public
*/
* detach (references) {
if (this.parent.isNew()) {
throw new ModelRelationException('Cannot detach values for an unsaved model instance')
}
if (!this.parent[this.fromKey]) {
throw new ModelRelationAttachException('cannot detach values for an unsaved model')
logger.warn(`Trying to attach values with ${this.fromKey} as primaryKey, whose value is falsy`)
}
const query = this.relatedQuery.queryBuilder.table(this.pivotTable).where(`${this.pivotLocalKey}`, this.parent[this.fromKey])

/**
* if references have been passed, then only remove them
*/
if (_.isArray(references)) {
query.whereIn(`${this.pivotOtherKey}`, references)
}
return yield query.delete()
}

/**
* shorthand for detach and then attach
*
* @param {Array} [references]
* @param {Object} [pivotValues]
* @return {Number}
*
* @public
*/
* sync (references, pivotValues) {
yield this.detach()
return yield this.attach(references, pivotValues)
}

/**
* saves the related model and creates the relationship
* inside the pivot table.
*
* @param {Object} relatedInstance
* @return {Boolean}
*
* @public
*/
* save (relatedInstance) {
if (relatedInstance instanceof this.related === false) {
throw new ModelRelationSaveException('save accepts an instance of related model')
Expand All @@ -205,6 +274,21 @@ class BelongsToMany extends Relation {
return isSaved
}

/**
* creates the related model instance and calls save on it
*
* @param {Object} values
* @return {Boolean}
*
* @public
*/
* create (values) {
const RelatedModel = this.related
const relatedInstance = new RelatedModel(values)
yield this.save(relatedInstance)
return relatedInstance
}

}

module.exports = BelongsToMany
28 changes: 19 additions & 9 deletions src/Lucid/Relations/Relation.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

require('harmony-reflect')
const proxyHandler = require('./proxyHandler')
const CatLog = require('cat-log')
const logger = new CatLog('adonis:lucid')
const NE = require('node-exceptions')
class ModelRelationException extends NE.LogicalException {}
class ModelRelationSaveException extends NE.LogicalException {}
Expand All @@ -29,8 +31,11 @@ class Relation {
* @public
*/
first () {
if (this.parent.isNew()) {
throw new ModelRelationException('Cannot fetch related model from an unsaved model instance')
}
if (!this.parent[this.fromKey]) {
throw new ModelRelationException('cannot fetch related model from an unsaved model instance')
logger.warn(`Trying to fetch relationship with ${this.fromKey} as primaryKey, whose value is falsy`)
}
this.relatedQuery.where(this.toKey, this.parent[this.fromKey])
return this.relatedQuery.first()
Expand All @@ -44,8 +49,11 @@ class Relation {
* @public
*/
fetch () {
if (this.parent.isNew()) {
throw new ModelRelationException('Cannot fetch related model from an unsaved model instance')
}
if (!this.parent[this.fromKey]) {
throw new ModelRelationException('cannot fetch related model from an unsaved model instance')
logger.warn(`Trying to fetch relationship with ${this.fromKey} as primaryKey, whose value is falsy`)
}
this.relatedQuery.where(this.toKey, this.parent[this.fromKey])
return this.relatedQuery.fetch()
Expand Down Expand Up @@ -90,10 +98,13 @@ class Relation {
*/
* save (relatedInstance) {
if (relatedInstance instanceof this.related === false) {
throw new ModelRelationSaveException('save accepts an instance of related model')
throw new ModelRelationSaveException('Save accepts an instance of related model')
}
if (this.parent.isNew()) {
throw new ModelRelationSaveException('Cannot save relation for an unsaved model instance')
}
if (!this.parent[this.fromKey]) {
throw new ModelRelationSaveException('cannot save relation for an unsaved model instance')
logger.warn(`Trying to save relationship with ${this.fromKey} as primaryKey, whose value is falsy`)
}
relatedInstance[this.toKey] = this.parent[this.fromKey]
return yield relatedInstance.save()
Expand All @@ -109,11 +120,10 @@ class Relation {
* @public
*/
* create (values) {
if (!this.parent[this.fromKey]) {
throw new ModelRelationSaveException('cannot create relation for an unsaved model instance')
}
values[this.toKey] = this.parent[this.fromKey]
return yield this.related.create(values)
const RelatedModel = this.related
const relatedInstance = new RelatedModel(values)
yield this.save(relatedInstance)
return relatedInstance
}

}
Expand Down
Loading

0 comments on commit 6892543

Please sign in to comment.