Skip to content

Commit

Permalink
feat(relations): add basic support for hasMany
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Jun 26, 2017
1 parent 78251ad commit eed5368
Show file tree
Hide file tree
Showing 5 changed files with 684 additions and 18 deletions.
44 changes: 26 additions & 18 deletions src/Lucid/Model/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -903,23 +903,20 @@ class Model {
if (this.$relations[key]) {
throw new Error('Trying to reset twice')
}
this.$relations[key] = value

/**
* If related value exists, then see if it's an array
* or not. Each model instance whether inside an array
* or not should have a parent.
*
* HOPE MAKES SENSE :)
*
* If not, then `hasOne` set the value to a model instances
* and `hasMany` set it to a collection of instances.
*/
if (_.size(value)) {
const arrayify = _.isArray(value) ? value : [value]
_(arrayify).filter((val) => !!val).each((val) => val.$parent = this.constructor.name)
if (!value) {
return
}

this.$relations[key] = value
if (value instanceof Model) {
value.$parent = this.constructor.name
return
}

if (value.rows) {
_(value.rows).filter((val) => !!val).each((val) => val.$parent = this.constructor.name)
}
}

/**
Expand Down Expand Up @@ -972,20 +969,31 @@ class Model {
}

/**
* Returns an instance of hasOne relation.
* Returns an instance of @ref('HasOne') relation.
*
* @method hasOne
*
* @param {String} relatedModel
* @param {String} primaryKey
* @param {String} foreignKey
* @param {String|Class} relatedModel
* @param {String} primaryKey
* @param {String} foreignKey
*
* @return {HasOne}
*/
hasOne (relatedModel, primaryKey = this.constructor.primaryKey, foreignKey = this.constructor.foreignKey) {
return new HasOne(this, relatedModel, primaryKey, foreignKey)
}

/**
* Returns an instance of @ref('HasMany') relation
*
* @method hasMany
*
* @param {String|Class} relatedModel
* @param {String} primaryKey
* @param {String} foreignKey
*
* @return {HasMany}
*/
hasMany (relatedModel, primaryKey = this.constructor.primaryKey, foreignKey = this.constructor.foreignKey) {
return new HasMany(this, relatedModel, primaryKey, foreignKey)
}
Expand Down
83 changes: 83 additions & 0 deletions src/Lucid/Relations/HasMany.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,93 @@
* file that was distributed with this source code.
*/

const _ = require('lodash')
const BaseRelation = require('./BaseRelation')
const CE = require('../../Exceptions')

class HasMany extends BaseRelation {
/**
* Load a single relationship from parent to child
* model, but only for one row.
*
* @method load
*
* @param {String|Number} value
*
* @return {Model}
*/
load () {
return this.relatedQuery.where(this.foreignKey, this.$primaryKeyValue).fetch()
}

/**
* Returns an array of values to be used for running
* whereIn query when eagerloading relationships.
*
* @method mapValues
*
* @param {Array} modelInstances - An array of model instances
*
* @return {Array}
*/
mapValues (modelInstances) {
return _.map(modelInstances, (modelInstance) => modelInstance[this.primaryKey])
}

/**
* Takes an array of related instances and returns an array
* for each parent record.
*
* @method group
*
* @param {Array} relatedInstances
*
* @return {Object} @multiple([key=String, values=Array, defaultValue=Null])
*/
group (relatedInstances) {
const Serializer = this.relatedModel.serializer

const transformedValues = _.transform(relatedInstances, (result, relatedInstance) => {
const foreignKeyValue = relatedInstance[this.foreignKey]
const existingRelation = _.find(result, (row) => row.identity === foreignKeyValue)

/**
* If there is already an existing instance for same parent
* record. We should override the value and do WARN the
* user since hasOne should never have multiple
* related instance.
*/
if (existingRelation) {
existingRelation.value.addRow(relatedInstance)
return result
}

result.push({
identity: foreignKeyValue,
value: new Serializer([relatedInstance])
})
return result
}, [])

return { key: this.primaryKey, values: transformedValues, defaultValue: new Serializer([]) }
}

/**
* Returns the related where query
*
* @method relatedWhere
*
* @param {Boolean} count
*
* @return {Object}
*/
relatedWhere (count) {
this.relatedQuery.whereRaw(`${this.$primaryTable}.${this.primaryKey} = ${this.$foriegnTable}.${this.foreignKey}`)
if (count) {
this.relatedQuery.count('*')
}
return this.relatedQuery.query
}
}

module.exports = HasMany
4 changes: 4 additions & 0 deletions src/Lucid/Serializers/Collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ class Collection {
return json
}

addRow (row) {
this.rows.push(row)
}

first () {
return _.first(this.rows)
}
Expand Down
8 changes: 8 additions & 0 deletions test/unit/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ module.exports = {
table.timestamps()
table.timestamp('deleted_at').nullable()
}),
db.schema.createTable('parts', function (table) {
table.increments()
table.integer('car_id')
table.string('part_name')
table.timestamps()
table.timestamp('deleted_at').nullable()
}),
db.schema.createTable('profiles', function (table) {
table.increments()
table.integer('user_id')
Expand Down Expand Up @@ -114,6 +121,7 @@ module.exports = {
return Promise.all([
db.schema.dropTable('users'),
db.schema.dropTable('cars'),
db.schema.dropTable('parts'),
db.schema.dropTable('profiles'),
db.schema.dropTable('pictures'),
db.schema.dropTable('identities'),
Expand Down

0 comments on commit eed5368

Please sign in to comment.